Commit 0f4dbd80 authored by johan's avatar johan
Browse files

JNI improve testing

parent 940b74b2
......@@ -23,6 +23,7 @@ find_package(Java REQUIRED)
include(UseJava)
set (LIMETESTER_SOURCE_FILES_JAVA
org/linphone/limeTester/HelloWorld.java
org/linphone/limeTester/LimeTester.java
)
......
package org.linphone.limeTester;
import org.linphone.lime.*;
import javax.net.ssl.*;
import java.util.UUID;
import java.security.cert.X509Certificate;
import java.io.File;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
import java.io.DataOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.concurrent.*;
/**
* @brief For testing purpose
* Simulate a network transmission, is actually just two static buffer used to
* store the DRmessage and cipherMessage generated by the encrypt function
* This message can be retrieved using the dedicated get methods.
*/
class mailBox {
private static byte[] m_DRMessage = null;
private static byte[] m_cipherMessage = null;
public static void post(byte[] DRMessage, byte[] cipherMessage) {
m_DRMessage = DRMessage;
m_cipherMessage = cipherMessage;
};
public static byte[] getCipherMessage() {
return m_cipherMessage;
};
public static byte[] getDRMessage() {
return m_DRMessage;
};
}
/**
* @brief For testing purpose
* This callback store reference to the input/ouput buffers recipients and cipherMessage
* passed to the encrypt function.
* The callback function is called when encryption is completed. It then post the
*/
class LimeStatusCallbackImpl_Mailbox implements LimeStatusCallback {
public int success;
public int fail;
public int timeout;
public RecipientData[] recipients;
public LimeOutputBuffer cipherMessage;
/**
* @brief Function called by native code when asynchronous processing is completed
*
* @param[in] status an integer mapped lime:CallbackReturn, use LimeCallbackReturn.fromNative to turn it into a java enumeration
* @param[in] message a string message giving some details in case of failure
*/
public void callback(int cstatus, String message) {
LimeCallbackReturn status = LimeCallbackReturn.fromNative(cstatus);
if (status == LimeCallbackReturn.SUCCESS) {
// Extract messages if there is one and post it
// In this test we know we have only one recipient, otherwise access the recipient device id in the recipients[].deviceId field
if (recipients != null) {
mailBox.post(recipients[0].DRmessage, cipherMessage.buffer);
recipients = null;
cipherMessage = null;
}
success++;
} else { // status is LimeCallbackReturn.FAIL
fail++;
}
}
public void resetStatus() {
success = 0;
fail = 0;
timeout = 4000;
recipients = null;
}
public LimeStatusCallbackImpl_Mailbox() {
resetStatus();
}
public boolean wait_for_success(int expected_success) {
try {
int time = 0;
while (time<timeout && success<expected_success) {
time += 25;
Thread.sleep(25);
}
}
catch (InterruptedException e) {
System.out.println("Interrupt exception in wait for success");
return false;
}
if (expected_success == success) {
return true;
} else {
return false;
}
}
}
public class HelloWorld {
/**
* Test Scenario:
* - Create Alice and Bob users
* - Alice encrypts to Bob who decrypts and check it matches the original
* - Bob encryps to Alice who decrypts and check it matches the original
* - Alice and Bob call the update function
*/
public static void hello_world(LimeCurveId curveId, String dbBasename, String x3dhServerUrl, LimePostToX3DH postObj) {
int expected_success = 0;
int expected_fail = 0;
// Create a callback, this one will be used for all operations
// which does not produce encrypted output(all except encryptions)
LimeStatusCallbackImpl statusCallback = new LimeStatusCallbackImpl();
// Create random device id for alice and bob
String AliceDeviceId = "alice"+UUID.randomUUID().toString();
String BobDeviceId = "bob"+UUID.randomUUID().toString();
// Create db filenames and delete potential existing ones
String curveIdString;
if (curveId == LimeCurveId.C25519) {
curveIdString = ".C25519";
} else {
curveIdString = ".C448";
}
String aliceDbFilename = "alice."+dbBasename+curveIdString+".sqlite3";
String bobDbFilename = "bob."+dbBasename+curveIdString+".sqlite3";
File file = new File(aliceDbFilename);
file.delete();
file = new File(bobDbFilename);
file.delete();
// Create bob and alice lime managers
// Any application using Lime shall instantiate one LimeManager only, even in case of multiple users managed by the application.
// Here we use two managers to simulate two separated devices (one manager is associated to one database
LimeManager aliceManager = new LimeManager(aliceDbFilename, postObj);
LimeManager bobManager = new LimeManager(bobDbFilename, postObj);
// create users, this operation is asynchronous (as the user is also created on X3DH server)
// The OPkInitialBatchSize parameter is optionnal and is used to set how many One-Time pre-keys will be
// uploaded to the X3DH server at creation. Default value is set in lime_jni.cpp to 100.
// Last parameter is a callback object implementing the LimeStatusCallback interface
// At user creation we would only need to retrieve the status and message(in case of failure only)
// - In case of successful operation the return code is an int translating to LimeCallbackReturn.SUCCESS, and message is empty
// - In case of failure, the return code is an int translating to LimeCallbackReturn.FAIL and the message shall give details on the failure cause
aliceManager.create_user(AliceDeviceId, x3dhServerUrl, curveId, statusCallback);
bobManager.create_user(BobDeviceId, x3dhServerUrl, curveId, 10, statusCallback);
// wait for the operations to complete
expected_success+= 2;
assert (statusCallback.wait_for_success(expected_success));
assert (statusCallback.fail == expected_fail);
/************** SENDER SIDE CODE *****************************/
/*** alice encrypt a message to bob, all parameters given to encrypt function are shared_ptr. ***/
// The encryption generates:
// - one common cipher message which must be sent to all recipient devices(depends on encryption policy, message length and recipient number, it may be actually empty)
// - a cipher header per recipient device, each recipient device shall receive its specific one
// Create a RecipientData array, in this basic case we will encrypt to one device only but we can do it to any number of recipient devices.
// RecipientData holds:
// - recipient device id (identify the recipient)
// - peer Device status :
// - input: if set to LimePeerDeviceStatus.FAIL : this entry will be ignored and no message would be encrypted for this device
// - ouput: the current status of this device in local database. See LimePeerDeviceStatus definition for details
// - Double Ratchet message : output of encryption process targeted to this recipient device only.
RecipientData[] recipients = new RecipientData[1]; // 1 target for the encrypt
String plainMessage = "Moi je connais un ami il s'appelle Alceste"; // here we define the plain text as a string but the input is a byte array so we can encrypt anything
recipients[0] = new RecipientData(BobDeviceId);
// Shall we have more recipients (bob can have several devices or be a conference sip:uri, alice other devices must get a copy of the message), we just need to create some more RecipientData with their respective Device Id (GRUU) and set them in the recipients array
// This is a buffer to store the cipherMessage which shall be distributed to all recipient in addition to their specific message
// A reference is passed to the jni and another one stored in the callback object
LimeOutputBuffer cipherMessage = new LimeOutputBuffer();
// Instanciate a mailbox callback to manage the encryption output
// It act the same than the regular callback and provide a wait_for function to be able to synchronise
// the program flow ( for the purpose of this test only, not in a real situation)
LimeStatusCallbackImpl_Mailbox alice_encryptCallback = new LimeStatusCallbackImpl_Mailbox();
// the mailbox callback store references to the output buffer so it would be able to access the encryption output
// this would be required in real situation
alice_encryptCallback.recipients = recipients;
alice_encryptCallback.cipherMessage = cipherMessage;
// encrypts, the plain message is a byte array so we can encrypt anything
// this call is asynchronous (unless you use an synchronous connection to the X3DH server)
// and the statusCallback.callback function will be called when the encryption is done
// encrypt, parameters are:
// - localDeviceId to select which of the users managed by the LimeManager we shall use to perform the encryption (in our example we have only one local device).
// - recipientUser: an id of the recipient user (which can hold several devices), typically its sip:uri
// - RecipientData vector (see above), list all recipient devices, will hold their DR message
// - plain message
// - cipher message (this one must then be distributed to all recipients devices but may be empty, see EncryptionPolicy for details)
// - a status callback object implenting the LimeStatusCallback interface
aliceManager.encrypt(AliceDeviceId, "bob", recipients, plainMessage.getBytes(), cipherMessage, alice_encryptCallback);
// in real sending situation, the local references of the recipients and cipherMessage are destroyed by exiting the function where they've been declared
// and where we called the encrypt function. (The LimeManager shall instead never be destroyed until the application terminates)
// They would be accessed directly via their reference stored in the callback object
recipients = null;
cipherMessage = null;
/************** SYNCHRO **************************************/
// Here we wait for the status callback
assert (alice_encryptCallback.wait_for_success(1)); // this is a new status callback so we expect 1 success
assert (alice_encryptCallback.fail == 0); // to check if we has a fail or a real timeout in case of failure
alice_encryptCallback = null; // we do not need this object anymore
/************** End of SYNCHRO *******************************/
/************** RECIPIENT SIDE CODE **************************/
// Get recipients and cipher message from the mailbox (simulate sending over a network))
byte[] receivedDRmessage = mailBox.getDRMessage();
byte[] receivedCipherMessage = mailBox.getCipherMessage();
// using the default encryption policy and encrypting to on recipient, we shall not have any cipherMessage
assert (receivedCipherMessage.length == 0):"Default encryption policy implies no cipherMessage for one recipient only";
LimeOutputBuffer decodedMessage = new LimeOutputBuffer();
// decrypt and check we get back to the original
// decryption is synchronous, no callback on this one
// We do not have any cipher message, so use the decrypt method version without it
// decrypt, parameters are:
// - localDeviceId to select which of the users managed by the LimeManager we shall use to perform the encryption (in our example we have only one local device).
// - recipientUser: an id of the recipient user (which can hold several devices), typically its sip:uri
// - senderDeviceId: identify the device who sent the message
// - Recipient DR message
// - cipher message (this one must then be distributed to all recipients devices but may be empty, see EncryptionPolicy for details)
// - output: the plain message
//
// decrypt return the current status of sending peer in the local database or LimePeerDeviceStatus.FAIL in case of decryption failure
LimePeerDeviceStatus status = bobManager.decrypt(BobDeviceId, "bob", AliceDeviceId, receivedDRmessage, decodedMessage);
String s = new String(decodedMessage.buffer);
assert (status == LimePeerDeviceStatus.UNKNOWN):"decrypt status was expected to be unknown but is not";
assert (s.equals(plainMessage)):"Decoded message is not the encoded one";
/************** End of RECIPIENT SIDE CODE *******************/
// clean db: delete users
expected_success+= 2;
aliceManager.delete_user(AliceDeviceId, statusCallback);
bobManager.delete_user(BobDeviceId, statusCallback);
assert (statusCallback.wait_for_success(expected_success));
assert (statusCallback.fail == expected_fail);
aliceManager = null;
bobManager = null;
}
}
......@@ -196,117 +196,6 @@ class LimePostToX3DH_Async implements LimePostToX3DH {
}
public class LimeTester {
/**
* Test Scenario:
* - Create Alice and Bob users
* - Alice encrypts to Bob who decrypts and check it matches the original
* - Bob encryps to Alice who decrypts and check it matches the original
* - Alice and Bob call the update function
*/
public static void hello_world(LimeCurveId curveId, String dbBasename, String x3dhServerUrl, LimePostToX3DH postObj) {
int expected_success = 0;
int expected_fail = 0;
// Create a callback
LimeStatusCallbackImpl statusCallback = new LimeStatusCallbackImpl();
// Create random device id for alice and bob
String AliceDeviceId = "alice"+UUID.randomUUID().toString();
String BobDeviceId = "bob"+UUID.randomUUID().toString();
// Create db filenames and delete potential existing ones
String curveIdString;
if (curveId == LimeCurveId.C25519) {
curveIdString = ".C25519.";
} else {
curveIdString = ".C448.";
}
String aliceDbFilename = "alice."+dbBasename+curveIdString+".sqlite3";
String bobDbFilename = "bob."+dbBasename+curveIdString+".sqlite3";
File file = new File(aliceDbFilename);
file.delete();
file = new File(bobDbFilename);
file.delete();
// Create bob and alice lime managers
LimeManager aliceManager = new LimeManager(aliceDbFilename, postObj);
LimeManager bobManager = new LimeManager(bobDbFilename, postObj);
// Use this managers to create users
expected_success+= 2;
aliceManager.create_user(AliceDeviceId, x3dhServerUrl, curveId, statusCallback);
bobManager.create_user(BobDeviceId, x3dhServerUrl, curveId, 10, statusCallback);
assert (statusCallback.wait_for_success(expected_success));
assert (statusCallback.fail == expected_fail);
// prepare for encrypt: An array of RecipientData, one element for each recipient device
// encrypted output will be stored in it
RecipientData[] recipients = new RecipientData[1]; // 1 target for the encrypt
String plainMessage = "Moi je connais un ami il s'appelle Alceste";
recipients[0] = new RecipientData(BobDeviceId);
LimeOutputBuffer cipherMessage = new LimeOutputBuffer();
// encrypts, the plain message is a byte array so we can encrypt anything
// this call is asynchronous (unless you use an synchronous connection to the X3DH server)
// and the statusCallback.callback function will be called when the encryption is done
expected_success+= 1;
aliceManager.encrypt(AliceDeviceId, "bob", recipients, plainMessage.getBytes(), cipherMessage, statusCallback);
// here we wait for the status callback to be called but a full asynchronous process would
// store references to recipients and cipherMessage in the statusCallback object and process
// them from the callback method.
assert (statusCallback.wait_for_success(expected_success));
assert (recipients[0].getPeerStatus() == LimePeerDeviceStatus.UNKNOWN):"Encryption status for Bob is expected to be UNKNOWN"; // Bob is unknown in Alice's base
assert (cipherMessage.buffer.length==0):"Default encryption policy implies no cipherMessage for one recipient only";
assert (statusCallback.fail == expected_fail);
LimeOutputBuffer decodedMessage = new LimeOutputBuffer();
// decrypt and check we get back to the original
// decryption is synchronous, no callback on this one
LimePeerDeviceStatus status = bobManager.decrypt(BobDeviceId, "bob", AliceDeviceId, recipients[0].DRmessage, decodedMessage);
String s = new String(decodedMessage.buffer);
assert (s.equals(plainMessage)):"Decoded message is not the encoded one";
assert (status == LimePeerDeviceStatus.UNKNOWN):"decrypt status was expected to be unknown but is not";
// encrypt another message
recipients = new RecipientData[1]; // 1 target for the encrypt
plainMessage = "Nous, on l'appelle Zantrop c'est note ami Zantrop";
recipients[0] = new RecipientData(AliceDeviceId);
cipherMessage = new LimeOutputBuffer();
expected_success+= 1;
bobManager.encrypt(BobDeviceId, "alice", recipients, plainMessage.getBytes(), cipherMessage, statusCallback, LimeEncryptionPolicy.CIPHERMESSAGE);
assert (statusCallback.wait_for_success(expected_success));
assert (recipients[0].getPeerStatus() == LimePeerDeviceStatus.UNTRUSTED):"Encryption status for Alice is expected to be UNTRUSTED"; // Alice status is untrusted in Bob's base
assert (cipherMessage.buffer.length>0):"Forced cipherMessage encryption policy must produce a cipherMessage even for one recipient only";
assert (statusCallback.fail == expected_fail);
decodedMessage = new LimeOutputBuffer();
// decrypt and check we get back to the original
status = aliceManager.decrypt(AliceDeviceId, "alice", BobDeviceId, recipients[0].DRmessage, cipherMessage.buffer, decodedMessage);
s = new String(decodedMessage.buffer);
assert (s.equals(plainMessage)):"Decoded message is not the encoded one";
assert (status == LimePeerDeviceStatus.UNTRUSTED):"decrypt status was expected to be untrusted but is not";
expected_success+= 2;
aliceManager.update(statusCallback);
bobManager.update(statusCallback);
assert (statusCallback.wait_for_success(expected_success));
assert (statusCallback.fail == expected_fail);
// clean db
expected_success+= 2;
aliceManager.delete_user(AliceDeviceId, statusCallback);
bobManager.delete_user(BobDeviceId, statusCallback);
assert (statusCallback.wait_for_success(expected_success));
assert (statusCallback.fail == expected_fail);
// Force garbage collection to be sure the native object destructor is called
aliceManager = null;
bobManager = null;
System.gc();
}
public static void main(String[] args) {
// Load lime library
try {
......@@ -344,12 +233,12 @@ public class LimeTester {
* Run it synchronous and asynchronous on both curves.
*/
LimePostToX3DH_Sync sync_postObj = new LimePostToX3DH_Sync();
hello_world(LimeCurveId.C25519, "hello_world", "https://localhost:25519", sync_postObj);
hello_world(LimeCurveId.C448, "hello_world", "https://localhost:25520", sync_postObj);
HelloWorld.hello_world(LimeCurveId.C25519, "hello_world", "https://localhost:25519", sync_postObj);
HelloWorld.hello_world(LimeCurveId.C448, "hello_world", "https://localhost:25520", sync_postObj);
LimePostToX3DH_Async async_postObj = new LimePostToX3DH_Async();
hello_world(LimeCurveId.C25519, "hello_world", "https://localhost:25519", async_postObj);
hello_world(LimeCurveId.C448, "hello_world", "https://localhost:25520", async_postObj);
HelloWorld.hello_world(LimeCurveId.C25519, "hello_world", "https://localhost:25519", async_postObj);
HelloWorld.hello_world(LimeCurveId.C448, "hello_world", "https://localhost:25520", async_postObj);
System.exit(0);
}
......
......@@ -238,8 +238,10 @@ static void helloworld_basic_test(const lime::CurveId curve, const std::string &
// Create an empty RecipientData vector, in this basic case we will encrypt to one device only but we can do it to any number of recipient devices.
// RecipientData holds:
// - recipient device id (identify the recipient)
// - peer Device identity verified status : output, set to true if this device is a verified one.
// - DRmessage : output of encryption process targeted to this recipient device only
// - peer Device status :
// - input : if explicitely set to lime::PeerDeviceStatus::fail, this entry is ignored
// - output : the current status of this device in local database. See lime::PeerDeviceStatus definition(in lime.hpp) for details
// - Double Ratchet message : output of encryption process targeted to this recipient device only
auto recipients = make_shared<std::vector<RecipientData>>();
recipients->emplace_back(*bobDeviceId); // we have only one recipient identified by its device id.
// Shall we have more recipients (bob can have several devices or be a conference sip:uri, alice other devices must get a copy of the message), we just need to emplace_back some more recipients Device Id (GRUU)
......@@ -472,8 +474,10 @@ static void helloworld_verifyIdentity_test(const lime::CurveId curve, const std:
// Create an empty RecipientData vector, in this basic case we will encrypt to one device only but we can do it to any number of recipient devices.
// RecipientData holds:
// - recipient device id (identify the recipient)
// - peer Device identity verified status : output, set to true if this device is a verified one.
// - DRmessage : output of encryption process targeted to this recipient device only
// - peer Device status :
// - input : if explicitely set to lime::PeerDeviceStatus::fail, this entry is ignored
// - output : the current status of this device in local database. See lime::PeerDeviceStatus definition(in lime.hpp) for details
// - Double Ratchet message : output of encryption process targeted to this recipient device only
auto recipients = make_shared<std::vector<RecipientData>>();
recipients->emplace_back(*bobDeviceId); // we have only one recipient identified by its device id.
// Shall we have more recipients (bob can have several devices or be a conference sip:uri, alice other devices must get a copy of the message), we just need to emplace_back some more recipients Device Id (GRUU)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment