
6 changed files with 1077 additions and 0 deletions
@ -0,0 +1,194 @@ |
|||||
|
package foundation.pEp.jniadapter.test.speedtest; |
||||
|
|
||||
|
import java.text.ParseException; |
||||
|
import java.util.regex.*; |
||||
|
|
||||
|
/** |
||||
|
* MT999 is a Free Format Message. |
||||
|
*/ |
||||
|
|
||||
|
public class MT999 extends SWIFTMsg { |
||||
|
|
||||
|
String trn; |
||||
|
String rr; |
||||
|
String narrative; |
||||
|
|
||||
|
/** |
||||
|
* Construct MT999 message by parsing. |
||||
|
* |
||||
|
* @param txt text to parse |
||||
|
* @throws ParseException if not a valid MT999 message |
||||
|
*/ |
||||
|
|
||||
|
public MT999(String txt) throws ParseException { |
||||
|
Matcher m = MTConstants.mt999_pattern.matcher(txt); |
||||
|
if (!m.matches()) |
||||
|
throw new ParseException("not a valid MT999 message", 0); |
||||
|
|
||||
|
// retrieve Basic Header and Application Header fields
|
||||
|
retrieveHeader(m); |
||||
|
|
||||
|
// no User Header Block
|
||||
|
|
||||
|
// Text Block
|
||||
|
trn = m.group("trn"); |
||||
|
rr = m.group("rr"); |
||||
|
narrative = m.group("narrative"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Construct MT999 message. |
||||
|
* |
||||
|
* @param srcAddress is sender's address |
||||
|
* @param dstAddress is receiver's address |
||||
|
* @param dir <code>"I"</code> for incoming message <code>"O"</code> for |
||||
|
* outgoing message |
||||
|
* @param id transaction id |
||||
|
* @param payload freeform text |
||||
|
* @param session session number |
||||
|
* @param sequence sequence number |
||||
|
*/ |
||||
|
|
||||
|
public MT999(String srcAddress, String dstAddress, String dir, String id, String relref, String payload, |
||||
|
String session, String sequence) { |
||||
|
|
||||
|
if (srcAddress.length() != 12) |
||||
|
throw new IllegalArgumentException("srcAddress must be 12 characters"); |
||||
|
if (dstAddress.length() != 12) |
||||
|
throw new IllegalArgumentException("dstAddress must be 12 characters"); |
||||
|
if (dir.compareTo("I") != 0 && dir.compareTo("O") != 0) |
||||
|
throw new IllegalArgumentException("dir must be I or O"); |
||||
|
if (id.length() < 1 || id.length() > 16) |
||||
|
throw new IllegalArgumentException("id must be between 1 and 12 characters"); |
||||
|
if (relref.length() > 16) |
||||
|
throw new IllegalArgumentException("related reference must be at most 16 characters"); |
||||
|
if (payload.length() < 1) |
||||
|
throw new IllegalArgumentException("payload must have a value"); |
||||
|
|
||||
|
if (session.length() != 4) |
||||
|
throw new IllegalArgumentException("session must be 4 digits"); |
||||
|
if (sequence.length() != 6) |
||||
|
throw new IllegalArgumentException("sequence must be 6 digits"); |
||||
|
|
||||
|
mt999(); |
||||
|
|
||||
|
logicalTerminalAddress = srcAddress; |
||||
|
destinationAddress = dstAddress; |
||||
|
inputIdentifier = dir; |
||||
|
|
||||
|
trn = id; |
||||
|
rr = relref; |
||||
|
narrative = payload; |
||||
|
|
||||
|
sessionNumber = session; |
||||
|
sequenceNumber = sequence; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Construct MT999 message with defaults for session and sequence. |
||||
|
* |
||||
|
* @param srcAddress is sender's address |
||||
|
* @param dstAddress is receiver's address |
||||
|
* @param dir <code>"I"</code> for incoming message <code>"O"</code> for |
||||
|
* outgoing message |
||||
|
* @param id transaction id |
||||
|
* @param payload freeform text |
||||
|
*/ |
||||
|
|
||||
|
public MT999(String srcAddress, String dstAddress, String dir, String id, String relref, String payload) { |
||||
|
|
||||
|
|
||||
|
if (srcAddress.length() != 12) |
||||
|
throw new IllegalArgumentException("srcAddress must be 12 characters"); |
||||
|
if (dstAddress.length() != 12) |
||||
|
throw new IllegalArgumentException("dstAddress must be 12 characters"); |
||||
|
if (dir.compareTo("I") != 0 && dir.compareTo("O") != 0) |
||||
|
throw new IllegalArgumentException("dir must be I or O"); |
||||
|
if (id.length() < 1 || id.length() > 16) |
||||
|
throw new IllegalArgumentException("id must be between 1 and 12 characters"); |
||||
|
if (relref.length() > 16) |
||||
|
throw new IllegalArgumentException("related reference must be at most 16 characters"); |
||||
|
if (payload.length() < 1) |
||||
|
throw new IllegalArgumentException("payload must have a value"); |
||||
|
|
||||
|
mt999(); |
||||
|
|
||||
|
logicalTerminalAddress = srcAddress; |
||||
|
destinationAddress = dstAddress; |
||||
|
inputIdentifier = dir; |
||||
|
|
||||
|
trn = id; |
||||
|
rr = relref; |
||||
|
narrative = payload; |
||||
|
|
||||
|
sessionNumber = "0000"; |
||||
|
sequenceNumber = "000000"; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Convert MT999 message by composing. |
||||
|
* |
||||
|
* @param fmt rendering format |
||||
|
* @return string representation of MT999 message |
||||
|
*/ |
||||
|
|
||||
|
public String toString(MTConstants.Format fmt) { |
||||
|
String result; |
||||
|
// FIXME: rework to stringbuffer
|
||||
|
switch (fmt) { |
||||
|
case MTFIN: |
||||
|
// Basic Header Block
|
||||
|
result = "{1:"; |
||||
|
result += applicationIdentifier; |
||||
|
result += serviceIdentifier; |
||||
|
result += logicalTerminalAddress; |
||||
|
result += sessionNumber; |
||||
|
result += sequenceNumber; |
||||
|
result += "}"; |
||||
|
|
||||
|
// Application Header Block
|
||||
|
result += "{2:"; |
||||
|
result += inputIdentifier; |
||||
|
result += messageType; |
||||
|
result += destinationAddress; |
||||
|
result += messagePriority; |
||||
|
result += "}"; |
||||
|
|
||||
|
// no User Header Block
|
||||
|
|
||||
|
// Text Block
|
||||
|
result += "{4:\n"; |
||||
|
result += ":20:" + trn + "\n"; |
||||
|
if (rr != null && rr.length() > 0) |
||||
|
result += ":21:" + rr +"\n"; |
||||
|
result += ":79:\n" + narrative; |
||||
|
result += "\n-}"; |
||||
|
return result; |
||||
|
|
||||
|
case MTXML: |
||||
|
throw new UnsupportedOperationException("MTXML not yet implemented"); |
||||
|
} |
||||
|
|
||||
|
throw new AssertionError("this should never happen"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Convert MT999 message by composing. |
||||
|
* |
||||
|
* @return string representation of MT999 message |
||||
|
*/ |
||||
|
|
||||
|
public String toString() { |
||||
|
return toString(MTConstants.Format.MTFIN); |
||||
|
} |
||||
|
|
||||
|
// MT999 specifica
|
||||
|
|
||||
|
private void mt999() { |
||||
|
applicationIdentifier = "F"; // FIN
|
||||
|
serviceIdentifier = "01"; // FIN
|
||||
|
messageType = "999"; |
||||
|
messagePriority = "N"; |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,60 @@ |
|||||
|
package foundation.pEp.jniadapter.test.speedtest; |
||||
|
|
||||
|
import java.util.regex.Pattern; |
||||
|
|
||||
|
public class MTConstants { |
||||
|
|
||||
|
/** |
||||
|
* Rendering Format of a SWIFT message. |
||||
|
*/ |
||||
|
|
||||
|
enum Format { |
||||
|
MTFIN, MTXML |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* SWIFTMessages have a Basic Header Block and an Application Header Block |
||||
|
*/ |
||||
|
|
||||
|
static final String mt_regex = "(?ms)" |
||||
|
// Basic Header Block
|
||||
|
+ "\\{1:" //
|
||||
|
+ "(?<ai>\\w)" //
|
||||
|
+ "(?<si>\\d{2})" //
|
||||
|
+ "(?<lta>\\w{12})" //
|
||||
|
+ "(?<sn>\\d{4})" //
|
||||
|
+ "(?<sqn>\\d{5,6})" //
|
||||
|
+ "\\}" |
||||
|
|
||||
|
// Application Header Block
|
||||
|
+ "\\{2:" //
|
||||
|
+ "(?<ii>I|O)" //
|
||||
|
+ "(?<mt>\\d{3})" //
|
||||
|
+ "(\\d{10})?" //
|
||||
|
+ "(?<da>\\w{12})" //
|
||||
|
+ "(.*)?" //
|
||||
|
+ "(?<mp>U|N|S)" //
|
||||
|
+ "\\}" |
||||
|
|
||||
|
// FIXME: User Header Block (the wrong way). The trailing .* is to make it useable for basic swift and mt999 ...
|
||||
|
+ "(\\{3:?(.*)\\})?(.*)" //
|
||||
|
; |
||||
|
|
||||
|
static final Pattern mt_pattern = Pattern.compile(mt_regex); |
||||
|
|
||||
|
static final String mt999_regex = mt_regex |
||||
|
// Text Block
|
||||
|
+ "\\{4:\n" //
|
||||
|
+ ":20:(?<trn>\\w{1,16})\n" //
|
||||
|
+ "(:21:(?<rr>.{1,16})\n)?" //
|
||||
|
+ ":79:(?<narrative>.*?)" //
|
||||
|
+ "\n-\\}" //
|
||||
|
// trailer
|
||||
|
+ ".*" |
||||
|
|
||||
|
; |
||||
|
|
||||
|
static final Pattern mt999_pattern = Pattern.compile(mt999_regex); |
||||
|
|
||||
|
} |
||||
|
|
@ -0,0 +1,480 @@ |
|||||
|
package foundation.pEp.jniadapter.test.speedtest; |
||||
|
|
||||
|
import java.nio.charset.StandardCharsets; |
||||
|
import java.text.ParseException; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.Properties; |
||||
|
import java.util.Vector; |
||||
|
import java.util.regex.Matcher; |
||||
|
import java.util.regex.Pattern; |
||||
|
|
||||
|
import foundation.pEp.jniadapter.Blob; |
||||
|
import foundation.pEp.jniadapter.Engine; |
||||
|
import foundation.pEp.jniadapter.Identity; |
||||
|
import foundation.pEp.jniadapter.Message; |
||||
|
import foundation.pEp.jniadapter.Pair; |
||||
|
|
||||
|
/** |
||||
|
* MTMsgCodec is the Codec class for encoding/decoding p≡p for SWIFT messages. |
||||
|
* |
||||
|
* <p> |
||||
|
* See <a href= |
||||
|
* "https://www.sepaforcorporates.com/swift-for-corporates/read-swift-message-structure/" |
||||
|
* target="_blank">SWIFT message structure</a>, <a href= |
||||
|
* "https://www.ibm.com/support/knowledgecenter/SSRH32_3.0.0_SWS/mt_msg_format.html" |
||||
|
* target="_blank">MT message format</a>, <a href= |
||||
|
* "http://www.iotafinance.com/en/SWIFT-ISO15022-Message-type-MT999.html" target |
||||
|
* ="_blank">MT999</a> |
||||
|
* </p> |
||||
|
* <p> |
||||
|
* Copyright 2019, <a href="https://pep.security" target="_blank">p≡p |
||||
|
* Security</a>. |
||||
|
* </p> |
||||
|
* |
||||
|
* @author Volker Birk |
||||
|
* @version %I% %G% |
||||
|
*/ |
||||
|
|
||||
|
// FIXME: rework to stringbuffer
|
||||
|
|
||||
|
public class MTMsgCodec { |
||||
|
public Engine pEp; |
||||
|
|
||||
|
/** |
||||
|
* Constructs a MessageCodec using p≡p engine. |
||||
|
* |
||||
|
* @param pEp_engine p≡p engine to use |
||||
|
*/ |
||||
|
|
||||
|
public MTMsgCodec(Engine pEp_engine) { |
||||
|
pEp = pEp_engine; |
||||
|
} |
||||
|
|
||||
|
private final static String magicKeys = "pEpKeys"; |
||||
|
private final static String magicEnc = "pEpMessage"; |
||||
|
|
||||
|
private final static String pgp_regex = "(?ms)" + "-----BEGIN PGP (?<type>.*?)-----\n" + "(\\w:.*?$\n)*" + "\n" |
||||
|
+ "(?<block>.*?)" + "-----END PGP (.*?)-----.*"; |
||||
|
|
||||
|
private static final Pattern pgp_pattern = Pattern.compile(pgp_regex); |
||||
|
|
||||
|
private static final String pgptypeMessage = "MESSAGE"; |
||||
|
private static final String pgptypePubkey = "PUBLIC KEY BLOCK"; |
||||
|
|
||||
|
// FIXME: private final static String uri_regex = "payto://swift/(?<bic>\\w+)";
|
||||
|
private final static String uri_regex = "(?<bic>\\w+)@BIC"; |
||||
|
|
||||
|
private final static Pattern uri_pattern = Pattern.compile(uri_regex); |
||||
|
|
||||
|
/** |
||||
|
* Strips PGP header and footer from encryption or key data. |
||||
|
* |
||||
|
* @param pgp_text text to work on |
||||
|
* @throws ParseException if text is not valid PGP data |
||||
|
* @return <code>pgp_text</code> without PGP header |
||||
|
*/ |
||||
|
|
||||
|
protected static String stripFromPGP(String pgp_text) throws ParseException { |
||||
|
|
||||
|
Matcher m = pgp_pattern.matcher(pgp_text); |
||||
|
if (!m.matches()) |
||||
|
throw new ParseException("not a PGP block", 0); |
||||
|
|
||||
|
return m.group("block"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Adds PGP header and footer. |
||||
|
* |
||||
|
* @param payload text to decorate |
||||
|
* @param pgp_type PGP data type |
||||
|
* @return <code>payload</code> with added header and footer |
||||
|
*/ |
||||
|
|
||||
|
protected static String addPGPHeader(String payload, String pgp_type) { |
||||
|
// FIXME: rework to stringbuffer
|
||||
|
return "-----BEGIN PGP " + pgp_type + "-----\n\n" + payload + "\n-----END PGP " + pgp_type + "-----\n"; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Decodes a BIC from an URI. |
||||
|
* |
||||
|
* @param uri the URI to decode from |
||||
|
* @throws ParseException if URI has not the correct form |
||||
|
* @return decoded BIC |
||||
|
*/ |
||||
|
|
||||
|
public static String decodeBIC(String uri) throws ParseException { |
||||
|
Matcher m = uri_pattern.matcher(uri); |
||||
|
if (!m.matches()) |
||||
|
throw new ParseException("not a valid URI", 0); |
||||
|
|
||||
|
return m.group("bic"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Encodes a BIC into an URI. |
||||
|
* |
||||
|
* @param bic BIC to encode |
||||
|
* @return encoded URI |
||||
|
*/ |
||||
|
|
||||
|
public static String encodeBIC(String bic) { |
||||
|
// return "payto://swift/" + bic
|
||||
|
return bic + "@BIC"; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Generates a list of transporting MT999 messages for a payload. |
||||
|
* |
||||
|
* @param from source address |
||||
|
* @param to destination address |
||||
|
* @param ii message direction |
||||
|
* @param trn message id |
||||
|
* @param payload payload to split |
||||
|
* @param magic magic string to mark as p≡p message |
||||
|
* |
||||
|
* @return array of String with MT999 messages |
||||
|
*/ |
||||
|
|
||||
|
protected String[] transportMT999(String from, String to, String ii, String trn, String rr, String payload, |
||||
|
String magic) { |
||||
|
|
||||
|
Vector<String> result = new Vector<String>(); |
||||
|
payload = payload.trim(); |
||||
|
|
||||
|
int j = 1, f = 0, t = 0; |
||||
|
for (int i = payload.indexOf("\n"); i != -1; i = payload.indexOf("\n", i + 1), j++) { |
||||
|
|
||||
|
if (j % 34 == 0) { |
||||
|
t = i + 1; |
||||
|
String cont = t < payload.length() - 1 ? "." : ""; |
||||
|
MT999 mt999 = new MT999(from, to, ii, trn, rr, magic + "\n" + payload.substring(f, t) + cont); |
||||
|
result.add(mt999.toString()); |
||||
|
f = i + 1; |
||||
|
} |
||||
|
} |
||||
|
if (t < payload.length() - 1) { |
||||
|
int z = payload.charAt(payload.length() - 1) == '\n' ? 1 : 0; |
||||
|
MT999 mt999 = new MT999(from, to, ii, "23", // fixed trn
|
||||
|
"", magic + "\n" + payload.substring(t, payload.length() - z)); |
||||
|
result.add(mt999.toString()); |
||||
|
} |
||||
|
|
||||
|
return result.toArray(new String[result.size()]); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* encodes a p≡p Message to a String with a serialized p≡p for SWIFT message. |
||||
|
* |
||||
|
* @param msg p≡p Message to encode if from or to are not set they're taken |
||||
|
* by parsing the header of <code>longmsg</code>; in this case |
||||
|
* message direction is being taken from there, too |
||||
|
* @param config properties with configuration |
||||
|
* @return String with encoded p≡p for SWIFT message |
||||
|
* @throws UnsupportedOperationException if <code>EncFormat</code> is other than |
||||
|
* <code>None</code>, <code>Inline</code> |
||||
|
* or <code>PEP</code> |
||||
|
*/ |
||||
|
|
||||
|
public String encode(Message msg, Properties config) { |
||||
|
if (msg.getLongmsg() == null || msg.getLongmsg().length() < 1) |
||||
|
throw new IllegalArgumentException("longmsg must contain the message"); |
||||
|
|
||||
|
String result = ""; |
||||
|
|
||||
|
String msgid = msg.getId() != null && msg.getId().length() > 0 ? msg.getId() : "23"; |
||||
|
String dir = msg.getDir() == Message.Direction.Incoming ? "I" : "O"; |
||||
|
|
||||
|
if (msgid.length() > 12) { |
||||
|
if (msgid.substring(0, 3).compareTo("pEp.") == 0 && msgid.length() >= 15) |
||||
|
msgid = msgid.substring(4, 15); |
||||
|
else |
||||
|
msgid = msgid.substring(0, 11); |
||||
|
} |
||||
|
|
||||
|
String from = msg.getFrom() != null && msg.getFrom().address.length() > 0 ? msg.getFrom().address : null; |
||||
|
|
||||
|
String to = msg.getTo() != null && msg.getTo().size() > 0 && msg.getTo().elementAt(0).address.length() > 0 |
||||
|
? msg.getTo().elementAt(0).address |
||||
|
: null; |
||||
|
|
||||
|
if (from != null) { |
||||
|
try { |
||||
|
from = decodeBIC(from); |
||||
|
if (from.length() != 12) |
||||
|
throw new IllegalArgumentException("from address must be URI with BIC12"); |
||||
|
} catch (ParseException ex) { |
||||
|
throw new IllegalArgumentException("from address must be URI with BIC12"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (to != null) { |
||||
|
try { |
||||
|
to = decodeBIC(to); |
||||
|
if (to.length() != 12) |
||||
|
throw new IllegalArgumentException("to address must be URI with BIC12"); |
||||
|
} catch (ParseException ex) { |
||||
|
throw new IllegalArgumentException("to address must be URI with BIC12"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
switch (msg.getEncFormat()) { |
||||
|
case None: |
||||
|
result = msg.getLongmsg(); // we send the message unmodified
|
||||
|
if (result.substring(0, 3).compareTo("{1:") == 0) { |
||||
|
// probably SWIFT MTFIN
|
||||
|
|
||||
|
if (from == null || to == null || msgid == null) { |
||||
|
// parse SWIFT header
|
||||
|
SWIFTMsg mh = new SWIFTMsg(); |
||||
|
|
||||
|
try { |
||||
|
mh.parseHeader(result); |
||||
|
} catch (ParseException ex) { |
||||
|
throw new UnsupportedOperationException("unsupported message format"); |
||||
|
} |
||||
|
|
||||
|
if (from == null) |
||||
|
from = mh.logicalTerminalAddress; |
||||
|
if (to == null) |
||||
|
to = mh.destinationAddress; |
||||
|
dir = mh.inputIdentifier; |
||||
|
} |
||||
|
} else if (result.substring(0, 1).compareTo("<") == 0) { |
||||
|
// probably XML
|
||||
|
|
||||
|
// FIXME: let's do the job for MTXML, too
|
||||
|
return result; |
||||
|
} else { // we don't know this format, so let it be
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
if(msg.getAttachments() != null) |
||||
|
for (int i = 0; i < msg.getAttachments().size(); ++i) { // FIXME: can attachments become null?
|
||||
|
Blob attach = msg.getAttachments().elementAt(i); |
||||
|
String magic; |
||||
|
|
||||
|
if (attach.mime_type.compareToIgnoreCase("application/pgp-keys") == 0) |
||||
|
magic = magicKeys; |
||||
|
else |
||||
|
break; // don't know this MIME type
|
||||
|
|
||||
|
// we send an MT999 with the keys
|
||||
|
String payload; |
||||
|
try { |
||||
|
payload = stripFromPGP(new String(attach.data, StandardCharsets.UTF_8)); |
||||
|
} catch (ParseException ex) { |
||||
|
// cannot parse this
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
String[] msgs = transportMT999(from, to, dir, msgid, "", payload, magic); |
||||
|
|
||||
|
for (int j = 0; j < msgs.length; ++j) { |
||||
|
if (j > 0) |
||||
|
result += "\n"; |
||||
|
result += msgs[j].toString(); |
||||
|
} |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
case PEP: |
||||
|
case PGPMIME: |
||||
|
case Inline: |
||||
|
if (from == null || to == null) |
||||
|
throw new IllegalArgumentException("from and to must be set to URIs with BIC12"); |
||||
|
|
||||
|
String pgp_txt; |
||||
|
Vector<Blob> attachments = null; |
||||
|
|
||||
|
if (msg.getEncFormat() == Message.EncFormat.PEP || msg.getEncFormat() == Message.EncFormat.PGPMIME) { |
||||
|
if (msg.getAttachments() == null || msg.getAttachments().size() != 2) |
||||
|
throw new IllegalArgumentException("no valid message format"); |
||||
|
Blob attach = msg.getAttachments().elementAt(1); |
||||
|
pgp_txt = new String(attach.data, StandardCharsets.UTF_8); |
||||
|
} else /* Inline */ { |
||||
|
pgp_txt = msg.getLongmsg(); |
||||
|
} |
||||
|
|
||||
|
String payload; |
||||
|
try { |
||||
|
payload = stripFromPGP(pgp_txt); |
||||
|
} catch (ParseException ex) { |
||||
|
// cannot parse this
|
||||
|
throw new IllegalArgumentException("illegal encryption text"); |
||||
|
} |
||||
|
String[] msgs = transportMT999(from, to, dir, msgid, "", payload, magicEnc); |
||||
|
|
||||
|
for (int j = 0; j < msgs.length; ++j) { |
||||
|
if (j > 0) |
||||
|
result += "\n"; |
||||
|
result += msgs[j].toString(); |
||||
|
} |
||||
|
|
||||
|
if (msg.getEncFormat() == Message.EncFormat.Inline) { |
||||
|
String[] attached_key = null; |
||||
|
attachments = msg.getAttachments(); |
||||
|
|
||||
|
for (int i = 0; attachments != null && i < attachments.size(); ++i) { |
||||
|
Blob attach = attachments.elementAt(i); |
||||
|
if (attach.mime_type.compareTo("application/pgp-keys") == 0) { |
||||
|
// we send an MT999 with the keys
|
||||
|
try { |
||||
|
payload = stripFromPGP(new String(attach.data, StandardCharsets.UTF_8)); |
||||
|
} catch (ParseException ex) { |
||||
|
// cannot parse this
|
||||
|
break; |
||||
|
} |
||||
|
msgs = transportMT999(from, to, dir, msgid, "", payload, magicKeys); |
||||
|
|
||||
|
for (int j = 0; j < msgs.length; ++j) { |
||||
|
if (j > 0) |
||||
|
result += "\n"; |
||||
|
result += msgs[j].toString(); |
||||
|
} |
||||
|
} else { |
||||
|
throw new UnsupportedOperationException( |
||||
|
"only application/pgp-keys is supported with Inline but got " + attach.mime_type); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
default: |
||||
|
throw new UnsupportedOperationException("unsupported encryption format"); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Creates p≡p message from MTFIN or MTXML using SWIFT header info |
||||
|
* |
||||
|
* @param header MTFIN header structure |
||||
|
* @param txt MTFIN message text |
||||
|
* @return p≡p message |
||||
|
*/ |
||||
|
|
||||
|
protected Message pEpMessageFromSWIFTMessage(SWIFTMsg header, String txt) { |
||||
|
Message m = new Message(); |
||||
|
|
||||
|
Identity from = new Identity(); |
||||
|
from.address = encodeBIC(header.logicalTerminalAddress); |
||||
|
m.setFrom(from); |
||||
|
|
||||
|
Identity to = new Identity(); |
||||
|
to.address = encodeBIC(header.destinationAddress); |
||||
|
Vector<Identity> _to = new Vector<Identity>(); |
||||
|
_to.add(to); |
||||
|
m.setTo(_to); |
||||
|
|
||||
|
m.setDir(header.inputIdentifier.compareTo("I") == 0 ? Message.Direction.Incoming : Message.Direction.Outgoing); |
||||
|
|
||||
|
m.setLongmsg(txt); |
||||
|
|
||||
|
ArrayList<Pair<String, String>> al = new ArrayList<Pair<String, String>>(); |
||||
|
Pair<String, String> field = new Pair<String, String>("X-pEp-Version", pEp.getProtocolVersion()); |
||||
|
al.add(field); |
||||
|
m.setOptFields(al); |
||||
|
return m; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* decodes p≡p Messages from a String with serialized p≡p for SWIFT messages. |
||||
|
* |
||||
|
* @param txt String to decode from |
||||
|
* @throws ParseException if the String does not contain SWIFT messages only |
||||
|
* @return array with p≡p Messages |
||||
|
* |
||||
|
*/ |
||||
|
|
||||
|
public Message[] decode(String txt) throws ParseException { |
||||
|
Vector<Message> result = new Vector<Message>(); |
||||
|
|
||||
|
if (txt.substring(0, 3).compareTo("{1:") == 0) { |
||||
|
// probably SWIFT MTFIN
|
||||
|
|
||||
|
int f = 0; |
||||
|
int t = txt.indexOf("{1:", 3); |
||||
|
if (t == -1) |
||||
|
t = txt.length(); |
||||
|
|
||||
|
String key_payload = ""; |
||||
|
String enc_payload = ""; |
||||
|
while (f < txt.length()) { |
||||
|
String _txt = txt.substring(f, t); |
||||
|
|
||||
|
f = t; |
||||
|
t = txt.indexOf("{1:", f + 3); |
||||
|
if (t == -1) |
||||
|
t = txt.length(); |
||||
|
|
||||
|
Message last = null; |
||||
|
|
||||
|
SWIFTMsg m = new SWIFTMsg(); |
||||
|
m.parseHeader(_txt); |
||||
|
|
||||
|
boolean done = false; |
||||
|
if (m.messageType.compareTo("999") == 0) { |
||||
|
MT999 _m = new MT999(_txt); |
||||
|
String narrative = _m.narrative.trim(); |
||||
|
if (narrative.substring(0, magicKeys.length()).compareTo(magicKeys) == 0) { |
||||
|
key_payload += narrative.substring(magicKeys.length()).trim(); |
||||
|
if (key_payload.substring(key_payload.length()).compareTo(".") == 0) { |
||||
|
key_payload = key_payload.substring(0, key_payload.length() - 2); |
||||
|
} else { |
||||
|
// p≡p keys
|
||||
|
String keydata = addPGPHeader(key_payload, pgptypePubkey); |
||||
|
key_payload = ""; |
||||
|
|
||||
|
if (last == null) |
||||
|
last = pEpMessageFromSWIFTMessage(m, "p≡p keys"); |
||||
|
|
||||
|
Vector<Blob> a = new Vector<Blob>(); |
||||
|
Blob b = new Blob(); |
||||
|
b.data = keydata.getBytes(StandardCharsets.UTF_8); |
||||
|
b.mime_type = "application/pgp-keys"; |
||||
|
b.filename = "pEpKeys.asc"; |
||||
|
a.add(b); |
||||
|
last.setAttachments(a); |
||||
|
|
||||
|
result.add(last); |
||||
|
last = null; |
||||
|
|
||||
|
} |
||||
|
done = true; |
||||
|
} else if (narrative.substring(0, magicEnc.length()).compareTo(magicEnc) == 0) { |
||||
|
// p≡p encrypted data
|
||||
|
enc_payload += narrative.substring(magicEnc.length()).trim(); |
||||
|
if (enc_payload.substring(enc_payload.length()).compareTo(".") == 0) { |
||||
|
enc_payload = enc_payload.substring(0, enc_payload.length() - 2); |
||||
|
} else { |
||||
|
// p≡p encryption
|
||||
|
String encdata = addPGPHeader(enc_payload, pgptypeMessage); |
||||
|
Message r = pEpMessageFromSWIFTMessage(m, encdata); |
||||
|
r.setEncFormat(Message.EncFormat.Inline); |
||||
|
result.add(r); |
||||
|
} |
||||
|
done = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!done) { |
||||
|
last = pEpMessageFromSWIFTMessage(m, _txt); |
||||
|
result.add(last); |
||||
|
} |
||||
|
} |
||||
|
} else if (txt.substring(0, 1).compareTo("<") == 0) { |
||||
|
// probably XML
|
||||
|
|
||||
|
// FIXME: let's do the job for MTXML, too
|
||||
|
throw new UnsupportedOperationException("XML not yet implemented"); |
||||
|
} else { // we don't know this format
|
||||
|
throw new ParseException("not a valid SWIFT message", 0); |
||||
|
} |
||||
|
|
||||
|
Message[] _result = new Message[result.size()]; |
||||
|
return result.toArray(_result); |
||||
|
} |
||||
|
} |
@ -0,0 +1,275 @@ |
|||||
|
package foundation.pEp.jniadapter.test.speedtest; |
||||
|
|
||||
|
import java.text.ParseException; |
||||
|
import java.nio.file.Files; |
||||
|
import java.nio.file.Paths; |
||||
|
import java.nio.charset.StandardCharsets; |
||||
|
import java.util.Vector; |
||||
|
import java.util.Scanner; |
||||
|
import foundation.pEp.jniadapter.*; |
||||
|
|
||||
|
public class SpeedTest { |
||||
|
private static Engine pEp = new Engine(); |
||||
|
private static MTMsgCodec codec = new MTMsgCodec(pEp); |
||||
|
private static Identity me = new Identity(true); |
||||
|
private static Identity you = new Identity(); |
||||
|
|
||||
|
protected static void decodingTest(long n, String testDataEnc) { |
||||
|
for (long i=0; i<n; ++i) { |
||||
|
try { |
||||
|
Message[] msgs = codec.decode(testDataEnc); |
||||
|
Vector<String> keys = new Vector<String>(); |
||||
|
Engine.decrypt_message_Return ret = pEp.decrypt_message(msgs[0], keys, 0); |
||||
|
String txt = ret.dst.getLongmsg(); |
||||
|
} |
||||
|
catch (ParseException ex) { |
||||
|
System.err.println("error: parsing test data"); |
||||
|
System.exit(3); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected class DecodingThread extends Thread { |
||||
|
private long _n; |
||||
|
private String _testDataEnc; |
||||
|
|
||||
|
public DecodingThread(long n, String testDataEnc) |
||||
|
{ |
||||
|
_n = n; |
||||
|
_testDataEnc = testDataEnc; |
||||
|
} |
||||
|
|
||||
|
public void run() { |
||||
|
decodingTest(_n, _testDataEnc); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static Message encrypt(String data) { |
||||
|
Message m = new Message(); |
||||
|
m.setDir(Message.Direction.Outgoing); |
||||
|
m.setFrom(me); |
||||
|
Vector<Identity> to = new Vector<Identity>(); |
||||
|
to.add(you); |
||||
|
m.setTo(to); |
||||
|
m.setLongmsg(data); |
||||
|
return pEp.encrypt_message(m, null, Message.EncFormat.Inline); |
||||
|
} |
||||
|
|
||||
|
protected static void encodingTest(long n, String testData) { |
||||
|
for (long i=0; i<n; ++i) { |
||||
|
Message enc = encrypt(testData); |
||||
|
String txt = codec.encode(enc, null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected class EncodingThread extends Thread { |
||||
|
private long _n; |
||||
|
private String _testData; |
||||
|
|
||||
|
public EncodingThread(long n, String testData) |
||||
|
{ |
||||
|
_n = n; |
||||
|
_testData = testData; |
||||
|
} |
||||
|
|
||||
|
public void run() { |
||||
|
encodingTest(_n, _testData); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void main(String[] args) { |
||||
|
long decoding = 0; |
||||
|
long encoding = 0; |
||||
|
int deth = 1; |
||||
|
int enth = 1; |
||||
|
|
||||
|
MT999 testMessage = new MT999("232323232323", "424242424242", "O", "23", "", "Hello, world"); |
||||
|
String testData = testMessage.toString(); |
||||
|
|
||||
|
for (int i=0; i<args.length; ++i) { |
||||
|
if (args[i].compareTo("-h") == 0 || args[i].compareTo("--help") ==0) |
||||
|
{ |
||||
|
System.out.println("SpeedTest [-e |--encode NUMBER] [-d | --decode NUMBER] [-f | --file TESTDATA] [-jd | --decoding-threads DT] [-je | --encoding-threads] [-h | --help]\n" |
||||
|
+ "\nEncodes and/or decodes messages to measure the speed.\n\n" |
||||
|
+ " -d, --decode NUMBER decode NUMBER messages\n" |
||||
|
+ " -e, --encode NUMBER encode NUMBER messages\n" |
||||
|
+ " -f, --file TESTDATA file with test data as UTF-8 encoded text\n" |
||||
|
+ " -jd, --decoding-threads DT starting DT threads for decoding\n" |
||||
|
+ " -je, --encoding-threads ET starting ET threads for encoding\n" |
||||
|
+ " -h, --help show this help message\n" |
||||
|
+ "\nThis program encrypts and encodes, and decrypts and decodes test data\n" |
||||
|
+ "NUMBER times, respectively. If you omit -f it will encode a default data set.\n" |
||||
|
); |
||||
|
System.exit(0); |
||||
|
} |
||||
|
else if (args[i].compareTo("-d") == 0 || args[i].compareTo("--decode") == 0) { |
||||
|
try { |
||||
|
decoding = Long.parseLong(args[i+1]); |
||||
|
++i; |
||||
|
} |
||||
|
catch (NumberFormatException ex) { |
||||
|
System.err.println(String.format("error: decimal number expected but found %s", args[i+1])); |
||||
|
System.exit(1); |
||||
|
} |
||||
|
catch (ArrayIndexOutOfBoundsException ex) { |
||||
|
System.err.println(String.format("error: %s is requiring a decimal number as argument", args[i])); |
||||
|
System.exit(1); |
||||
|
} |
||||
|
} |
||||
|
else if (args[i].compareTo("-e") == 0 || args[i].compareTo("--encode") == 0) { |
||||
|
try { |
||||
|
encoding = Long.parseLong(args[i+1]); |
||||
|
++i; |
||||
|
} |
||||
|
catch (NumberFormatException ex) { |
||||
|
System.err.println(String.format("error: decimal number expected but found %s", args[i+1])); |
||||
|
System.exit(1); |
||||
|
} |
||||
|
catch (ArrayIndexOutOfBoundsException ex) { |
||||
|
System.err.println(String.format("error: %s is requiring a decimal number as argument", args[i])); |
||||
|
System.exit(1); |
||||
|
} |
||||
|
} |
||||
|
else if (args[i].compareTo("-f") == 0 || args[i].compareTo("--file") == 0) { |
||||
|
String filename = ""; |
||||
|
|
||||
|
try { |
||||
|
filename = args[i+1]; |
||||
|
++i; |
||||
|
} |
||||
|
catch (ArrayIndexOutOfBoundsException ex) { |
||||
|
System.err.println(String.format("error: %s is requiring a filename as argument", args[i])); |
||||
|
System.exit(1); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
if (filename.compareTo("-") == 0) { |
||||
|
Scanner s = new Scanner(System.in).useDelimiter("\\A"); |
||||
|
testData = s.hasNext() ? s.next() : ""; |
||||
|
} |
||||
|
else { |
||||
|
testData = new String(Files.readAllBytes(Paths.get(filename)), StandardCharsets.UTF_8); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
catch (Exception ex) { |
||||
|
System.err.println(String.format("error: cannot read file %s", args[i])); |
||||
|
System.exit(2); |
||||
|
} |
||||
|
} |
||||
|
else if (args[i].compareTo("-jd") == 0 || args[i].compareTo("----decoding-threads") == 0) { |
||||
|
try { |
||||
|
deth = Integer.parseInt(args[i+1]); |
||||
|
++i; |
||||
|
} |
||||
|
catch (NumberFormatException ex) { |
||||
|
System.err.println(String.format("error: decimal number expected but found %s", args[i+1])); |
||||
|
System.exit(1); |
||||
|
} |
||||
|
catch (ArrayIndexOutOfBoundsException ex) { |
||||
|
System.err.println(String.format("error: %s is requiring a decimal number as argument", args[i])); |
||||
|
System.exit(1); |
||||
|
} |
||||
|
} |
||||
|
else if (args[i].compareTo("-je") == 0 || args[i].compareTo("----encoding-threads") == 0) { |
||||
|
try { |
||||
|
enth = Integer.parseInt(args[i+1]); |
||||
|
++i; |
||||
|
} |
||||
|
catch (NumberFormatException ex) { |
||||
|
System.err.println(String.format("error: decimal number expected but found %s", args[i+1])); |
||||
|
System.exit(1); |
||||
|
} |
||||
|
catch (ArrayIndexOutOfBoundsException ex) { |
||||
|
System.err.println(String.format("error: %s is requiring a decimal number as argument", args[i])); |
||||
|
System.exit(1); |
||||
|
} |
||||
|
} |
||||
|
else { |
||||
|
System.err.println(String.format("illegal parameter: %s", args[i])); |
||||
|
System.exit(1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (decoding < 0 || encoding < 0 || !(encoding > 0 || decoding > 0)) { |
||||
|
System.err.println("arguments error: -d or -e (or both) must be used with a positive number"); |
||||
|
System.exit(1); |
||||
|
} |
||||
|
|
||||
|
if (deth < 1 || enth < 1 || deth > 1024 || enth > 1024) { |
||||
|
System.err.println("arguments error: -jd or -je must be used with a positive number (max 1024)"); |
||||
|
System.exit(1); |
||||
|
} |
||||
|
|
||||
|
me.address = MTMsgCodec.encodeBIC("232323232323"); |
||||
|
me.username = "sender"; |
||||
|
me.user_id = "23"; |
||||
|
pEp.myself(me); |
||||
|
|
||||
|
you.address = MTMsgCodec.encodeBIC("424242424242"); |
||||
|
you.username = "receiver"; |
||||
|
you.user_id = me.user_id; |
||||
|
pEp.myself(you); // make a key for it
|
||||
|
|
||||
|
Message enc = encrypt(testData); |
||||
|
String testDataEnc = codec.encode(enc, null); |
||||
|
|
||||
|
long startTime = System.nanoTime(); |
||||
|
|
||||
|
if (decoding > 0) { |
||||
|
if (deth == 1) { |
||||
|
decodingTest(decoding, testDataEnc); |
||||
|
} |
||||
|
else { |
||||
|
SpeedTest st = new SpeedTest(); |
||||
|
Thread[] dts = new Thread[deth]; |
||||
|
for (int i=0; i < deth; ++i) { |
||||
|
dts[i] = st.new DecodingThread(decoding, testDataEnc); |
||||
|
dts[i].start(); |
||||
|
} |
||||
|
for (int i=0; i < deth; ++i) { |
||||
|
try { |
||||
|
dts[i].join(); |
||||
|
} |
||||
|
catch (InterruptedException ex) { } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
long decodingTime = System.nanoTime(); |
||||
|
long decodingDelta = decodingTime - startTime; |
||||
|
|
||||
|
if (encoding > 0) { |
||||
|
if (enth == 1) { |
||||
|
encodingTest(decoding, testData); |
||||
|
} |
||||
|
else { |
||||
|
SpeedTest st = new SpeedTest(); |
||||
|
Thread[] ets = new Thread[enth]; |
||||
|
for (int i=0; i < enth; ++i) { |
||||
|
ets[i] = st.new EncodingThread(encoding, testData); |
||||
|
ets[i].start(); |
||||
|
} |
||||
|
for (int i=0; i < enth; ++i) { |
||||
|
try { |
||||
|
ets[i].join(); |
||||
|
} |
||||
|
catch (InterruptedException ex) { } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
long encodingDelta = System.nanoTime() - decodingTime; |
||||
|
|
||||
|
double ent = (double) encodingDelta / 1000000000; |
||||
|
double det = (double) decodingDelta / 1000000000; |
||||
|
|
||||
|
double enr = (double) encoding / ent; |
||||
|
double der = (double) decoding / det; |
||||
|
|
||||
|
System.out.println(String.format( |
||||
|
"encrypted and encoded %d messages in %.3f sec. (%.1f msg./sec. per core)\n" |
||||
|
+ "decrypted and decoded %d messages in %.3f sec. (%.1f msg./sec. per core)", |
||||
|
encoding, ent, enr, decoding, det, der)); |
||||
|
} |
||||
|
} |
@ -0,0 +1,58 @@ |
|||||
|
package foundation.pEp.jniadapter.test.speedtest; |
||||
|
|
||||
|
import java.text.ParseException; |
||||
|
import java.util.regex.*; |
||||
|
|
||||
|
public class SWIFTMsg { |
||||
|
// Basic Header Block
|
||||
|
public String applicationIdentifier; |
||||
|
public String serviceIdentifier; |
||||
|
public String logicalTerminalAddress; |
||||
|
public String sessionNumber; |
||||
|
public String sequenceNumber; |
||||
|
|
||||
|
// Application Header Block
|
||||
|
public String inputIdentifier; |
||||
|
public String messageType; |
||||
|
public String destinationAddress; |
||||
|
public String messagePriority; |
||||
|
|
||||
|
/** |
||||
|
* retrieve header fields of Basic Header Block and Application Header |
||||
|
* Block |
||||
|
* |
||||
|
* @param m Matcher object of a regex result |
||||
|
*/ |
||||
|
|
||||
|
public void retrieveHeader(Matcher m) { |
||||
|
// Basic Header Block
|
||||
|
applicationIdentifier = m.group("ai"); |
||||
|
serviceIdentifier = m.group("si"); |
||||
|
logicalTerminalAddress = m.group("lta"); |
||||
|
sessionNumber = m.group("sn"); |
||||
|
sequenceNumber = m.group("sqn"); |
||||
|
|
||||
|
// Application Header Block
|
||||
|
inputIdentifier = m.group("ii"); |
||||
|
messageType = m.group("mt"); |
||||
|
destinationAddress = m.group("da"); |
||||
|
messagePriority = m.group("mp"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* parse MTFIN header and retrieve header fields into variables. |
||||
|
* |
||||
|
* @param txt MTxxx message to parse |
||||
|
* @throws ParseException if not a valid MTxxx message |
||||
|
*/ |
||||
|
|
||||
|
public void parseHeader(String txt) throws ParseException { |
||||
|
// String header = txt.substring(0, 50);
|
||||
|
|
||||
|
Matcher m = MTConstants.mt_pattern.matcher(txt); |
||||
|
if (!m.matches()) |
||||
|
throw new ParseException("not a valid MTxxx message", 0); |
||||
|
|
||||
|
retrieveHeader(m); |
||||
|
} |
||||
|
} |
@ -0,0 +1,10 @@ |
|||||
|
#!/usr/bin/env bash |
||||
|
|
||||
|
ENGINE_LIB_PATH=$HOME/local-default/lib |
||||
|
|
||||
|
export HOME=../resources/per-user-dirs/alice |
||||
|
export LD_LIBRARY_PATH=$ENGINE_LIB_PATH |
||||
|
export DYLD_LIBRARY_PATH=$ENGINE_LIB_PATH |
||||
|
|
||||
|
cd ../../../../../ |
||||
|
java -enableassertions -Xcheck:jni -cp .:../../src -Djava.library.path=.:../../src foundation.pEp.jniadapter.test.speedtest.SpeedTest $@ |
Loading…
Reference in new issue