Commit 00df6b89 authored by johan's avatar johan
Browse files

JNI add exception management

+ port more lime test to java
+ select tests run according to curves selected at build
parent f9c08bd8
......@@ -203,7 +203,7 @@ namespace lime {
* It is advised to capture a copy of cipherMessage and recipients shared_ptr in this callback so they can access
* the output of encryption as it won't be part of the callback parameters.
* @param[in] encryptionPolicy select how to manage the encryption: direct use of Double Ratchet message or encrypt in the cipher message and use the DR message to share the cipher message key
* default is optimized output size mode.
* default is optimized upload size mode.
*/
void encrypt(const std::string &localDeviceId, std::shared_ptr<const std::string> recipientUserId, std::shared_ptr<std::vector<RecipientData>> recipients, std::shared_ptr<const std::vector<uint8_t>> plainMessage, std::shared_ptr<std::vector<uint8_t>> cipherMessage, const limeCallback &callback, lime::EncryptionPolicy encryptionPolicy=lime::EncryptionPolicy::optimizeUploadSize);
......
......@@ -23,6 +23,7 @@ find_package(Java REQUIRED)
include(UseJava)
set (LIME_SOURCE_FILES_JAVA
org/linphone/lime/LimeException.java
org/linphone/lime/LimeCurveId.java
org/linphone/lime/LimeEncryptionPolicy.java
org/linphone/lime/LimeOutputBuffer.java
......
/*
LimeException.java
@author Johan Pascal
@copyright Copyright (C) 2019 Belledonne Communications SARL
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.linphone.lime;
public class LimeException extends Exception {
public LimeException(String message) {
super(message);
}
}
......@@ -18,6 +18,13 @@
*/
package org.linphone.lime;
/**
* @brief A java wrapper around the native Lime Manager interface
*
* To use this wrapper you must implement the interfaces
* - LimePostToX3DH to communicate with the X3DH Https server
* - LimeStatusCallback to manage the lime response to asynchronous operations: create/delete users, encrypt, update
*/
public class LimeManager {
private long nativePtr; // stores the native object pointer
......@@ -42,7 +49,7 @@ public class LimeManager {
* @brief Lime Manager constructor
*
* @param[in] db_access string used to access DB: can be filename for sqlite3 or access params for mysql, directly forwarded to SOCI session opening
* @param[in] X3DH_post_data A function to send data to the X3DH server, parameters includes a callback to transfer back the server response
* @param[in] postObj An object used to send data to the X3DH server
*/
public LimeManager(String db_access, LimePostToX3DH postObj) {
initialize(db_access, postObj);
......@@ -63,7 +70,7 @@ public class LimeManager {
* A user is published on an X3DH key server who must run using the same elliptic curve selected for this user (creation will fail otherwise), the server url cannot be changed later
*
* @param[in] localDeviceId Identify the local user acount to use, it must be unique and is also be used as Id on the X3DH key server, it shall be the GRUU
* @param[in] serverUrl The complete url(including port) of the X3DH key server. It must connect using HTTPS. Example: https://sip5.linphone.org:25519
* @param[in] serverURL The complete url(including port) of the X3DH key server. It must connect using HTTPS. Example: https://sip5.linphone.org:25519
* @param[in] curveId Choice of elliptic curve to use as base for ECDH and EdDSA operation involved. Can be CurveId::c25519 or CurveId::c448.
* @param[in] OPkInitialBatchSize Number of OPks in the first batch uploaded to X3DH server
* @param[in] statusObj This operation contact the X3DH server and is thus asynchronous, when server responds,
......@@ -71,14 +78,14 @@ public class LimeManager {
* @note
* The OPkInitialBatchSize is optionnal, if not used, set to defaults 100
*/
public void create_user(String localDeviceId, String serverURL, LimeCurveId curveId, int OPkInitialBatchSize, LimeStatusCallback statusObj) {
public void create_user(String localDeviceId, String serverURL, LimeCurveId curveId, int OPkInitialBatchSize, LimeStatusCallback statusObj) throws LimeException {
this.n_create_user(localDeviceId, serverURL, curveId.getNative(), OPkInitialBatchSize, statusObj);
}
/**
* @overload void create_user(String localDeviceId, String serverURL, LimeCurveId curveId, LimeStatusCallback statusObj)
*/
public void create_user(String localDeviceId, String serverURL, LimeCurveId curveId, LimeStatusCallback statusObj) {
this.n_create_user(localDeviceId, serverURL, curveId.getNative(), 100, statusObj);
public void create_user(String localDeviceId, String serverURL, LimeCurveId curveId, LimeStatusCallback statusObj) throws LimeException {
this.n_create_user(localDeviceId, serverURL, curveId.getNative(), 100, statusObj);
}
/**
......@@ -90,7 +97,7 @@ public class LimeManager {
* @param[in] statusObj This operation contact the X3DH server and is thus asynchronous, when server responds,
* the statusObj.callback will be called giving the exit status and an error message in case of failure
*/
public native void delete_user(String localDeviceId, LimeStatusCallback statusObj);
public native void delete_user(String localDeviceId, LimeStatusCallback statusObj) throws LimeException;
/**
* @brief Encrypt a buffer (text or file) for a given list of recipient devices
......@@ -122,12 +129,12 @@ public class LimeManager {
* @param[in,out] recipients a list of RecipientData holding:
* - the recipient device Id(GRUU)
* - an empty buffer to store the DRmessage which must then be routed to that recipient
* - the peer Status. If peerStatus is set to fail, this entry is ignored otherwise the peerStatus is set by the encrypt, see ::PeerDeviceStatus definition for details
* - the peer Status. If peerStatus is set to fail, this entry is ignored otherwise the peerStatus is set by the encrypt, see LimePeerDeviceStatus definition for details
* @param[in] plainMessage a buffer holding the message to encrypt, can be text or data.
* @param[out] cipherMessage points to the buffer to store the encrypted message which must be routed to all recipients(if one is produced, depends on encryption policy)
* @param[in] callback Performing encryption may involve the X3DH server and is thus asynchronous, when the operation is completed,
* this callback will be called giving the exit status and an error message in case of failure.
* It is advised to capture a copy of cipherMessage and recipients shared_ptr in this callback so they can access
* @param[in] statusObj Performing encryption may involve the X3DH server and is thus asynchronous, when the operation is completed,
* this statusObj.callback will be called giving the exit status and an error message in case of failure.
* It is advised to store a reference to cipherMessage and recipients in this object so they can access
* the output of encryption as it won't be part of the callback parameters.
* @param[in] encryptionPolicy select how to manage the encryption: direct use of Double Ratchet message or encrypt in the cipher message and use the DR message to share the cipher message key
* default is optimized output size mode.
......@@ -182,8 +189,8 @@ public class LimeManager {
*
* Is performed for all users founds in local storage
*
* @param[in] callback This operation may contact the X3DH server and is thus asynchronous, when server responds,
* this callback will be called giving the exit status and an error message in case of failure.
* @param[in] statusObj This operation may contact the X3DH server and is thus asynchronous, when server responds,
* this statusObj.callback will be called giving the exit status and an error message in case of failure.
* @param[in] OPkServerLowLimit If server holds less OPk than this limit, generate and upload a batch of OPks
* @param[in] OPkBatchSize Number of OPks in a batch uploaded to server
*
......@@ -210,7 +217,7 @@ public class LimeManager {
* @param[in] localDeviceId used to identify which local account we're dealing with, shall be the GRUU
* @param[out] Ik the EdDSA public identity key, formatted as in RFC8032
*/
public native void get_selfIdentityKey(String localDeviceId, LimeOutputBuffer Ik);
public native void get_selfIdentityKey(String localDeviceId, LimeOutputBuffer Ik) throws LimeException;
/**
* @brief set the peer device status flag in local storage: unsafe, trusted or untrusted.
......@@ -240,7 +247,7 @@ public class LimeManager {
* - ignore Ik
* - insert/update the status. If inserted, insert an invalid Ik
*/
public void set_peerDeviceStatus(String peerDeviceId, byte[] Ik, LimePeerDeviceStatus status) {
public void set_peerDeviceStatus(String peerDeviceId, byte[] Ik, LimePeerDeviceStatus status) throws LimeException{
this.n_set_peerDeviceStatus_Ik(peerDeviceId, Ik, status.getNative());
}
......@@ -273,6 +280,15 @@ public class LimeManager {
return LimePeerDeviceStatus.fromNative(this.n_get_peerDeviceStatus(peerDeviceId));
}
/**
* @brief delete a peerDevice from local storage
*
* @param[in] peerDeviceId The device Id to be removed from local storage, shall be its GRUU
*
* Call is silently ignored if the device is not found in local storage
*/
public native void delete_peerDevice(String peerDeviceId);
/**
* @brief native function to process the X3DH server response
*
......
......@@ -29,7 +29,7 @@ public interface LimePostToX3DH {
* @param[in] from shall be included in the from field of HTTPS packet sent to the server(holds the local device Id of message sender)
* @param[in] message the binary content of the message to be sent
*
* @Note: To forward the server's response, call the LimeManager.process_response static method
* @note: To forward the server's response, call the LimeManager.process_response static method giving back the native object pointer
*/
public void postToX3DHServer(long ptr, String url, String from, byte[] message);
}
......@@ -20,13 +20,13 @@ package org.linphone.lime;
/** @brief Define an interface for the status callback
* The native code will call this callback function on
* the object passed as parameter to it.
* the LimeStatusCallback object passed as parameter to it.
*/
public interface LimeStatusCallback {
/**
* @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] status an integer mapped lime:CallbackReturn, use LimeCallbackReturn.fromNative to turn it into a java enumeration, do not use it directly
* @param[in] message a string message giving some details in case of failure
*/
public void callback(int status, String message);
......
......@@ -22,6 +22,7 @@
#include "lime_log.hpp"
#include <lime/lime.hpp>
#include "bctoolbox/exception.hh"
/**
* @brief Holds a stateful function pointer to be called to process the X3DH server response
......@@ -101,7 +102,7 @@ static jni::jint c2jPeerDeviceStatus(lime::PeerDeviceStatus peerStatus) {
* mapping in java is :
* SUCCESS(0) FAIL(1)
*
* @param[in] peerStatus the c++ enumerated peer Device Status
* @param[in] status the c++ enumerated peer Device Status
* @return the java enumerated peer device status (silently default to 1:FAIL)
*/
static jni::jint c2jCallbackReturn(lime::CallbackReturn status) {
......@@ -121,7 +122,7 @@ static jni::jint c2jCallbackReturn(lime::CallbackReturn status) {
* DRMESSAGE(0) CIPHERMESSAGE(1) OPTIMIZEUPLOADSIZE(2) OPTIMIZEGLOBALBANDWIDTH(3)
*
* @param[in] encryptionPolicy The java mapped integer to an encryption policy enum
* @return the c++ enumerated encryption policy (silently default to optimizeGlobalBandwidth)
* @return the c++ enumerated encryption policy (silently default to optimizeUploadSize)
*/
static lime::EncryptionPolicy j2cEncryptionPolicy(jni::jint encryptionPolicy) {
switch (encryptionPolicy) {
......@@ -130,10 +131,10 @@ static lime::EncryptionPolicy j2cEncryptionPolicy(jni::jint encryptionPolicy) {
case 1:
return lime::EncryptionPolicy::cipherMessage;
case 3:
return lime::EncryptionPolicy::optimizeUploadSize;
return lime::EncryptionPolicy::optimizeGlobalBandwidth;
case 2:
default:
return lime::EncryptionPolicy::optimizeGlobalBandwidth;
return lime::EncryptionPolicy::optimizeUploadSize;
}
}
......@@ -164,6 +165,7 @@ struct jPostToX3DH { static constexpr auto Name() { return "org/linphone/lime/Li
struct jStatusCallback { static constexpr auto Name() { return "org/linphone/lime/LimeStatusCallback"; } };
struct jRecipientData { static constexpr auto Name() { return "org/linphone/lime/RecipientData"; } };
struct jLimeOutputBuffer { static constexpr auto Name() { return "org/linphone/lime/LimeOutputBuffer"; } };
struct jLimeException { static constexpr auto Name() { return "org/linphone/lime/LimeException"; } };
jni::JNIEnv& env { jni::GetEnv(*vm) };
......@@ -174,6 +176,11 @@ struct jLimeManager {
std::unique_ptr<lime::LimeManager> m_manager; /**< a unique pointer to the actual lime manager */
jni::Global<jni::Object<jPostToX3DH>, jni::EnvGettingDeleter> jGlobalPostObj; /**< a global reference to the java postToX3DH object. TODO: unclear if EnvIgnoringDeleter is not a better fit here. */
inline void ThrowJavaLimeException(JNIEnv& env, const std::string &message) {
auto LimeExceptionClass = env.FindClass("org/linphone/lime/LimeException");
env.ThrowNew(LimeExceptionClass, message.data());
}
/** @brief Constructor
* unlike the native lime manager constructor, this one get only one argument has cpp closure cannot be passed easily to java
* @param[in] db_access the database access path
......@@ -231,9 +238,15 @@ struct jLimeManager {
jstatusObjRef->Call(g_env, StatusMethod, c2jCallbackReturn(status), jni::Make<jni::String>(g_env, message));
};
m_manager->create_user( jni::Make<std::string>(env, localDeviceId),
jni::Make<std::string>(env, serverUrl),
j2cCurveId(jcurveId), jOPkInitialBatchSize, callback_lambda);
try {
m_manager->create_user( jni::Make<std::string>(env, localDeviceId),
jni::Make<std::string>(env, serverUrl),
j2cCurveId(jcurveId), jOPkInitialBatchSize, callback_lambda);
} catch (BctbxException const &e) {
ThrowJavaLimeException(env, e.str());
} catch (std::exception const &e) { // catch anything
ThrowJavaLimeException(env, e.what());
}
}
void delete_user(jni::JNIEnv &env, const jni::String &localDeviceId, jni::Object<jStatusCallback> &jstatusObj ) {
......@@ -244,15 +257,22 @@ struct jLimeManager {
// see create_user for details on this
auto jstatusObjRef = std::make_shared<jni::Global<jni::Object<jStatusCallback>, jni::EnvGettingDeleter>>(jni::NewGlobal<jni::EnvGettingDeleter>(env, jstatusObj));
m_manager->delete_user( jni::Make<std::string>(env, localDeviceId),
[c_vm, jstatusObjRef](const lime::CallbackReturn status, const std::string message){
auto callback_lambda = [c_vm, jstatusObjRef](const lime::CallbackReturn status, const std::string message){
// get the env from VM, and retrieve the callback method on StatusCallback class
jni::JNIEnv& g_env { jni::GetEnv(*c_vm)};
auto StatusClass = jni::Class<jStatusCallback>::Find(g_env);
auto StatusMethod = StatusClass.GetMethod<void (jni::jint, jni::String)>(g_env, "callback");
// call the callback on the statusObj we got in param when calling create_user
jstatusObjRef->Call(g_env, StatusMethod, c2jCallbackReturn(status), jni::Make<jni::String>(g_env, message));
});
};
try {
m_manager->delete_user( jni::Make<std::string>(env, localDeviceId), callback_lambda);
} catch (BctbxException const &e) {
ThrowJavaLimeException(env, e.str());
} catch (std::exception const &e) { // catch anything
ThrowJavaLimeException(env, e.what());
}
}
void encrypt(jni::JNIEnv &env, const jni::String &jlocalDeviceId, const jni::String &jrecipientUserId, jni::Array<jni::Object<jRecipientData>> &jrecipients,
......@@ -394,23 +414,35 @@ struct jLimeManager {
}
void get_selfIdentityKey(jni::JNIEnv &env, const jni::String &jlocalDeviceId, jni::Object<jLimeOutputBuffer> &jIk) {
// retrieve the LimeOutputBuffer class
auto LimeOutputBufferClass = jni::Class<jLimeOutputBuffer>::Find(env);
auto LimeOutputBufferField = LimeOutputBufferClass.GetField<jni::Array<jni::jbyte>>(env, "buffer");
// get the Ik
std::vector<uint8_t> Ik{};
m_manager->get_selfIdentityKey(jni::Make<std::string>(env, jlocalDeviceId), Ik);
auto jIkArray = jni::Make<jni::Array<jni::jbyte>>(env, reinterpret_cast<const std::vector<int8_t>&>(Ik));
jIk.Set(env, LimeOutputBufferField, jIkArray);
try {
// retrieve the LimeOutputBuffer class
auto LimeOutputBufferClass = jni::Class<jLimeOutputBuffer>::Find(env);
auto LimeOutputBufferField = LimeOutputBufferClass.GetField<jni::Array<jni::jbyte>>(env, "buffer");
// get the Ik
std::vector<uint8_t> Ik{};
m_manager->get_selfIdentityKey(jni::Make<std::string>(env, jlocalDeviceId), Ik);
auto jIkArray = jni::Make<jni::Array<jni::jbyte>>(env, reinterpret_cast<const std::vector<int8_t>&>(Ik));
jIk.Set(env, LimeOutputBufferField, jIkArray);
} catch (BctbxException const &e) {
ThrowJavaLimeException(env, e.str());
} catch (std::exception const &e) { // catch anything
ThrowJavaLimeException(env, e.what());
}
}
void set_peerDeviceStatus_Ik(jni::JNIEnv &env, const jni::String &jpeerDeviceId, const jni::Array<jni::jbyte> &jIk, const jni::jint jstatus) {
// turn the Ik byte array into a vector of uint8_t
auto Ik = std::make_shared<std::vector<uint8_t>>();
jbyteArray2uin8_tVector(env, jIk, Ik);
m_manager->set_peerDeviceStatus(jni::Make<std::string>(env, jpeerDeviceId), *Ik, j2cPeerDeviceStatus(jstatus));
try {
// turn the Ik byte array into a vector of uint8_t
auto Ik = std::make_shared<std::vector<uint8_t>>();
jbyteArray2uin8_tVector(env, jIk, Ik);
m_manager->set_peerDeviceStatus(jni::Make<std::string>(env, jpeerDeviceId), *Ik, j2cPeerDeviceStatus(jstatus));
} catch (BctbxException const &e) {
ThrowJavaLimeException(env, e.str());
} catch (std::exception const &e) { // catch anything
ThrowJavaLimeException(env, e.what());
}
}
void set_peerDeviceStatus(jni::JNIEnv &env, const jni::String &jpeerDeviceId, const jni::jint jstatus) {
......@@ -420,6 +452,10 @@ struct jLimeManager {
jni::jint get_peerDeviceStatus(jni::JNIEnv &env, const jni::String &jpeerDeviceId) {
return c2jPeerDeviceStatus(m_manager->get_peerDeviceStatus(jni::Make<std::string>(env, jpeerDeviceId)));
}
void delete_peerDevice(jni::JNIEnv &env, const jni::String &jpeerDeviceId) {
m_manager->delete_peerDevice(jni::Make<std::string>(env, jpeerDeviceId));
}
};
#define METHOD(MethodPtr, name) jni::MakeNativePeerMethod<decltype(MethodPtr), (MethodPtr)>(name)
......@@ -436,7 +472,8 @@ jni::RegisterNativePeer<jLimeManager>(env, jni::Class<jLimeManager>::Find(env),
METHOD(&jLimeManager::get_selfIdentityKey, "get_selfIdentityKey"),
METHOD(&jLimeManager::set_peerDeviceStatus_Ik, "n_set_peerDeviceStatus_Ik"),
METHOD(&jLimeManager::set_peerDeviceStatus, "n_set_peerDeviceStatus"),
METHOD(&jLimeManager::get_peerDeviceStatus, "n_get_peerDeviceStatus")
METHOD(&jLimeManager::get_peerDeviceStatus, "n_get_peerDeviceStatus"),
METHOD(&jLimeManager::delete_peerDevice, "delete_peerDevice")
);
// bind the process_response to the static java LimeManager.process_response method
......
......@@ -36,4 +36,19 @@ add_jar(LimeTester ${LIMETESTER_SOURCE_FILES_JAVA} INCLUDE_JARS ${LIME_JAR_FILE}
get_target_property(Tester_jarFile LimeTester JAR_FILE)
add_test(NAME JNI COMMAND ${Java_JAVA_EXECUTABLE} -Djava.library.path=${CMAKE_BINARY_DIR}/src -cp ${Tester_jarFile}:${LIME_JAR_FILE} -Xcheck:jni -ea org/linphone/limeTester/LimeTester)
configure_file(../data/pattern_getSelfIk.C25519.sqlite3 ${CMAKE_CURRENT_BINARY_DIR}/pattern_getSelfIk.C25519.sqlite3 COPYONLY)
configure_file(../data/pattern_getSelfIk.C448.sqlite3 ${CMAKE_CURRENT_BINARY_DIR}/pattern_getSelfIk.C448.sqlite3 COPYONLY)
if (ENABLE_CURVE25519)
set (JAVA_CURVE25519_OPTION -DenableC25519=true)
else ()
set (JAVA_CURVE25519_OPTION -DenableC25519=false)
endif()
if (ENABLE_CURVE448)
set (JAVA_CURVE448_OPTION -DenableC448=true)
else ()
set (JAVA_CURVE448_OPTION -DenableC448=false)
endif()
add_test(NAME JNI COMMAND ${Java_JAVA_EXECUTABLE} ${JAVA_CURVE25519_OPTION} ${JAVA_CURVE448_OPTION} -Djava.library.path=${CMAKE_BINARY_DIR}/src -cp ${Tester_jarFile}:${LIME_JAR_FILE} -Xcheck:jni -ea org/linphone/limeTester/LimeTester)
......@@ -84,7 +84,7 @@ class LimeStatusCallbackImpl_Mailbox implements LimeStatusCallback {
public void resetStatus() {
success = 0;
fail = 0;
timeout = 4000;
timeout = 5000;
recipients = null;
}
......@@ -146,122 +146,127 @@ public class HelloWorld {
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);
// Do not forget do deallocate the native ressources
aliceManager.nativeDestructor();
bobManager.nativeDestructor();
aliceManager = null;
bobManager = null;
try {
// 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