@ -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,38 @@ | |||
include ../../../../../../../Makefile.conf | |||
include ../Makefile.conf | |||
TEST_UNIT_NAME=speedtest | |||
JAVA_CLASSES = \ | |||
MT999.class \ | |||
MTConstants.class \ | |||
MTMsgCodec.class \ | |||
SpeedTest.class \ | |||
SWIFTMsg.class | |||
.PHONY: pitytest compile alice test clean | |||
all: alice compile | |||
pitytest: | |||
$(MAKE) -C $(PITYTEST_DIR) | |||
alice: compile clean-pep-home-alice | |||
cd $(JAVA_CWD);pwd;HOME=$(JAVA_PEP_HOME_DIR_ALICE) $(JAVA) $(JAVA_PKG_BASENAME).$(TEST_UNIT_NAME).SpeedTest | |||
compile: $(JAVA_CLASSES) pitytest | |||
%.class: %.java | |||
cd $(JAVA_CWD);javac -cp $(CLASSPATH) $(JAVA_PKG_BASEPATH)/$(TEST_UNIT_NAME)/$< | |||
clean: | |||
rm -f $(JAVA_CLASSES) | |||
rm -f *.class | |||
rm -f *.log | |||
rm -Rf .gnupg | |||
rm -Rf .lldb | |||
clean-pep-home: clean-pep-home-alice | |||
clean-pep-home-alice: | |||
rm -rf $(PEP_HOME_DIR_ALICE)/.pEp |
@ -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,295 @@ | |||
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(); | |||
private static long decodingCount = 0; | |||
private static long encodingCount = 0; | |||
private static int deth = 0; | |||
private static int enth = 0; | |||
private static String testData = null; | |||
protected static void decodingTest(Engine eng, 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 = eng.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; | |||
private Engine _localpEp; | |||
public DecodingThread(long n, String testDataEnc) { | |||
_n = n; | |||
_testDataEnc = testDataEnc; | |||
_localpEp = new Engine(); | |||
} | |||
public void run() { | |||
decodingTest(_localpEp, _n, _testDataEnc); | |||
} | |||
} | |||
private static Message encrypt(Engine eng, 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 eng.encrypt_message(m, null, Message.EncFormat.Inline); | |||
} | |||
protected static void encodingTest(Engine eng, long n, String testData) { | |||
for (long i = 0; i < n; ++i) { | |||
Message enc = encrypt(eng, testData); | |||
String txt = codec.encode(enc, null); | |||
} | |||
} | |||
protected class EncodingThread extends Thread { | |||
private long _n; | |||
private String _testData; | |||
private Engine _localpEp; | |||
public EncodingThread(long n, String testData) { | |||
_n = n; | |||
_testData = testData; | |||
_localpEp = new Engine(); | |||
} | |||
public void run() { | |||
encodingTest(_localpEp, _n, _testData); | |||
} | |||
} | |||
private static void parseOpts(String[] args) { | |||
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 per thread\n" | |||
+ " -e, --encode NUMBER encode NUMBER messages per thread\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 { | |||
decodingCount = 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 { | |||
encodingCount = 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); | |||
} | |||
} | |||
} | |||
public static void main(String[] args) { | |||
System.out.println("Initializing..."); | |||
int cores = Runtime.getRuntime().availableProcessors(); | |||
System.out.println(String.format("Number of cores: %d", cores)); | |||
encodingCount = 10; | |||
decodingCount = 10; | |||
enth = cores; | |||
deth = cores; | |||
MT999 testMessage = new MT999("232323232323", "424242424242", "O", "23", "", "Hello, world"); | |||
testData = testMessage.toString(); | |||
parseOpts(args); | |||
if (decodingCount < 0 || encodingCount < 0 || !(encodingCount > 0 || decodingCount > 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(pEp, testData); | |||
String testDataEnc = codec.encode(enc, null); | |||
Thread[] dts = new Thread[deth]; | |||
Thread[] ets = new Thread[enth]; | |||
System.out.println("Creating " + deth + " decoding threads"); | |||
// create threads | |||
if (deth > 1) { | |||
SpeedTest st = new SpeedTest(); | |||
for (int i = 0; i < deth; ++i) { | |||
dts[i] = st.new DecodingThread(decodingCount, testDataEnc); | |||
} | |||
} | |||
System.out.println("Creating " + enth + " encoding threads"); | |||
if (enth > 1) { | |||
SpeedTest st = new SpeedTest(); | |||
for (int i = 0; i < enth; ++i) { | |||
ets[i] = st.new EncodingThread(encodingCount, testData); | |||
} | |||
} | |||
// Benchmark starting | |||
System.out.println("Starting benchmark..."); | |||
System.out.println("decoding " + decodingCount + " msgs per thread"); | |||
long startTime = System.nanoTime(); | |||
if (deth == 1) { | |||
decodingTest(pEp, decodingCount, testDataEnc); | |||
} else { | |||
SpeedTest st = new SpeedTest(); | |||
for (int i = 0; i < deth; ++i) { | |||
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; | |||
System.out.println("encoding " + decodingCount + " msgs per thread"); | |||
if (enth == 1) { | |||
encodingTest(pEp, decodingCount, testData); | |||
} else { | |||
SpeedTest st = new SpeedTest(); | |||
for (int i = 0; i < enth; ++i) { | |||
ets[i].start(); | |||
} | |||
for (int i = 0; i < enth; ++i) { | |||
try { | |||
ets[i].join(); | |||
} catch (InterruptedException ex) { | |||
} | |||
} | |||
} | |||
long encodingDelta = System.nanoTime() - decodingTime; | |||
double encTimeSecs = (double) encodingDelta / 1000000000; | |||
double decTimeSecs = (double) decodingDelta / 1000000000; | |||
long encTotal = encodingCount * enth; | |||
long decTotal = decodingCount * deth; | |||
double enr = (double) encTotal / encTimeSecs; | |||
double der = (double) decTotal / decTimeSecs; | |||
System.out.println(String.format( | |||
"encrypted and encoded %d messages in %.3f sec. (%.1f msgs/sec) using %d threads\n" | |||
+ "decrypted and decoded %d messages in %.3f sec. (%.1f msgs/sec) using %d threads", | |||
encTotal, encTimeSecs, enr, enth, decTotal, decTimeSecs, der, deth)); | |||
} | |||
} |
@ -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 $@ |