From 00df6b89a1c1c81fc71045e044e679ebcf9320a2 Mon Sep 17 00:00:00 2001
From: Johan Pascal <johan.pascal@belledonne-communications.com>
Date: Mon, 4 Feb 2019 12:02:40 +0700
Subject: [PATCH] JNI add exception management + port more lime test to java +
 select tests run according to curves selected at build

---
 include/lime/lime.hpp                         |    2 +-
 src/java/CMakeLists.txt                       |    1 +
 src/java/org/linphone/lime/LimeException.java |   25 +
 src/java/org/linphone/lime/LimeManager.java   |   44 +-
 .../org/linphone/lime/LimePostToX3DH.java     |    2 +-
 .../org/linphone/lime/LimeStatusCallback.java |    4 +-
 src/lime_jni.cpp                              |   87 +-
 tester/java/CMakeLists.txt                    |   17 +-
 .../org/linphone/limeTester/HelloWorld.java   |  239 +--
 .../linphone/limeTester/LimeLimeTester.java   | 1505 +++++++++++++++--
 .../org/linphone/limeTester/LimeTester.java   |   67 +-
 .../linphone/limeTester/LimeTesterUtils.java  |   72 +
 12 files changed, 1717 insertions(+), 348 deletions(-)
 create mode 100644 src/java/org/linphone/lime/LimeException.java

diff --git a/include/lime/lime.hpp b/include/lime/lime.hpp
index 09aa514..dab64b1 100644
--- a/include/lime/lime.hpp
+++ b/include/lime/lime.hpp
@@ -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);
 
diff --git a/src/java/CMakeLists.txt b/src/java/CMakeLists.txt
index ce9e03d..92061c5 100644
--- a/src/java/CMakeLists.txt
+++ b/src/java/CMakeLists.txt
@@ -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
diff --git a/src/java/org/linphone/lime/LimeException.java b/src/java/org/linphone/lime/LimeException.java
new file mode 100644
index 0000000..eb853fe
--- /dev/null
+++ b/src/java/org/linphone/lime/LimeException.java
@@ -0,0 +1,25 @@
+/*
+	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);
+	}
+}
diff --git a/src/java/org/linphone/lime/LimeManager.java b/src/java/org/linphone/lime/LimeManager.java
index e5bfa03..b0ecabf 100644
--- a/src/java/org/linphone/lime/LimeManager.java
+++ b/src/java/org/linphone/lime/LimeManager.java
@@ -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
 	 *
diff --git a/src/java/org/linphone/lime/LimePostToX3DH.java b/src/java/org/linphone/lime/LimePostToX3DH.java
index 7a13cb5..cd00df3 100644
--- a/src/java/org/linphone/lime/LimePostToX3DH.java
+++ b/src/java/org/linphone/lime/LimePostToX3DH.java
@@ -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);
 }
diff --git a/src/java/org/linphone/lime/LimeStatusCallback.java b/src/java/org/linphone/lime/LimeStatusCallback.java
index 12c6f4a..3581f71 100644
--- a/src/java/org/linphone/lime/LimeStatusCallback.java
+++ b/src/java/org/linphone/lime/LimeStatusCallback.java
@@ -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);
diff --git a/src/lime_jni.cpp b/src/lime_jni.cpp
index ed3e0d2..6c1f58e 100644
--- a/src/lime_jni.cpp
+++ b/src/lime_jni.cpp
@@ -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
diff --git a/tester/java/CMakeLists.txt b/tester/java/CMakeLists.txt
index 3978bae..8ede820 100644
--- a/tester/java/CMakeLists.txt
+++ b/tester/java/CMakeLists.txt
@@ -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)
diff --git a/tester/java/org/linphone/limeTester/HelloWorld.java b/tester/java/org/linphone/limeTester/HelloWorld.java
index 61a93b3..7616718 100644
--- a/tester/java/org/linphone/limeTester/HelloWorld.java
+++ b/tester/java/org/linphone/limeTester/HelloWorld.java
@@ -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
+			// 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;
+		}
+		catch (LimeException e) {
+			assert(false):"Got an unexpected exception during Hello World test: "+e.getMessage();
+		}
 
 		// Remove database files
 		file = new File(aliceDbFilename);
diff --git a/tester/java/org/linphone/limeTester/LimeLimeTester.java b/tester/java/org/linphone/limeTester/LimeLimeTester.java
index 9c4ed3e..46f8df3 100644
--- a/tester/java/org/linphone/limeTester/LimeLimeTester.java
+++ b/tester/java/org/linphone/limeTester/LimeLimeTester.java
@@ -21,8 +21,125 @@ package org.linphone.limeTester;
 import org.linphone.lime.*;
 import java.util.UUID;
 import java.io.File;
+import java.util.Arrays;
 
 public class LimeLimeTester {
+	/* Test Scenario:
+	 * - Create alice using LimeManager
+	 * - Fail at creating alice again in the same DB
+	 * - Delete Alice
+	 * - Fail at deleting an unknown user
+	 * - Create Alice again
+	 * - using an other DB, create alice, success on local Storage but shall get a callback error
+	 * - clean DB by deleting alice
+	 */
+	public static void user_management(LimeCurveId curveId, String dbBasename, String x3dhServerUrl, LimePostToX3DH postObj) {
+		int expected_success = 0;
+		int expected_fail = 0;
+
+		LimeTesterUtils TesterUtils = new LimeTesterUtils();
+
+		// Create a callback, this one will be used for all operations
+		LimeStatusCallbackImpl statusCallback = new LimeStatusCallbackImpl();
+
+		// 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 aliceDb2Filename = "alice."+dbBasename+curveIdString+".db2.sqlite3"; // this one will be used to generate another base
+		File file = new File(aliceDbFilename);
+		file.delete();
+
+		// Create alice lime managers
+		LimeManager aliceManager = new LimeManager(aliceDbFilename, postObj);
+
+		// Create random device id for alice and bob
+		String AliceDeviceId = "alice."+UUID.randomUUID().toString();
+
+		try {
+			aliceManager.create_user(AliceDeviceId, x3dhServerUrl, curveId, 10, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success));
+		}
+		catch (LimeException e) {
+			assert(false):"Got an unexpected exception during Lime user management test: "+e.getMessage();
+		}
+
+		boolean gotException = false;
+		// Create again alice device, it shall fail with an exception
+		try {
+			aliceManager.create_user(AliceDeviceId, x3dhServerUrl, curveId, 10, statusCallback);
+		}
+		catch (LimeException e) {
+			gotException = true;
+		}
+
+		assert (gotException == true);
+
+		// Delete alice device, it shall work
+		try {
+			expected_success+= 1;
+			aliceManager.delete_user(AliceDeviceId, statusCallback);
+			assert (statusCallback.wait_for_success(expected_success));
+		}
+		catch (LimeException e) {
+			assert(false):"Got an unexpected exception during Lime user management test: "+e.getMessage();
+		}
+
+		// Delete bob device, it is not in DB, it shall raise an exception
+		gotException = false;
+		try {
+			aliceManager.delete_user("bob", statusCallback);
+		}
+		catch (LimeException e) {
+			gotException = true;
+		}
+		assert (gotException == true):"Expected an exception when deleting an unknown device";
+
+		try {
+			// Create alice again
+			aliceManager.create_user(AliceDeviceId, x3dhServerUrl, curveId, 10, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success));
+
+			// create a new db file and an other manager
+			file = new File(aliceDb2Filename);
+			file.delete();
+
+			// Create alice lime managers
+			LimeManager alice2Manager = new LimeManager(aliceDb2Filename, postObj);
+			// Create alice again, it shall pass the local creation but fail on X3DH server.
+			// So we will get a fail in callback but no exception
+			alice2Manager.create_user(AliceDeviceId, x3dhServerUrl, curveId, 10, statusCallback);
+			expected_fail+= 1;
+			assert (statusCallback.wait_for_fail(expected_fail));
+
+			// clean db: delete users
+			expected_success+= 1;
+			aliceManager.delete_user(AliceDeviceId, statusCallback);
+			assert (statusCallback.wait_for_success(expected_success));
+			assert (statusCallback.fail == expected_fail);
+
+			// Do not forget do deallocate the native ressources
+			aliceManager.nativeDestructor();
+			alice2Manager.nativeDestructor();
+			aliceManager = null;
+		}
+		catch (LimeException e) {
+			assert(false):"Got an unexpected exception during Lime user management test: "+e.getMessage();
+		}
+
+		// Remove database files
+		file = new File(aliceDbFilename);
+		file.delete();
+		file = new File(aliceDb2Filename);
+		file.delete();
+	}
+
 	/* Test Scenario:
 	 * - Alice and Bob(d1 and d2) register themselves on X3DH server
 	 * - Alice send message to Bob (d1 and d2)
@@ -42,7 +159,6 @@ public class LimeLimeTester {
 		LimeTesterUtils TesterUtils = new LimeTesterUtils();
 
 		// 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 db filenames and delete potential existing ones
@@ -59,142 +175,147 @@ public class LimeLimeTester {
 		file = new File(bobDbFilename);
 		file.delete();
 
-		// Create bob and alice lime managers
-		LimeManager aliceManager = new LimeManager(aliceDbFilename, postObj);
-		LimeManager bobManager = new LimeManager(bobDbFilename, postObj);
+		try {
+			// Create bob and alice lime managers
+			LimeManager aliceManager = new LimeManager(aliceDbFilename, postObj);
+			LimeManager bobManager = new LimeManager(bobDbFilename, postObj);
 
-		// Create random device id for alice and bob
-		String AliceDeviceId = "alice."+UUID.randomUUID().toString();
-		String BobDeviceId1 = "bob.d1."+UUID.randomUUID().toString();
-		String BobDeviceId2 = "bob.d2."+UUID.randomUUID().toString();
+			// Create random device id for alice and bob
+			String AliceDeviceId = "alice."+UUID.randomUUID().toString();
+			String BobDeviceId1 = "bob.d1."+UUID.randomUUID().toString();
+			String BobDeviceId2 = "bob.d2."+UUID.randomUUID().toString();
 
-		aliceManager.create_user(AliceDeviceId, x3dhServerUrl, curveId, 10, statusCallback);
-		expected_success+= 1;
-		assert (statusCallback.wait_for_success(expected_success));
-		bobManager.create_user(BobDeviceId1, x3dhServerUrl, curveId, 10, statusCallback);
-		expected_success+= 1;
-		assert (statusCallback.wait_for_success(expected_success)); // We cannot create both bob's device at the same time as they use the same database
-		bobManager.create_user(BobDeviceId2, x3dhServerUrl, curveId, 10, statusCallback);
-		expected_success+= 1;
-		assert (statusCallback.wait_for_success(expected_success));
-		assert (statusCallback.fail == expected_fail);
+			aliceManager.create_user(AliceDeviceId, x3dhServerUrl, curveId, 10, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success));
+			bobManager.create_user(BobDeviceId1, x3dhServerUrl, curveId, 10, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success)); // We cannot create both bob's device at the same time as they use the same database
+			bobManager.create_user(BobDeviceId2, x3dhServerUrl, curveId, 10, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success));
+			assert (statusCallback.fail == expected_fail);
 
-		// Alice sends a message to bob
-		RecipientData[] recipients = new RecipientData[2]; // Bob has 2 devices
-		recipients[0] = new RecipientData(BobDeviceId1);
-		recipients[1] = new RecipientData(BobDeviceId2);
+			// Alice sends a message to bob
+			RecipientData[] recipients = new RecipientData[2]; // Bob has 2 devices
+			recipients[0] = new RecipientData(BobDeviceId1);
+			recipients[1] = new RecipientData(BobDeviceId2);
 
-		LimeOutputBuffer cipherMessage = new LimeOutputBuffer();
-		aliceManager.encrypt(AliceDeviceId, "bob", recipients, LimeTesterUtils.patterns[0].getBytes(), cipherMessage, statusCallback);
-		expected_success+= 1;
-		assert (statusCallback.wait_for_success(expected_success));
+			LimeOutputBuffer cipherMessage = new LimeOutputBuffer();
+			aliceManager.encrypt(AliceDeviceId, "bob", recipients, LimeTesterUtils.patterns[0].getBytes(), cipherMessage, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success));
 
-		// loop on outputs and decrypt with bob Manager
-		for (RecipientData recipient : recipients) {
-			LimeOutputBuffer decodedMessage = new LimeOutputBuffer();
-			assert(TesterUtils.DR_message_holdsX3DHInit(recipient.DRmessage).holdsX3DHInit);  // new sessions created, they must convey X3DH init message
-			assert(bobManager.decrypt(recipient.deviceId, "bob", AliceDeviceId, recipient.DRmessage, cipherMessage.buffer, decodedMessage) != LimePeerDeviceStatus.FAIL);
-			String s = new String(decodedMessage.buffer);
-			assert (s.equals(LimeTesterUtils.patterns[0])):"Decoded message is not the encoded one";
-		}
+			// loop on outputs and decrypt with bob Manager
+			for (RecipientData recipient : recipients) {
+				LimeOutputBuffer decodedMessage = new LimeOutputBuffer();
+				assert(TesterUtils.DR_message_holdsX3DHInit(recipient.DRmessage).holdsX3DHInit);  // new sessions created, they must convey X3DH init message
+				assert(bobManager.decrypt(recipient.deviceId, "bob", AliceDeviceId, recipient.DRmessage, cipherMessage.buffer, decodedMessage) != LimePeerDeviceStatus.FAIL);
+				String s = new String(decodedMessage.buffer);
+				assert (s.equals(LimeTesterUtils.patterns[0])):"Decoded message is not the encoded one";
+			}
 
-		// encrypt another one, same recipients(we shall have no new X3DH session but still the X3DH init message)
-		recipients = new RecipientData[2]; // Bob has 2 devices
-		recipients[0] = new RecipientData(BobDeviceId1);
-		recipients[1] = new RecipientData(BobDeviceId2);
+			// encrypt another one, same recipients(we shall have no new X3DH session but still the X3DH init message)
+			recipients = new RecipientData[2]; // Bob has 2 devices
+			recipients[0] = new RecipientData(BobDeviceId1);
+			recipients[1] = new RecipientData(BobDeviceId2);
 
-		cipherMessage = new LimeOutputBuffer();
-		aliceManager.encrypt(AliceDeviceId, "bob", recipients, LimeTesterUtils.patterns[1].getBytes(), cipherMessage, statusCallback);
-		expected_success+= 1;
-		assert (statusCallback.wait_for_success(expected_success));
+			cipherMessage = new LimeOutputBuffer();
+			aliceManager.encrypt(AliceDeviceId, "bob", recipients, LimeTesterUtils.patterns[1].getBytes(), cipherMessage, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success));
 
-		// loop on outputs and decrypt with bob Manager
-		for (RecipientData recipient : recipients) {
-			LimeOutputBuffer decodedMessage = new LimeOutputBuffer();
-			assert(TesterUtils.DR_message_holdsX3DHInit(recipient.DRmessage).holdsX3DHInit);  // they must again convey X3DH init message
-			assert(bobManager.decrypt(recipient.deviceId, "bob", AliceDeviceId, recipient.DRmessage, cipherMessage.buffer, decodedMessage) == LimePeerDeviceStatus.UNTRUSTED); // now we know alice
-			String s = new String(decodedMessage.buffer);
-			assert (s.equals(LimeTesterUtils.patterns[1])):"Decoded message is not the encoded one";
-		}
+			// loop on outputs and decrypt with bob Manager
+			for (RecipientData recipient : recipients) {
+				LimeOutputBuffer decodedMessage = new LimeOutputBuffer();
+				assert(TesterUtils.DR_message_holdsX3DHInit(recipient.DRmessage).holdsX3DHInit);  // they must again convey X3DH init message
+				assert(bobManager.decrypt(recipient.deviceId, "bob", AliceDeviceId, recipient.DRmessage, cipherMessage.buffer, decodedMessage) == LimePeerDeviceStatus.UNTRUSTED); // now we know alice
+				String s = new String(decodedMessage.buffer);
+				assert (s.equals(LimeTesterUtils.patterns[1])):"Decoded message is not the encoded one";
+			}
 
-		// bob.d1 reply to alice and copy bob.d2
-		recipients = new RecipientData[2];
-		recipients[0] = new RecipientData(AliceDeviceId);
-		recipients[1] = new RecipientData(BobDeviceId2);
+			// bob.d1 reply to alice and copy bob.d2
+			recipients = new RecipientData[2];
+			recipients[0] = new RecipientData(AliceDeviceId);
+			recipients[1] = new RecipientData(BobDeviceId2);
 
-		cipherMessage = new LimeOutputBuffer();
-		bobManager.encrypt(BobDeviceId1, "alice", recipients, LimeTesterUtils.patterns[2].getBytes(), cipherMessage, statusCallback);
-		expected_success+= 1;
-		assert (statusCallback.wait_for_success(expected_success));
+			cipherMessage = new LimeOutputBuffer();
+			bobManager.encrypt(BobDeviceId1, "alice", recipients, LimeTesterUtils.patterns[2].getBytes(), cipherMessage, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success));
 
-		// decrypt it
-		LimeOutputBuffer decodedMessage = new LimeOutputBuffer();
-		assert(TesterUtils.DR_message_holdsX3DHInit(recipients[0].DRmessage).holdsX3DHInit == false);  // alice.d1 to bob.d1 already set up the DR Session, we shall not have any  X3DH message here
-		assert(aliceManager.decrypt(recipients[0].deviceId, "alice", BobDeviceId1, recipients[0].DRmessage, cipherMessage.buffer, decodedMessage) == LimePeerDeviceStatus.UNTRUSTED); // we know alice
-		String s = new String(decodedMessage.buffer);
-		assert (s.equals(LimeTesterUtils.patterns[2])):"Decoded message is not the encoded one";
+			// decrypt it
+			LimeOutputBuffer decodedMessage = new LimeOutputBuffer();
+			assert(TesterUtils.DR_message_holdsX3DHInit(recipients[0].DRmessage).holdsX3DHInit == false);  // alice.d1 to bob.d1 already set up the DR Session, we shall not have any  X3DH message here
+			assert(aliceManager.decrypt(recipients[0].deviceId, "alice", BobDeviceId1, recipients[0].DRmessage, cipherMessage.buffer, decodedMessage) == LimePeerDeviceStatus.UNTRUSTED); // we know alice
+			String s = new String(decodedMessage.buffer);
+			assert (s.equals(LimeTesterUtils.patterns[2])):"Decoded message is not the encoded one";
 
-		decodedMessage = new LimeOutputBuffer();
-		assert(TesterUtils.DR_message_holdsX3DHInit(recipients[1].DRmessage).holdsX3DHInit);   // bob.d1 to bob.d2 is a new session, we must have a X3DH message here
-		assert(bobManager.decrypt(recipients[1].deviceId, "alice", BobDeviceId1, recipients[1].DRmessage, cipherMessage.buffer, decodedMessage) != LimePeerDeviceStatus.FAIL); // as we use the same manager for bob.d1 and bob.d2, the are untrusted but it shall be unknown, just check it does not fail
-		s = new String(decodedMessage.buffer);
-		assert (s.equals(LimeTesterUtils.patterns[2])):"Decoded message is not the encoded one";
+			decodedMessage = new LimeOutputBuffer();
+			assert(TesterUtils.DR_message_holdsX3DHInit(recipients[1].DRmessage).holdsX3DHInit);   // bob.d1 to bob.d2 is a new session, we must have a X3DH message here
+			assert(bobManager.decrypt(recipients[1].deviceId, "alice", BobDeviceId1, recipients[1].DRmessage, cipherMessage.buffer, decodedMessage) != LimePeerDeviceStatus.FAIL); // as we use the same manager for bob.d1 and bob.d2, the are untrusted but it shall be unknown, just check it does not fail
+			s = new String(decodedMessage.buffer);
+			assert (s.equals(LimeTesterUtils.patterns[2])):"Decoded message is not the encoded one";
 
-		// Now do bob.d2 to alice and bob.d1 every one has an open session towards everyone
-		recipients = new RecipientData[2];
-		recipients[0] = new RecipientData(AliceDeviceId);
-		recipients[1] = new RecipientData(BobDeviceId1);
+			// Now do bob.d2 to alice and bob.d1 every one has an open session towards everyone
+			recipients = new RecipientData[2];
+			recipients[0] = new RecipientData(AliceDeviceId);
+			recipients[1] = new RecipientData(BobDeviceId1);
 
-		cipherMessage = new LimeOutputBuffer();
-		bobManager.encrypt(BobDeviceId2, "alice", recipients, LimeTesterUtils.patterns[3].getBytes(), cipherMessage, statusCallback);
-		expected_success+= 1;
-		assert (statusCallback.wait_for_success(expected_success));
+			cipherMessage = new LimeOutputBuffer();
+			bobManager.encrypt(BobDeviceId2, "alice", recipients, LimeTesterUtils.patterns[3].getBytes(), cipherMessage, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success));
 
-		// decrypt it
-		decodedMessage = new LimeOutputBuffer();
-		assert(TesterUtils.DR_message_holdsX3DHInit(recipients[0].DRmessage).holdsX3DHInit == false);  // alice.d1 to bob.d1 already set up the DR Session, we shall not have any  X3DH message here
-		assert(aliceManager.decrypt(recipients[0].deviceId, "alice", BobDeviceId2, recipients[0].DRmessage, cipherMessage.buffer, decodedMessage) == LimePeerDeviceStatus.UNTRUSTED); // we know alice
-		s = new String(decodedMessage.buffer);
-		assert (s.equals(LimeTesterUtils.patterns[3])):"Decoded message is not the encoded one";
-
-		decodedMessage = new LimeOutputBuffer();
-		assert(TesterUtils.DR_message_holdsX3DHInit(recipients[1].DRmessage).holdsX3DHInit == false);   // bob.d2 to bob.d1 is already open so no X3DH message here
-		assert(bobManager.decrypt(recipients[1].deviceId, "alice", BobDeviceId2, recipients[1].DRmessage, cipherMessage.buffer, decodedMessage) == LimePeerDeviceStatus.UNTRUSTED); // Bob's d1 knows bob's d2
-		s = new String(decodedMessage.buffer);
-		assert (s.equals(LimeTesterUtils.patterns[3])):"Decoded message is not the encoded one";
-
-		// encrypt another one from alice to bob.d1 and .d2, it must not send X3DH init anymore
-		recipients = new RecipientData[2]; // Bob has 2 devices
-		recipients[0] = new RecipientData(BobDeviceId1);
-		recipients[1] = new RecipientData(BobDeviceId2);
-
-		cipherMessage = new LimeOutputBuffer();
-		aliceManager.encrypt(AliceDeviceId, "bob", recipients, LimeTesterUtils.patterns[4].getBytes(), cipherMessage, statusCallback);
-		expected_success+= 1;
-		assert (statusCallback.wait_for_success(expected_success));
+			// decrypt it
+			decodedMessage = new LimeOutputBuffer();
+			assert(TesterUtils.DR_message_holdsX3DHInit(recipients[0].DRmessage).holdsX3DHInit == false);  // alice.d1 to bob.d1 already set up the DR Session, we shall not have any  X3DH message here
+			assert(aliceManager.decrypt(recipients[0].deviceId, "alice", BobDeviceId2, recipients[0].DRmessage, cipherMessage.buffer, decodedMessage) == LimePeerDeviceStatus.UNTRUSTED); // we know alice
+			s = new String(decodedMessage.buffer);
+			assert (s.equals(LimeTesterUtils.patterns[3])):"Decoded message is not the encoded one";
 
-		// loop on outputs and decrypt with bob Manager
-		for (RecipientData recipient : recipients) {
 			decodedMessage = new LimeOutputBuffer();
-			assert(TesterUtils.DR_message_holdsX3DHInit(recipient.DRmessage).holdsX3DHInit == false);
-			assert(bobManager.decrypt(recipient.deviceId, "bob", AliceDeviceId, recipient.DRmessage, cipherMessage.buffer, decodedMessage) == LimePeerDeviceStatus.UNTRUSTED); // now we know alice
+			assert(TesterUtils.DR_message_holdsX3DHInit(recipients[1].DRmessage).holdsX3DHInit == false);   // bob.d2 to bob.d1 is already open so no X3DH message here
+			assert(bobManager.decrypt(recipients[1].deviceId, "alice", BobDeviceId2, recipients[1].DRmessage, cipherMessage.buffer, decodedMessage) == LimePeerDeviceStatus.UNTRUSTED); // Bob's d1 knows bob's d2
 			s = new String(decodedMessage.buffer);
-			assert (s.equals(LimeTesterUtils.patterns[4])):"Decoded message is not the encoded one";
-		}
+			assert (s.equals(LimeTesterUtils.patterns[3])):"Decoded message is not the encoded one";
 
-		// clean db: delete users
-		expected_success+= 3;
-		aliceManager.delete_user(AliceDeviceId, statusCallback);
-		bobManager.delete_user(BobDeviceId1, statusCallback);
-		bobManager.delete_user(BobDeviceId2, statusCallback);
-		assert (statusCallback.wait_for_success(expected_success));
-		assert (statusCallback.fail == expected_fail);
+			// encrypt another one from alice to bob.d1 and .d2, it must not send X3DH init anymore
+			recipients = new RecipientData[2]; // Bob has 2 devices
+			recipients[0] = new RecipientData(BobDeviceId1);
+			recipients[1] = new RecipientData(BobDeviceId2);
 
-		// Do not forget do deallocate the native ressources
-		aliceManager.nativeDestructor();
-		bobManager.nativeDestructor();
-		aliceManager = null;
-		bobManager = null;
+			cipherMessage = new LimeOutputBuffer();
+			aliceManager.encrypt(AliceDeviceId, "bob", recipients, LimeTesterUtils.patterns[4].getBytes(), cipherMessage, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success));
+
+			// loop on outputs and decrypt with bob Manager
+			for (RecipientData recipient : recipients) {
+				decodedMessage = new LimeOutputBuffer();
+				assert(TesterUtils.DR_message_holdsX3DHInit(recipient.DRmessage).holdsX3DHInit == false);
+				assert(bobManager.decrypt(recipient.deviceId, "bob", AliceDeviceId, recipient.DRmessage, cipherMessage.buffer, decodedMessage) == LimePeerDeviceStatus.UNTRUSTED); // now we know alice
+				s = new String(decodedMessage.buffer);
+				assert (s.equals(LimeTesterUtils.patterns[4])):"Decoded message is not the encoded one";
+			}
+
+			// clean db: delete users
+			expected_success+= 3;
+			aliceManager.delete_user(AliceDeviceId, statusCallback);
+			bobManager.delete_user(BobDeviceId1, statusCallback);
+			bobManager.delete_user(BobDeviceId2, 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;
+		}
+		catch (LimeException e) {
+			assert(false):"Got an unexpected exception during Lime Basic test : "+e.getMessage();
+		}
 
 		// Remove database files
 		file = new File(aliceDbFilename);
@@ -218,7 +339,6 @@ public class LimeLimeTester {
 		LimeTesterUtils TesterUtils = new LimeTesterUtils();
 
 		// 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 db filenames and delete potential existing ones
@@ -235,80 +355,1107 @@ public class LimeLimeTester {
 		file = new File(bobDbFilename);
 		file.delete();
 
-		// Create bob and alice lime managers
-		LimeManager aliceManager = new LimeManager(aliceDbFilename, postObj);
-		LimeManager bobManager = new LimeManager(bobDbFilename, postObj);
+		try {
+			// Create bob and alice lime managers
+			LimeManager aliceManager = new LimeManager(aliceDbFilename, postObj);
+			LimeManager bobManager = new LimeManager(bobDbFilename, postObj);
 
-		// Create random device id for alice and bob
-		String AliceDeviceId = "alice."+UUID.randomUUID().toString();
-		String BobDeviceId1 = "bob.d1."+UUID.randomUUID().toString();
-		String BobDeviceId2 = "bob.d2."+UUID.randomUUID().toString();
-		String BobDeviceId3 = "bob.d3."+UUID.randomUUID().toString(); // we just get a random device name but we will not create it
+			// Create random device id for alice and bob
+			String AliceDeviceId = "alice."+UUID.randomUUID().toString();
+			String BobDeviceId1 = "bob.d1."+UUID.randomUUID().toString();
+			String BobDeviceId2 = "bob.d2."+UUID.randomUUID().toString();
+			String BobDeviceId3 = "bob.d3."+UUID.randomUUID().toString(); // we just get a random device name but we will not create it
 
-		aliceManager.create_user(AliceDeviceId, x3dhServerUrl, curveId, 10, statusCallback);
-		expected_success+= 1;
-		assert (statusCallback.wait_for_success(expected_success));
-		bobManager.create_user(BobDeviceId1, x3dhServerUrl, curveId, 10, statusCallback);
-		expected_success+= 1;
-		assert (statusCallback.wait_for_success(expected_success)); // We cannot create both bob's device at the same time as they use the same database
-		bobManager.create_user(BobDeviceId2, x3dhServerUrl, curveId, 10, statusCallback);
-		expected_success+= 1;
-		assert (statusCallback.wait_for_success(expected_success));
-		assert (statusCallback.fail == expected_fail);
+			aliceManager.create_user(AliceDeviceId, x3dhServerUrl, curveId, 10, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success));
+			bobManager.create_user(BobDeviceId1, x3dhServerUrl, curveId, 10, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success)); // We cannot create both bob's device at the same time as they use the same database
+			bobManager.create_user(BobDeviceId2, x3dhServerUrl, curveId, 10, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success));
+			assert (statusCallback.fail == expected_fail);
+
+			// Alice sends a message to bob.d3 devices, non existent
+			RecipientData[] recipients = new RecipientData[1];
+			recipients[0] = new RecipientData(BobDeviceId3);
+
+			LimeOutputBuffer cipherMessage = new LimeOutputBuffer();
+			aliceManager.encrypt(AliceDeviceId, "bob", recipients, LimeTesterUtils.patterns[0].getBytes(), cipherMessage, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success));
+			assert (recipients[0].getPeerStatus() == LimePeerDeviceStatus.FAIL); // the device is unknown, so it shall fail
+
+			// Alice sends a message to bob 3 devices, one is non existent
+			recipients = new RecipientData[3];
+			recipients[0] = new RecipientData(BobDeviceId1);
+			recipients[1] = new RecipientData(BobDeviceId2);
+			recipients[2] = new RecipientData(BobDeviceId3);
+
+			cipherMessage = new LimeOutputBuffer();
+			aliceManager.encrypt(AliceDeviceId, "bob", recipients, LimeTesterUtils.patterns[0].getBytes(), cipherMessage, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success));
+
+			// decrypt with bob Manager
+			// bob.d1
+			LimeOutputBuffer decodedMessage = new LimeOutputBuffer();
+			assert(TesterUtils.DR_message_holdsX3DHInit(recipients[0].DRmessage).holdsX3DHInit);  // new sessions created, they must convey X3DH init message
+			assert(bobManager.decrypt(recipients[0].deviceId, "bob", AliceDeviceId, recipients[0].DRmessage, cipherMessage.buffer, decodedMessage) != LimePeerDeviceStatus.FAIL);
+			String s = new String(decodedMessage.buffer);
+			assert (s.equals(LimeTesterUtils.patterns[0])):"Decoded message is not the encoded one";
+
+			// bob.d2
+			decodedMessage = new LimeOutputBuffer();
+			assert(TesterUtils.DR_message_holdsX3DHInit(recipients[1].DRmessage).holdsX3DHInit);  // new sessions created, they must convey X3DH init message
+			assert(bobManager.decrypt(recipients[1].deviceId, "bob", AliceDeviceId, recipients[1].DRmessage, cipherMessage.buffer, decodedMessage) != LimePeerDeviceStatus.FAIL);
+			s = new String(decodedMessage.buffer);
+			assert (s.equals(LimeTesterUtils.patterns[0])):"Decoded message is not the encoded one";
+
+			// bob.d3
+			assert (recipients[2].getPeerStatus() == LimePeerDeviceStatus.FAIL); // the device is unknown, so it shall fail
+			assert (recipients[2].DRmessage.length == 0); // encrypt produced no DRmessage for non existent device
+
+			// clean db: delete users
+			expected_success+= 3;
+			aliceManager.delete_user(AliceDeviceId, statusCallback);
+			bobManager.delete_user(BobDeviceId1, statusCallback);
+			bobManager.delete_user(BobDeviceId2, 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;
+		}
+		catch (LimeException e) {
+			assert(false):"Got an unexpected exception during Lime user not found test : "+e.getMessage();
+		}
+
+		// Remove database files
+		file = new File(aliceDbFilename);
+		file.delete();
+		file = new File(bobDbFilename);
+		file.delete();
+	}
+
+	/* Test Scenario :
+	 * alice.d1 will encrypt to bob.d1, bob.d2, bob.d3, bob.d4
+	 * - message burst from alice.d1 -> bob.d1
+	 * - wait for callbacks. alice.d1 hold session toward d1 only
+	 * then burst encrypt to:
+	 * - bob.d1, bob.d2 : test enqueing if a part of recipients are not available
+	 * - bob.d1 : test going through if we can process it without calling X3DH server
+	 * - bob.d2 : test enqueue and have session ready when processed
+	 * - bob.d3 : test enqueue and must start an asynchronous X3DH request when back
+	 * - bob.d4 : test enqueue and must start an asynchronous X3DH request when back
+	 */
+	public static void x3dh_multidev_operation_queue(LimeCurveId curveId, String dbBasename, String x3dhServerUrl, LimePostToX3DH postObj) {
+		int expected_success = 0;
+		int expected_fail = 0;
+
+		LimeTesterUtils TesterUtils = new LimeTesterUtils();
+
+		// Create a callback, this one will be used for all operations
+		LimeStatusCallbackImpl statusCallback = new LimeStatusCallbackImpl();
+
+		// 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();
+
+		try {
+			// Create bob and alice lime managers
+			LimeManager aliceManager = new LimeManager(aliceDbFilename, postObj);
+			LimeManager bobManager = new LimeManager(bobDbFilename, postObj);
+
+			// Create random device id for alice and bob
+			String AliceDeviceId = "alice."+UUID.randomUUID().toString();
+			String BobDeviceId1 = "bob.d1."+UUID.randomUUID().toString();
+			String BobDeviceId2 = "bob.d2."+UUID.randomUUID().toString();
+			String BobDeviceId3 = "bob.d3."+UUID.randomUUID().toString();
+			String BobDeviceId4 = "bob.d4."+UUID.randomUUID().toString();
+
+			aliceManager.create_user(AliceDeviceId, x3dhServerUrl, curveId, 10, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success));
+			bobManager.create_user(BobDeviceId1, x3dhServerUrl, curveId, 10, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success));
+			bobManager.create_user(BobDeviceId2, x3dhServerUrl, curveId, 10, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success));
+			bobManager.create_user(BobDeviceId3, x3dhServerUrl, curveId, 10, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success));
+			bobManager.create_user(BobDeviceId4, x3dhServerUrl, curveId, 10, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success));
+			assert (statusCallback.fail == expected_fail);
+
+			// Alice encrypts a burst of messages to bob.d1
+			int messageBurstSize = 10;
+			RecipientData[][] recipients = new RecipientData[messageBurstSize][1];
+			LimeOutputBuffer[] cipherMessage = new LimeOutputBuffer[messageBurstSize];
+
+			for (int i=0; i<messageBurstSize; i++) {
+				recipients[i][0] = new RecipientData(BobDeviceId1);
+				cipherMessage[i] = new LimeOutputBuffer();
+			}
+
+			for (int i=0; i<messageBurstSize; i++) {
+				aliceManager.encrypt(AliceDeviceId, "bob", recipients[i], LimeTesterUtils.patterns[i].getBytes(), cipherMessage[i], statusCallback);
+				expected_success+= 1;
+			}
+			assert (statusCallback.wait_for_success(expected_success));
+
+			byte[] X3DH_initMessageBuffer = new byte[0];
+
+			// loop on cipher message and decrypt them bob Manager
+			for (int i=0; i<messageBurstSize; i++) {
+				LimeOutputBuffer decodedMessage = new LimeOutputBuffer();
+				assert(TesterUtils.DR_message_holdsX3DHInit(recipients[i][0].DRmessage).holdsX3DHInit);  // new sessions created, they must convey X3DH init message
+				if (i==0) { // first message of the burst, extract and store the X3DH init message
+					X3DH_initMessageBuffer = TesterUtils.DR_message_extractX3DHInit(recipients[i][0].DRmessage);
+				} else { // following message of the burst, extract X3DH init message and compare it to the first one, they must be the same, we shall not create new sessions
+					byte[] X3DH_initMessageBuffer_next = TesterUtils.DR_message_extractX3DHInit(recipients[i][0].DRmessage);
+					assert(Arrays.equals(X3DH_initMessageBuffer, X3DH_initMessageBuffer_next));
+				}
+				assert(bobManager.decrypt(recipients[i][0].deviceId, "bob", AliceDeviceId, recipients[i][0].DRmessage, cipherMessage[i].buffer, decodedMessage) != LimePeerDeviceStatus.FAIL);
+				String s = new String(decodedMessage.buffer);
+				assert (s.equals(LimeTesterUtils.patterns[i])):"Decoded message is not the encoded one";
+			}
+
+			recipients = new RecipientData[5][];
+			cipherMessage = new LimeOutputBuffer[5];
+			for (int i=0; i<5; i++) {
+				cipherMessage[i] = new LimeOutputBuffer();
+			}
+			// now alice will request encryption of message without waiting for callback to:
+			//  bob.d1,bob.d2 -> this one shall trigger a X3DH request to acquire bob.d2 key bundle
+			recipients[0] = new RecipientData[2];
+			recipients[0][0] = new RecipientData(BobDeviceId1);
+			recipients[0][1] = new RecipientData(BobDeviceId2);
+			//  bob.d1 -> this one shall be just be processed so callback will be called before even returning from encrypt call
+			recipients[1] = new RecipientData[1];
+			recipients[1][0] = new RecipientData(BobDeviceId1);
+			//  bob.d2 -> this one shall be queued and processed when d1,d2 is done but it won't trigger an X3DH request
+			recipients[2] = new RecipientData[1];
+			recipients[2][0] = new RecipientData(BobDeviceId2);
+			//  bob.d3 -> this one shall be queued and processed when previous one is done, it will trigger an X3DH request to get d3 key bundle
+			recipients[3] = new RecipientData[1];
+			recipients[3][0] = new RecipientData(BobDeviceId3);
+			//  bob.d4 -> this one shall be queued and processed when previous one is done, it will trigger an X3DH request to get d4 key bundle
+			recipients[4] = new RecipientData[1];
+			recipients[4][0] = new RecipientData(BobDeviceId4);
+
+			for (int i=0; i<5; i++) {
+				aliceManager.encrypt(AliceDeviceId, "bob", recipients[i], LimeTesterUtils.patterns[messageBurstSize+i].getBytes(), cipherMessage[i], statusCallback);
+				expected_success+= 1;
+			}
+			assert (statusCallback.wait_for_success(expected_success));
+
+			// Check the encryption queue is ok
+			// recipients holds:
+			// recipients[0] -> bob.d1, bob.d2
+			// recipents[1] -> bob.d1
+			// recipients[2] -> bob.d2
+			// Check on these that the X3DH init message are matching (we didn't create a second session an encryption was queued correctly)
+			// recipients[0][0] and recipients[1][0]
+			byte[] X3DH_initMessageBuffer1 = TesterUtils.DR_message_extractX3DHInit(recipients[0][0].DRmessage);
+			byte[] X3DH_initMessageBuffer2 = TesterUtils.DR_message_extractX3DHInit(recipients[1][0].DRmessage);
+			assert(X3DH_initMessageBuffer1.length>0); // check we actually found a X3DH buffer
+			assert(Arrays.equals(X3DH_initMessageBuffer1, X3DH_initMessageBuffer2));
+			// recipients[0][1] and recipients[2][0]
+			X3DH_initMessageBuffer1 = TesterUtils.DR_message_extractX3DHInit(recipients[0][1].DRmessage);
+			X3DH_initMessageBuffer2 = TesterUtils.DR_message_extractX3DHInit(recipients[2][0].DRmessage);
+			assert(X3DH_initMessageBuffer1.length>0); // check we actually found a X3DH buffer
+			assert(Arrays.equals(X3DH_initMessageBuffer1, X3DH_initMessageBuffer2));
+
+			// decrypt and match original message
+			// in recipient[0] we have a message encrypted for bob.d1 and bob.d2
+			LimeOutputBuffer decodedMessage = new LimeOutputBuffer();
+			assert(bobManager.decrypt(BobDeviceId1, "bob", AliceDeviceId, recipients[0][0].DRmessage, cipherMessage[0].buffer, decodedMessage) != LimePeerDeviceStatus.FAIL);
+			String s = new String(decodedMessage.buffer);
+			assert (s.equals(LimeTesterUtils.patterns[messageBurstSize])):"Decoded message is not the encoded one";
+			decodedMessage = new LimeOutputBuffer();
+			assert(bobManager.decrypt(BobDeviceId2, "bob", AliceDeviceId, recipients[0][1].DRmessage, cipherMessage[0].buffer, decodedMessage) != LimePeerDeviceStatus.FAIL);
+			s = new String(decodedMessage.buffer);
+			assert (s.equals(LimeTesterUtils.patterns[messageBurstSize])):"Decoded message is not the encoded one";
+
+			// in recipient[1] we have a message encrypted to bob.d1
+			decodedMessage = new LimeOutputBuffer();
+			assert(bobManager.decrypt(BobDeviceId1, "bob", AliceDeviceId, recipients[1][0].DRmessage, cipherMessage[1].buffer, decodedMessage) != LimePeerDeviceStatus.FAIL);
+			s = new String(decodedMessage.buffer);
+			assert (s.equals(LimeTesterUtils.patterns[messageBurstSize+1])):"Decoded message is not the encoded one";
+
+			// in recipient[2] we have a message encrypted to bob.d2
+			decodedMessage = new LimeOutputBuffer();
+			assert(bobManager.decrypt(BobDeviceId2, "bob", AliceDeviceId, recipients[2][0].DRmessage, cipherMessage[2].buffer, decodedMessage) != LimePeerDeviceStatus.FAIL);
+			s = new String(decodedMessage.buffer);
+			assert (s.equals(LimeTesterUtils.patterns[messageBurstSize+2])):"Decoded message is not the encoded one";
+
+			// in recipient[3] we have a message encrypted to bob.d3
+			decodedMessage = new LimeOutputBuffer();
+			assert(bobManager.decrypt(BobDeviceId3, "bob", AliceDeviceId, recipients[3][0].DRmessage, cipherMessage[3].buffer, decodedMessage) != LimePeerDeviceStatus.FAIL);
+			s = new String(decodedMessage.buffer);
+			assert (s.equals(LimeTesterUtils.patterns[messageBurstSize+3])):"Decoded message is not the encoded one";
+
+			// in recipient[4] we have a message encrypted to bob.d4
+			decodedMessage = new LimeOutputBuffer();
+			assert(bobManager.decrypt(BobDeviceId4, "bob", AliceDeviceId, recipients[4][0].DRmessage, cipherMessage[4].buffer, decodedMessage) != LimePeerDeviceStatus.FAIL);
+			s = new String(decodedMessage.buffer);
+			assert (s.equals(LimeTesterUtils.patterns[messageBurstSize+4])):"Decoded message is not the encoded one";
+
+			// clean db: delete users
+			expected_success+= 5;
+			aliceManager.delete_user(AliceDeviceId, statusCallback);
+			bobManager.delete_user(BobDeviceId1, statusCallback);
+			bobManager.delete_user(BobDeviceId2, statusCallback);
+			bobManager.delete_user(BobDeviceId3, statusCallback);
+			bobManager.delete_user(BobDeviceId4, 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;
+		}
+		catch (LimeException e) {
+			assert(false):"Got an unexpected exception during Multidev operation queue test : "+e.getMessage();
+		}
+
+		// Remove database files
+		file = new File(aliceDbFilename);
+		file.delete();
+		file = new File(bobDbFilename);
+		file.delete();
+	}
+
+	/*
+	 * Test scenario :
+	 * - check the Ik in pattern_db is retrieved as expected
+	 * - try asking for an unknown user, we shall get an exception
+	 */
+	public static void getSelfIk(LimeCurveId curveId, String dbFilename, LimePostToX3DH postObj, byte[] patternIk) {
+		LimeManager aliceManager = new LimeManager(dbFilename, postObj);
+		try  {
+			// retrieve alice identity key
+			LimeOutputBuffer Ik = new LimeOutputBuffer();
+			aliceManager.get_selfIdentityKey("alice", Ik);
+
+			assert(Arrays.equals(Ik.buffer, patternIk));
+		} catch (LimeException e) {
+			aliceManager.nativeDestructor();
+			assert(false):"Got an unexpected exception during getSelfIk test : "+e.getMessage();
+			return;
+		}
+
+		// try to get the Ik of a user not in there, we shall get an exception
+		try {
+			LimeOutputBuffer Ik = new LimeOutputBuffer();
+			aliceManager.get_selfIdentityKey("bob", Ik);
+		} catch (LimeException e) {
+			aliceManager.nativeDestructor();
+			// just swallow it
+			return;
+		}
+		aliceManager.nativeDestructor();
+		assert(false):"Get the Ik of a user not in local Storage didn't throw an exception";
+	}
+
+	/* Test scenario
+	 * - Create managers and DB for alice, bob, carol and dave
+	 * - Get their Identity key and gives alice (apply mutual action with alice's identity key on bob's, carol's and dave's manager)
+	 *     - bob's keys as trusted
+	 *     - set carol's key as trusted and then untrusted so it is in Alice local storage as untrusted
+	 *     - set dave's key as untrusted, as it was not konwn before, it shall not be registered at all in Alice local storage
+	 * - Alice encrypts a message to bob, carol and dave. Check that the peerDevice status given after encryption are respectively: trusted, untrusted, unknown
+	 * - Recipients decrypt Alice message and check we have the expected return values:
+	 *   - Bob: trusted
+	 *   - Carol: untrusted
+	 *   - Dave: unknown
+	 * - Alice encrypts a second message to bob, carol and dave. Check that the peerDevice status given after encryption are respectively: trusted, untrusted, untrusted
+	 * - Recipients decrypt Alice message and check we have the expected return values:
+	 *   - Bob: trusted
+	 *   - Carol: untrusted
+	 *   - Dave: untrusted
+	 */
+	public static void peerDeviceStatus(LimeCurveId curveId, String dbBasename,  String x3dhServerUrl, LimePostToX3DH postObj) {
+		int expected_success = 0;
+		int expected_fail = 0;
+
+		LimeTesterUtils TesterUtils = new LimeTesterUtils();
+
+		// Create a callback, this one will be used for all operations
+		LimeStatusCallbackImpl statusCallback = new LimeStatusCallbackImpl();
+
+		// 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";
+		String carolDbFilename = "carol."+dbBasename+curveIdString+".sqlite3";
+		String daveDbFilename = "dave."+dbBasename+curveIdString+".sqlite3";
+		File file = new File(aliceDbFilename);
+		file.delete();
+		file = new File(bobDbFilename);
+		file.delete();
+		file = new File(carolDbFilename);
+		file.delete();
+		file = new File(daveDbFilename);
+		file.delete();
+
+		try {
+			// Create random device ids
+			String aliceDeviceId = "alice."+UUID.randomUUID().toString();
+			String bobDeviceId = "bob."+UUID.randomUUID().toString();
+			String carolDeviceId = "carol."+UUID.randomUUID().toString();
+			String daveDeviceId = "dave."+UUID.randomUUID().toString();
+
+			// create Manager and devices
+			LimeManager aliceManager = new LimeManager(aliceDbFilename, postObj);
+			aliceManager.create_user(aliceDeviceId, x3dhServerUrl, curveId, 10, statusCallback);
+
+			LimeManager bobManager = new LimeManager(bobDbFilename, postObj);
+			bobManager.create_user(bobDeviceId, x3dhServerUrl, curveId, 10, statusCallback);
+
+			LimeManager carolManager = new LimeManager(carolDbFilename, postObj);
+			carolManager.create_user(carolDeviceId, x3dhServerUrl, curveId, 10, statusCallback);
+
+			LimeManager daveManager = new LimeManager(daveDbFilename, postObj);
+			daveManager.create_user(daveDeviceId, x3dhServerUrl, curveId, 10, statusCallback);
+
+			expected_success += 4;
+			assert (statusCallback.wait_for_success(expected_success));
 
-		// Alice sends a message to bob.d3 devices, non existent
-		RecipientData[] recipients = new RecipientData[1];
-		recipients[0] = new RecipientData(BobDeviceId3);
+			// retrieve their respective Ik
+			LimeOutputBuffer aliceIk = new LimeOutputBuffer();
+			LimeOutputBuffer bobIk = new LimeOutputBuffer();
+			LimeOutputBuffer carolIk = new LimeOutputBuffer();
+			LimeOutputBuffer daveIk = new LimeOutputBuffer();
+			aliceManager.get_selfIdentityKey(aliceDeviceId, aliceIk);
+			bobManager.get_selfIdentityKey(bobDeviceId, bobIk);
+			carolManager.get_selfIdentityKey(carolDeviceId, carolIk);
+			daveManager.get_selfIdentityKey(daveDeviceId, daveIk);
+
+			// exchange trust between alice and bob
+			bobManager.set_peerDeviceStatus(aliceDeviceId, aliceIk.buffer, LimePeerDeviceStatus.TRUSTED);
+			aliceManager.set_peerDeviceStatus(bobDeviceId, bobIk.buffer, LimePeerDeviceStatus.TRUSTED);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.TRUSTED);
+			assert(aliceManager.get_peerDeviceStatus(bobDeviceId) == LimePeerDeviceStatus.TRUSTED);
+
+			// alice and carol gets trust and back to not trust so the Ik gets registered in their local storage
+			carolManager.set_peerDeviceStatus(aliceDeviceId, aliceIk.buffer, LimePeerDeviceStatus.TRUSTED);
+			aliceManager.set_peerDeviceStatus(carolDeviceId, carolIk.buffer, LimePeerDeviceStatus.TRUSTED);
+			carolManager.set_peerDeviceStatus(aliceDeviceId, aliceIk.buffer, LimePeerDeviceStatus.UNTRUSTED);
+			aliceManager.set_peerDeviceStatus(carolDeviceId, carolIk.buffer, LimePeerDeviceStatus.UNTRUSTED);
+			assert(carolManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.UNTRUSTED);
+			assert(aliceManager.get_peerDeviceStatus(carolDeviceId) == LimePeerDeviceStatus.UNTRUSTED);
+
+			// alice and dave gets just an untrusted setting, as they do not know each other, it shall not affect their respective local storage and they would remain unknown
+			daveManager.set_peerDeviceStatus(aliceDeviceId, aliceIk.buffer, LimePeerDeviceStatus.UNTRUSTED);
+			aliceManager.set_peerDeviceStatus(daveDeviceId, daveIk.buffer, LimePeerDeviceStatus.UNTRUSTED);
+			assert(daveManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.UNKNOWN);
+			assert(aliceManager.get_peerDeviceStatus(daveDeviceId) == LimePeerDeviceStatus.UNKNOWN);
+
+
+			// Alice encrypts a message for Bob, Carol and Dave
+			RecipientData[] recipients = new RecipientData[3];
+			recipients[0] = new RecipientData(bobDeviceId);
+			recipients[1] = new RecipientData(carolDeviceId);
+			recipients[2] = new RecipientData(daveDeviceId);
+
+			LimeOutputBuffer cipherMessage = new LimeOutputBuffer();
+			aliceManager.encrypt(aliceDeviceId, "my friends group", recipients, LimeTesterUtils.patterns[0].getBytes(), cipherMessage, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success));
+
+			assert(recipients[0].getPeerStatus() == LimePeerDeviceStatus.TRUSTED); // recipient 0 is Bob: trusted
+			assert(recipients[1].getPeerStatus() == LimePeerDeviceStatus.UNTRUSTED); // recipient 1 is Carol: untrusted
+			assert(recipients[2].getPeerStatus() == LimePeerDeviceStatus.UNKNOWN); // recipient 2 is Dave: unknown
+
+			// recipients decrypt
+			LimeOutputBuffer decodedMessage = new LimeOutputBuffer();
+			// bob shall return trusted
+			assert(bobManager.decrypt(bobDeviceId, "my friends group", aliceDeviceId, recipients[0].DRmessage, cipherMessage.buffer, decodedMessage) == LimePeerDeviceStatus.TRUSTED);
+			String s = new String(decodedMessage.buffer);
+			assert (s.equals(LimeTesterUtils.patterns[0])):"Decoded message is not the encoded one";
+
+			decodedMessage = new LimeOutputBuffer();
+			// carol shall return untrusted
+			assert(carolManager.decrypt(carolDeviceId, "my friends group", aliceDeviceId, recipients[1].DRmessage, cipherMessage.buffer, decodedMessage) == LimePeerDeviceStatus.UNTRUSTED);
+			s = new String(decodedMessage.buffer);
+			assert (s.equals(LimeTesterUtils.patterns[0])):"Decoded message is not the encoded one";
+
+			decodedMessage = new LimeOutputBuffer();
+			// dave shall return unknown
+			assert(daveManager.decrypt(daveDeviceId, "my friends group", aliceDeviceId, recipients[2].DRmessage, cipherMessage.buffer, decodedMessage) == LimePeerDeviceStatus.UNKNOWN);
+			s = new String(decodedMessage.buffer);
+			assert (s.equals(LimeTesterUtils.patterns[0])):"Decoded message is not the encoded one";
+
+			// Alice encrypts a second message for Bob, Carol and Dave
+			recipients = new RecipientData[3];
+			recipients[0] = new RecipientData(bobDeviceId);
+			recipients[1] = new RecipientData(carolDeviceId);
+			recipients[2] = new RecipientData(daveDeviceId);
+
+			cipherMessage = new LimeOutputBuffer();
+			aliceManager.encrypt(aliceDeviceId, "my friends group", recipients, LimeTesterUtils.patterns[1].getBytes(), cipherMessage, statusCallback);
+			expected_success+= 1;
+			assert (statusCallback.wait_for_success(expected_success));
+
+			assert(recipients[0].getPeerStatus() == LimePeerDeviceStatus.TRUSTED); // recipient 0 is Bob: trusted
+			assert(recipients[1].getPeerStatus() == LimePeerDeviceStatus.UNTRUSTED); // recipient 1 is Carol: untrusted
+			assert(recipients[2].getPeerStatus() == LimePeerDeviceStatus.UNTRUSTED); // recipient 2 is Dave: untrusted
+
+			// recipients decrypt
+			decodedMessage = new LimeOutputBuffer();
+			// bob shall return trusted
+			assert(bobManager.decrypt(bobDeviceId, "my friends group", aliceDeviceId, recipients[0].DRmessage, cipherMessage.buffer, decodedMessage) == LimePeerDeviceStatus.TRUSTED);
+			s = new String(decodedMessage.buffer);
+			assert (s.equals(LimeTesterUtils.patterns[1])):"Decoded message is not the encoded one";
+
+			decodedMessage = new LimeOutputBuffer();
+			// carol shall return untrusted
+			assert(carolManager.decrypt(carolDeviceId, "my friends group", aliceDeviceId, recipients[1].DRmessage, cipherMessage.buffer, decodedMessage) == LimePeerDeviceStatus.UNTRUSTED);
+			s = new String(decodedMessage.buffer);
+			assert (s.equals(LimeTesterUtils.patterns[1])):"Decoded message is not the encoded one";
+
+			decodedMessage = new LimeOutputBuffer();
+			// dave shall return untrusted
+			assert(daveManager.decrypt(daveDeviceId, "my friends group", aliceDeviceId, recipients[2].DRmessage, cipherMessage.buffer, decodedMessage) == LimePeerDeviceStatus.UNTRUSTED);
+			s = new String(decodedMessage.buffer);
+			assert (s.equals(LimeTesterUtils.patterns[1])):"Decoded message is not the encoded one";
+
+			// Cleaning
+			expected_success+= 4;
+			aliceManager.delete_user(aliceDeviceId, statusCallback);
+			bobManager.delete_user(bobDeviceId, statusCallback);
+			carolManager.delete_user(carolDeviceId, statusCallback);
+			daveManager.delete_user(daveDeviceId, 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();
+			carolManager.nativeDestructor();
+			daveManager.nativeDestructor();
+			aliceManager = null;
+			bobManager = null;
+			carolManager = null;
+			daveManager = null;
+		}
+		catch (LimeException e) {
+			assert(false):"Got an unexpected exception during Multidev operation queue test : "+e.getMessage();
+		}
+
+		// Remove database files
+		file = new File(aliceDbFilename);
+		file.delete();
+		file = new File(bobDbFilename);
+		file.delete();
+		file = new File(carolDbFilename);
+		file.delete();
+		file = new File(daveDbFilename);
+		file.delete();
+
+	}
+
+	/*
+	 * Scenario: Bob encrypt a message to Alice device 1 and 2 using given encryptionPolicy
+	 *
+	 * parameters allow to control:
+	 *  - plaintext message
+	 *  - number of recipients (1 or 2)
+	 *  - forced encryption policy(with a bool switch)
+	 *  - expected message type (must be DRMessage or cipherMessage)
+	 */
+	private static void encryptionPolicy(LimeManager aliceManager, String aliceDevice1Id, String aliceDevice2Id,
+			LimeManager bobManager, String bobDeviceId,
+			String plainMessage, boolean multipleRecipients,
+			LimeEncryptionPolicy setEncryptionPolicy, boolean forceEncryptionPolicy,
+			LimeEncryptionPolicy getEncryptionPolicy) {
+
+		int expected_success = 0;
+		int expected_fail = 0;
+
+		LimeTesterUtils TesterUtils = new LimeTesterUtils();
+
+		// Create a callback, this one will be used for all operations
+		LimeStatusCallbackImpl statusCallback = new LimeStatusCallbackImpl();
+
+
+		// bob encrypt a message to Alice devices 1 (and 2 if setting says so)
+		RecipientData[] recipients;
+		if (multipleRecipients) {
+			recipients = new RecipientData[2];
+			recipients[0] = new RecipientData(aliceDevice1Id);
+			recipients[1] = new RecipientData(aliceDevice2Id);
+		} else {
+			recipients = new RecipientData[1];
+			recipients[0] = new RecipientData(aliceDevice1Id);
+		}
 
 		LimeOutputBuffer cipherMessage = new LimeOutputBuffer();
-		aliceManager.encrypt(AliceDeviceId, "bob", recipients, LimeTesterUtils.patterns[0].getBytes(), cipherMessage, statusCallback);
+
+		if (forceEncryptionPolicy) {
+			bobManager.encrypt(bobDeviceId, "alice", recipients, plainMessage.getBytes(), cipherMessage, statusCallback, setEncryptionPolicy);
+		} else {
+			bobManager.encrypt(bobDeviceId, "alice", recipients, plainMessage.getBytes(), cipherMessage, statusCallback);
+		}
 		expected_success+= 1;
 		assert (statusCallback.wait_for_success(expected_success));
-		assert (recipients[0].getPeerStatus() == LimePeerDeviceStatus.FAIL); // the device is unknown, so it shall fail
 
-		// Alice sends a message to bob 3 devices, one is non existent
-		recipients = new RecipientData[3];
-		recipients[0] = new RecipientData(BobDeviceId1);
-		recipients[1] = new RecipientData(BobDeviceId2);
-		recipients[2] = new RecipientData(BobDeviceId3);
+		boolean is_directEncryptionType = TesterUtils.DR_message_payloadDirectEncrypt(recipients[0].DRmessage);
+		if (multipleRecipients) {
+			// all cipher header must have the same message type
+			assert(is_directEncryptionType == TesterUtils.DR_message_payloadDirectEncrypt(recipients[1].DRmessage));
+		}
 
-		cipherMessage = new LimeOutputBuffer();
-		aliceManager.encrypt(AliceDeviceId, "bob", recipients, LimeTesterUtils.patterns[0].getBytes(), cipherMessage, statusCallback);
-		expected_success+= 1;
-		assert (statusCallback.wait_for_success(expected_success));
+		if (getEncryptionPolicy == LimeEncryptionPolicy.DRMESSAGE) {
+			assert(is_directEncryptionType);
+			assert(cipherMessage.buffer.length == 0); // in direct Encryption mode, cipherMessage is empty
+		} else {
+			assert(is_directEncryptionType == false);
+			assert(cipherMessage.buffer.length > 0); // in direct cipher message mode, cipherMessage is not empty
+		}
 
-		// decrypt with bob Manager
-		// bob.d1
+		// alice1 decrypt
 		LimeOutputBuffer decodedMessage = new LimeOutputBuffer();
-		assert(TesterUtils.DR_message_holdsX3DHInit(recipients[0].DRmessage).holdsX3DHInit);  // new sessions created, they must convey X3DH init message
-		assert(bobManager.decrypt(recipients[0].deviceId, "bob", AliceDeviceId, recipients[0].DRmessage, cipherMessage.buffer, decodedMessage) != LimePeerDeviceStatus.FAIL);
+		if (is_directEncryptionType) { // when having the message in DR message only, use the decrypt interface without cipherMessage
+			assert(aliceManager.decrypt(aliceDevice1Id, "alice", bobDeviceId, recipients[0].DRmessage, decodedMessage) != LimePeerDeviceStatus.FAIL);
+		} else {
+			assert(aliceManager.decrypt(aliceDevice1Id, "alice", bobDeviceId, recipients[0].DRmessage, cipherMessage.buffer, decodedMessage) != LimePeerDeviceStatus.FAIL);
+		}
+
 		String s = new String(decodedMessage.buffer);
-		assert (s.equals(LimeTesterUtils.patterns[0])):"Decoded message is not the encoded one";
-
-		// bob.d2
-		decodedMessage = new LimeOutputBuffer();
-		assert(TesterUtils.DR_message_holdsX3DHInit(recipients[1].DRmessage).holdsX3DHInit);  // new sessions created, they must convey X3DH init message
-		assert(bobManager.decrypt(recipients[1].deviceId, "bob", AliceDeviceId, recipients[1].DRmessage, cipherMessage.buffer, decodedMessage) != LimePeerDeviceStatus.FAIL);
-		s = new String(decodedMessage.buffer);
-		assert (s.equals(LimeTesterUtils.patterns[0])):"Decoded message is not the encoded one";
-
-		// bob.d3
-		assert (recipients[2].getPeerStatus() == LimePeerDeviceStatus.FAIL); // the device is unknown, so it shall fail
-		assert (recipients[2].DRmessage.length == 0); // encrypt produced no DRmessage for non existent device
-
-		// clean db: delete users
-		expected_success+= 3;
-		aliceManager.delete_user(AliceDeviceId, statusCallback);
-		bobManager.delete_user(BobDeviceId1, statusCallback);
-		bobManager.delete_user(BobDeviceId2, statusCallback);
-		assert (statusCallback.wait_for_success(expected_success));
-		assert (statusCallback.fail == expected_fail);
+		assert (s.equals(plainMessage)):"Decoded message is not the encoded one";
 
-		// Do not forget do deallocate the native ressources
-		aliceManager.nativeDestructor();
-		bobManager.nativeDestructor();
-		aliceManager = null;
-		bobManager = null;
+		if (multipleRecipients) {
+			// alice2 decrypt
+			decodedMessage = new LimeOutputBuffer();
+			assert(aliceManager.decrypt(aliceDevice2Id, "alice", bobDeviceId, recipients[1].DRmessage, cipherMessage.buffer, decodedMessage) != LimePeerDeviceStatus.FAIL);
+			s = new String(decodedMessage.buffer);
+			assert (s.equals(plainMessage)):"Decoded message is not the encoded one";
+		}
+	}
+
+	public static void encryptionPolicy_suite(LimeCurveId curveId, String dbBasename, String x3dhServerUrl,  LimePostToX3DH postObj) {
+		int expected_success = 0;
+		int expected_fail = 0;
+
+		LimeTesterUtils TesterUtils = new LimeTesterUtils();
+
+		// Create a callback, this one will be used for all operations
+		LimeStatusCallbackImpl statusCallback = new LimeStatusCallbackImpl();
+
+		// 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();
+
+		try {
+			// Create random device ids
+			String aliceDevice1Id = "alice.d1."+UUID.randomUUID().toString();
+			String aliceDevice2Id = "alice.d2."+UUID.randomUUID().toString();
+			String bobDeviceId = "bob."+UUID.randomUUID().toString();
+
+			// create Manager and devices
+			LimeManager aliceManager = new LimeManager(aliceDbFilename, postObj);
+			aliceManager.create_user(aliceDevice1Id, x3dhServerUrl, curveId, 10, statusCallback);
+			assert (statusCallback.wait_for_success(++expected_success));
+			aliceManager.create_user(aliceDevice2Id, x3dhServerUrl, curveId, 10, statusCallback);
+			assert (statusCallback.wait_for_success(++expected_success));
+
+			LimeManager bobManager = new LimeManager(bobDbFilename, postObj);
+			bobManager.create_user(bobDeviceId, x3dhServerUrl, curveId, 10, statusCallback);
+			assert (statusCallback.wait_for_success(++expected_success));
+
+			// Bob encrypts to alice and we check result
+			// Short messages
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.shortMessage, false, // single recipient
+					LimeEncryptionPolicy.OPTIMIZEUPLOADSIZE, false, // default policy(->optimizeUploadSize)
+					LimeEncryptionPolicy.DRMESSAGE); // -> DRMessage
+
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.shortMessage, false, // single recipient
+					LimeEncryptionPolicy.OPTIMIZEUPLOADSIZE, true, // force to optimizeUploadSize
+					LimeEncryptionPolicy.DRMESSAGE); // -> DRMessage
+
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.shortMessage, false, // single recipient
+					LimeEncryptionPolicy.OPTIMIZEGLOBALBANDWIDTH, true, // force to optimizeGlobalBandwidth
+					LimeEncryptionPolicy.DRMESSAGE); // -> DRMessage
+
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.shortMessage, false, // single recipient
+					LimeEncryptionPolicy.DRMESSAGE, true, // force to DRmessage
+					LimeEncryptionPolicy.DRMESSAGE); // -> DRMessage
+
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.shortMessage, false, // single recipient
+					LimeEncryptionPolicy.CIPHERMESSAGE, true, // force to cipher message
+					LimeEncryptionPolicy.CIPHERMESSAGE); // -> cipher message
+
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.shortMessage, true, // multiple recipients
+					LimeEncryptionPolicy.OPTIMIZEUPLOADSIZE, false, // default policy(->optimizeUploadSize)
+					LimeEncryptionPolicy.DRMESSAGE); // -> DRMessage
+
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.shortMessage, true, // multiple recipient
+					LimeEncryptionPolicy.OPTIMIZEUPLOADSIZE, true, // force to optimizeUploadSize
+					LimeEncryptionPolicy.DRMESSAGE); // -> DRMessage
+
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.shortMessage, true, // multiple recipient
+					LimeEncryptionPolicy.OPTIMIZEGLOBALBANDWIDTH, true, // force to optimizeGlobalBandwidth
+					LimeEncryptionPolicy.DRMESSAGE); // -> DRMessage
+
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.shortMessage, true, // multiple recipient
+					LimeEncryptionPolicy.DRMESSAGE, true, // force to DRmessage
+					LimeEncryptionPolicy.DRMESSAGE); // -> DRMessage
+
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.shortMessage, true, // multiple recipient
+					LimeEncryptionPolicy.CIPHERMESSAGE, true, // force to cipher message
+					LimeEncryptionPolicy.CIPHERMESSAGE); // -> cipher message
+
+			// Long or veryLong messages
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.longMessage, false, // single recipient
+					LimeEncryptionPolicy.OPTIMIZEUPLOADSIZE, false, // default policy(->optimizeUploadSize)
+					LimeEncryptionPolicy.DRMESSAGE); // -> DRMessage
+
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.longMessage, false, // single recipient
+					LimeEncryptionPolicy.OPTIMIZEUPLOADSIZE, true, // force optimizeUploadSize
+					LimeEncryptionPolicy.DRMESSAGE); // -> DRMessage
+
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.longMessage, false, // single recipient
+					LimeEncryptionPolicy.OPTIMIZEGLOBALBANDWIDTH, true, // force optimizeGlobalBandwidth
+					LimeEncryptionPolicy.DRMESSAGE); // -> DRMessage
+
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.longMessage, false, // single recipient
+					LimeEncryptionPolicy.DRMESSAGE, true, // force to DRmessage
+					LimeEncryptionPolicy.DRMESSAGE); // -> DRMessage
+
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.longMessage, false, // single recipient
+					LimeEncryptionPolicy.CIPHERMESSAGE, true, // force to cipher message
+					LimeEncryptionPolicy.CIPHERMESSAGE); // -> cipher message
+
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.longMessage, true, // multiple recipient
+					LimeEncryptionPolicy.OPTIMIZEUPLOADSIZE, false, // default policy(->optimizeUploadSize)
+					LimeEncryptionPolicy.CIPHERMESSAGE); // -> cipher message
+
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.longMessage, true, // multiple recipient
+					LimeEncryptionPolicy.OPTIMIZEUPLOADSIZE, true, // force optimizeUploadSize
+					LimeEncryptionPolicy.CIPHERMESSAGE); // -> cipher message
+
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.longMessage, true, // multiple recipient
+					LimeEncryptionPolicy.OPTIMIZEGLOBALBANDWIDTH, true, // force optimizeGlobalBandwidth
+					LimeEncryptionPolicy.DRMESSAGE); // -> DRMessage (we need a very long message to switch to cipherMessage with that setting)
+
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.veryLongMessage, true, // multiple recipient
+					LimeEncryptionPolicy.OPTIMIZEGLOBALBANDWIDTH, true, // force optimizeGlobalBandwidth
+					LimeEncryptionPolicy.CIPHERMESSAGE); // -> cipherMessage (we need a very long message to switch to cipherMessage with that setting)
+
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.longMessage, true, // multiple recipient
+					LimeEncryptionPolicy.DRMESSAGE, true, // force to DRmessage
+					LimeEncryptionPolicy.DRMESSAGE); // -> DRMessage
+
+			encryptionPolicy(aliceManager, aliceDevice1Id, aliceDevice2Id,
+					bobManager, bobDeviceId,
+					LimeTesterUtils.longMessage, true, // multiple recipient
+					LimeEncryptionPolicy.CIPHERMESSAGE, true, // force to cipher message
+					LimeEncryptionPolicy.CIPHERMESSAGE); // -> cipher message
+
+			// Cleaning
+			expected_success+= 3;
+			aliceManager.delete_user(aliceDevice1Id, statusCallback);
+			aliceManager.delete_user(aliceDevice2Id, 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;
+		}
+		catch (LimeException e) {
+			assert(false):"Got an unexpected exception during Multidev operation queue test : "+e.getMessage();
+		}
+
+		// Remove database files
+		file = new File(aliceDbFilename);
+		file.delete();
+		file = new File(bobDbFilename);
+		file.delete();
+	}
+	/*Test Scenario:
+	 * - create Bob and Alice devices
+	 * - retrieve their respective Identity keys
+	 * - check if they are verified -> they shall not be
+	 * - set alice key as verified in bob's context
+	 * - check it is now verified
+	 * - set it to unsafe and check
+	 * - set it as non verified and check
+	 * - set it to unsafe and then untrusted using the alternative API without giving the Ik
+	 * - try to set it to trusted using the API without Ik, we shall have and exception
+	 * - try to set it to unknown, we shall have and exception
+	 * - try to set it to fail, we shall have and exception
+	 * - try to set a different alice identity key in bob's context, we shall have an exception
+	 * - bob encrypts a message to alice -> check return status give NOT all recipients trusted
+	 * - set alice key as verified in bob's context
+	 * - bob encrypts a message to alice -> check return status give all recipients trusted
+	 * - set a fake bob key in alice context
+	 * - try to decrypt bob's message, it shall fail
+	 * - alice try to encrypt a message to bob, it shall fail
+	 */
+	public static void identityVerifiedStatus(LimeCurveId curveId, String dbBasename, String x3dhServerUrl,  LimePostToX3DH postObj) {
+		int expected_success = 0;
+		int expected_fail = 0;
+
+		LimeTesterUtils TesterUtils = new LimeTesterUtils();
+
+		// Create a callback, this one will be used for all operations
+		LimeStatusCallbackImpl statusCallback = new LimeStatusCallbackImpl();
+
+		// 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 random device ids
+		String aliceDeviceId = "alice."+UUID.randomUUID().toString();
+		String bobDeviceId = "bob."+UUID.randomUUID().toString();
+
+		LimeManager aliceManager = new LimeManager(aliceDbFilename, postObj);
+		LimeManager bobManager = new LimeManager(bobDbFilename, postObj);
+
+		LimeOutputBuffer aliceIk = new LimeOutputBuffer();
+		LimeOutputBuffer bobIk = new LimeOutputBuffer();
+		LimeOutputBuffer fakeIk = new LimeOutputBuffer();
+
+		try {
+			// create devices
+			aliceManager.create_user(aliceDeviceId, x3dhServerUrl, curveId, 10, statusCallback);
+			assert (statusCallback.wait_for_success(++expected_success));
+			bobManager.create_user(bobDeviceId, x3dhServerUrl, curveId, 10, statusCallback);
+			assert (statusCallback.wait_for_success(++expected_success));
+
+
+			// retrieve their respective Ik
+			aliceManager.get_selfIdentityKey(aliceDeviceId, aliceIk);
+			bobManager.get_selfIdentityKey(bobDeviceId, bobIk);
+
+			// build the fake alice Ik
+			aliceManager.get_selfIdentityKey(aliceDeviceId, fakeIk);
+			fakeIk.buffer[0] ^= 0xFF;
+
+			// check their status: they don't know each other
+			assert(aliceManager.get_peerDeviceStatus(bobDeviceId) == LimePeerDeviceStatus.UNKNOWN);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.UNKNOWN);
+
+			// set alice Id key as verified in Bob's Manager and check it worked
+			bobManager.set_peerDeviceStatus(aliceDeviceId, aliceIk.buffer, LimePeerDeviceStatus.TRUSTED);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.TRUSTED);
+			// set it to unsafe and check it worked
+			bobManager.set_peerDeviceStatus(aliceDeviceId, aliceIk.buffer, LimePeerDeviceStatus.UNSAFE);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.UNSAFE);
+
+			// reset it to untrusted and check it is still unsafe : we can escape unsafe only by setting to safe
+			bobManager.set_peerDeviceStatus(aliceDeviceId, aliceIk.buffer, LimePeerDeviceStatus.UNTRUSTED);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.UNSAFE);
+
+			// set alice Id key as verified and check it this time it worked
+			bobManager.set_peerDeviceStatus(aliceDeviceId, aliceIk.buffer, LimePeerDeviceStatus.TRUSTED);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.TRUSTED);
+
+			// set to untrusted without using alice Ik
+			bobManager.set_peerDeviceStatus(aliceDeviceId, LimePeerDeviceStatus.UNTRUSTED);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.UNTRUSTED);
+
+			// set to unsafe without using alice Ik
+			bobManager.set_peerDeviceStatus(aliceDeviceId, LimePeerDeviceStatus.UNSAFE);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.UNSAFE);
+
+			// try to set it to trusted without giving the Ik, it shall be ignored
+			bobManager.set_peerDeviceStatus(aliceDeviceId, LimePeerDeviceStatus.TRUSTED);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.UNSAFE);
+
+			// set it back to trusted and check it this time it worked
+			bobManager.set_peerDeviceStatus(aliceDeviceId, aliceIk.buffer, LimePeerDeviceStatus.TRUSTED);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.TRUSTED);
+
+			// try to set it to unknown, it shall be ignored
+			bobManager.set_peerDeviceStatus(aliceDeviceId, aliceIk.buffer, LimePeerDeviceStatus.UNKNOWN);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.TRUSTED);
+
+			// try to set it to fail, it shall be ignored
+			bobManager.set_peerDeviceStatus(aliceDeviceId, aliceIk.buffer, LimePeerDeviceStatus.FAIL);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.TRUSTED);
+
+			// try to set another key for alice in bob's context, setting it to untrusted, it shall be ok as the Ik is ignored when setting to untrusted
+			bobManager.set_peerDeviceStatus(aliceDeviceId, fakeIk.buffer, LimePeerDeviceStatus.UNTRUSTED);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.UNTRUSTED);
+
+			// same goes for unsafe
+			bobManager.set_peerDeviceStatus(aliceDeviceId, fakeIk.buffer, LimePeerDeviceStatus.UNSAFE);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.UNSAFE);
+
+			// set it back to trusted with the real key, it shall be Ok as it is still the one present in local storage
+			bobManager.set_peerDeviceStatus(aliceDeviceId, aliceIk.buffer, LimePeerDeviceStatus.TRUSTED);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.TRUSTED);
+		}
+		catch (LimeException e) {
+			assert(false):"Got an unexpected exception during Multidev operation queue test : "+e.getMessage();
+		}
+
+		boolean gotException = false;
+
+		try {
+			// try to set another key for alice in bob's context, it shall generate an exception
+			bobManager.set_peerDeviceStatus(aliceDeviceId, fakeIk.buffer, LimePeerDeviceStatus.TRUSTED);
+		} catch (LimeException e) {
+			gotException = true;
+		}
+
+		assert(gotException);
+		gotException = false;
+
+		try {
+			// Now delete the alice device from Bob's cache and check its status is now back to unknown
+			bobManager.delete_peerDevice(aliceDeviceId);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.UNKNOWN);
+
+			// set the device with the fake Ik but to untrusted so it won't be actually stored and shall still be unknown
+			bobManager.set_peerDeviceStatus(aliceDeviceId, fakeIk.buffer, LimePeerDeviceStatus.UNTRUSTED);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.UNKNOWN);
+
+			// set the device with the fake Ik but to unsafe so the key shall not be registered in base but the user will
+			bobManager.set_peerDeviceStatus(aliceDeviceId, fakeIk.buffer, LimePeerDeviceStatus.UNSAFE);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.UNSAFE);
+		}
+		catch (LimeException e) {
+			assert(false):"Got an unexpected exception during Multidev operation queue test : "+e.getMessage();
+		}
+
+		try {
+			// try to set it to trusted, still using the fake Ik, it shall generate an exception as the Ik is invalid in storage
+			bobManager.set_peerDeviceStatus(aliceDeviceId, fakeIk.buffer, LimePeerDeviceStatus.TRUSTED);
+		} catch (LimeException e) {
+			gotException = true;
+		}
+
+		assert(gotException);
+		gotException = false;
+
+		try {
+			// same than above but using the actual key : try to set it to trusted, still using the fake Ik, it shall generate an exception as the Ik is invalid in storage
+			bobManager.set_peerDeviceStatus(aliceDeviceId, aliceIk.buffer, LimePeerDeviceStatus.TRUSTED);
+		} catch (LimeException e) {
+			gotException = true;
+
+			// Now delete the alice device from Bob's cache and check its status is now back to unknown
+			bobManager.delete_peerDevice(aliceDeviceId);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.UNKNOWN);
+		}
+
+		assert(gotException);
+		gotException = false;
+
+
+
+
+
+
+		try {
+			// Bob encrypts a message for Alice, alice device status shall be : unknown(it is the first message bob sends and alice is not in cache)
+			RecipientData[] recipients = new RecipientData[1];
+			recipients[0] = new RecipientData(aliceDeviceId);
+			LimeOutputBuffer cipherMessage = new LimeOutputBuffer();
+			bobManager.encrypt(bobDeviceId, "alice", recipients, LimeTesterUtils.patterns[0].getBytes(), cipherMessage, statusCallback);
+			assert (statusCallback.wait_for_success(++expected_success));
+			assert(recipients[0].getPeerStatus() == LimePeerDeviceStatus.UNKNOWN);
+
+			// Bob encrypts a second message for Alice, alice device status shall now be : untrusted(we know that device but didn't share the trust yet)
+			recipients = new RecipientData[1];
+			recipients[0] = new RecipientData(aliceDeviceId);
+			cipherMessage = new LimeOutputBuffer();
+			bobManager.encrypt(bobDeviceId, "alice", recipients, LimeTesterUtils.patterns[1].getBytes(), cipherMessage, statusCallback);
+			assert (statusCallback.wait_for_success(++expected_success));
+			assert(recipients[0].getPeerStatus() == LimePeerDeviceStatus.UNTRUSTED);
+
+			// set again the key as verified in bob's context
+			bobManager.set_peerDeviceStatus(aliceDeviceId, aliceIk.buffer, LimePeerDeviceStatus.TRUSTED);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.TRUSTED);
+
+			// Bob encrypts a message for Alice, alice device status shall now be : trusted
+			recipients = new RecipientData[1];
+			recipients[0] = new RecipientData(aliceDeviceId);
+			cipherMessage = new LimeOutputBuffer();
+			bobManager.encrypt(bobDeviceId, "alice", recipients, LimeTesterUtils.patterns[2].getBytes(), cipherMessage, statusCallback);
+			assert (statusCallback.wait_for_success(++expected_success));
+			assert(recipients[0].getPeerStatus() == LimePeerDeviceStatus.TRUSTED);
+
+			// set a fake bob key in alice context(set is as verified otherwise the request is just ignored)
+			bobManager.get_selfIdentityKey(bobDeviceId, fakeIk);
+			fakeIk.buffer[0] ^= 0xFF;
+			aliceManager.set_peerDeviceStatus(bobDeviceId, fakeIk.buffer, LimePeerDeviceStatus.TRUSTED);
+
+			// alice decrypt but it will fail as the identity key in X3DH init packet is not matching the one we assert as verified
+			LimeOutputBuffer decodedMessage = new LimeOutputBuffer();
+			assert(aliceManager.decrypt(aliceDeviceId, "alice", bobDeviceId, recipients[0].DRmessage, cipherMessage.buffer, decodedMessage) == LimePeerDeviceStatus.FAIL);
+
+			// alice now try to encrypt to Bob but it will fail as key fetched from X3DH server won't match the one we assert as verified
+			recipients = new RecipientData[1];
+			recipients[0] = new RecipientData(bobDeviceId);
+			cipherMessage = new LimeOutputBuffer();
+			aliceManager.encrypt(aliceDeviceId, "bob", recipients, LimeTesterUtils.patterns[3].getBytes(), cipherMessage, statusCallback);
+			assert (statusCallback.wait_for_fail(++expected_fail));
+
+			// delete bob's key from alice context and just set it to unsafe, he will get then no Ik in local storage
+			aliceManager.delete_peerDevice(bobDeviceId);
+			assert(aliceManager.get_peerDeviceStatus(bobDeviceId) == LimePeerDeviceStatus.UNKNOWN);
+			aliceManager.set_peerDeviceStatus(bobDeviceId, LimePeerDeviceStatus.UNSAFE);
+			assert(aliceManager.get_peerDeviceStatus(bobDeviceId) == LimePeerDeviceStatus.UNSAFE);
+
+			// delete alice's key from Bob context, it will delete all session associated to Alice so when we encrypt a new message, it will fetch a new OPk as the previous one was deleted by alice
+			bobManager.delete_peerDevice(aliceDeviceId);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.UNKNOWN);
+
+			// Bob encrypts a message for Alice, alice device status shall be : unknown(it is the first message bob sends and alice is not in cache)
+			recipients = new RecipientData[1];
+			recipients[0] = new RecipientData(aliceDeviceId);
+			cipherMessage = new LimeOutputBuffer();
+			bobManager.encrypt(bobDeviceId, "alice", recipients, LimeTesterUtils.patterns[4].getBytes(), cipherMessage, statusCallback);
+			assert (statusCallback.wait_for_success(++expected_success));
+			assert(recipients[0].getPeerStatus() == LimePeerDeviceStatus.UNKNOWN);
+
+			// alice tries again to decrypt but it will fail as the identity key in X3DH init packet is not matching the invalid one set in base by setting its status to unsafe
+			decodedMessage = new LimeOutputBuffer();
+			assert(aliceManager.decrypt(aliceDeviceId, "alice", bobDeviceId, recipients[0].DRmessage, cipherMessage.buffer, decodedMessage) == LimePeerDeviceStatus.FAIL);
+
+			// alice fails again to encrypt to Bob as key fetched from X3DH server won't match the one we assert as verified
+			recipients = new RecipientData[1];
+			recipients[0] = new RecipientData(bobDeviceId);
+			cipherMessage = new LimeOutputBuffer();
+			aliceManager.encrypt(aliceDeviceId, "bob", recipients, LimeTesterUtils.patterns[5].getBytes(), cipherMessage, statusCallback);
+			assert (statusCallback.wait_for_fail(++expected_fail));
+
+			// delete bob's key from alice context
+			aliceManager.delete_peerDevice(bobDeviceId);
+			assert(aliceManager.get_peerDeviceStatus(bobDeviceId) == LimePeerDeviceStatus.UNKNOWN);
+
+			// delete alice's key from Bob context, it will delete all session associated to Alice so when we encrypt a new message, it will fetch a new OPk as the previous one was deleted by alice
+			bobManager.delete_peerDevice(aliceDeviceId);
+			assert(bobManager.get_peerDeviceStatus(aliceDeviceId) == LimePeerDeviceStatus.UNKNOWN);
+
+			// Bob encrypts a message for Alice, alice device status shall be : unknown(it is the first message bob sends and alice is not in cache)
+			recipients = new RecipientData[1];
+			recipients[0] = new RecipientData(aliceDeviceId);
+			cipherMessage = new LimeOutputBuffer();
+			bobManager.encrypt(bobDeviceId, "alice", recipients, LimeTesterUtils.patterns[6].getBytes(), cipherMessage, statusCallback);
+			assert (statusCallback.wait_for_success(++expected_success));
+			assert(recipients[0].getPeerStatus() == LimePeerDeviceStatus.UNKNOWN);
+
+			// alice tries again to decrypt but it shall work and return status unknown as we just deleted bob's device
+			decodedMessage = new LimeOutputBuffer();
+			assert(aliceManager.decrypt(aliceDeviceId, "alice", bobDeviceId, recipients[0].DRmessage, cipherMessage.buffer, decodedMessage) == LimePeerDeviceStatus.UNKNOWN);
+			String s = new String(decodedMessage.buffer);
+			assert (s.equals(LimeTesterUtils.patterns[6])):"Decoded message is not the encoded one";
+
+			// now set bob's to trusted in alice cache, it shall work as key retrieved from X3DH init message during decryption match the one we're giving
+			aliceManager.set_peerDeviceStatus(bobDeviceId, bobIk.buffer, LimePeerDeviceStatus.TRUSTED);
+			assert(aliceManager.get_peerDeviceStatus(bobDeviceId) == LimePeerDeviceStatus.TRUSTED);
+
+			// Cleaning
+			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;
+		}
+		catch (LimeException e) {
+			assert(false):"Got an unexpected exception during Multidev operation queue test : "+e.getMessage();
+		}
 
 		// Remove database files
 		file = new File(aliceDbFilename);
diff --git a/tester/java/org/linphone/limeTester/LimeTester.java b/tester/java/org/linphone/limeTester/LimeTester.java
index 7c31a7f..1fe99b4 100644
--- a/tester/java/org/linphone/limeTester/LimeTester.java
+++ b/tester/java/org/linphone/limeTester/LimeTester.java
@@ -56,6 +56,21 @@ public class LimeTester {
 			System.out.println("Exception while initializing the trustAll certificates" + e.getMessage());
 			System.exit(-1);
 		}
+
+		// Get system properties to check if we enabled curves 25519 and/or 448
+		// default is disabling the tests
+		String SPenableC25519 = System.getProperty("enableC25519");
+		String SPenableC448 = System.getProperty("enableC448");
+		boolean enableC25519 = false;
+		boolean enableC448 = false;
+		if (SPenableC25519 != null) {
+			enableC25519 = SPenableC25519.equals("true");
+		}
+		if (SPenableC448 != null) {
+			enableC448 = SPenableC448.equals("true");
+		}
+		System.out.println("Tests enabled on curve 25519 "+enableC25519);
+		System.out.println("Tests enabled on curve 448 "+enableC448);
 		// End of INSECURE CODE - USED FOR TESTING ONLY - DO NOT USE THIS IN REAL SITUATION
 
 		/*
@@ -66,27 +81,63 @@ public class LimeTester {
 		 * HelloWorld code is meant to be run with asynchronous X3DH server access but is anyway able to run synchronous.
 		 */
 		LimePostToX3DH_Sync sync_postObj = new LimePostToX3DH_Sync();
-		HelloWorld.hello_world(LimeCurveId.C25519, "hello_world", "https://localhost:25519", sync_postObj);
-		HelloWorld.hello_world(LimeCurveId.C448, "hello_world", "https://localhost:25520", sync_postObj);
+		if (enableC25519) HelloWorld.hello_world(LimeCurveId.C25519, "hello_world", "https://localhost:25519", sync_postObj);
+		if (enableC448) HelloWorld.hello_world(LimeCurveId.C448, "hello_world", "https://localhost:25520", sync_postObj);
 
 		LimePostToX3DH_Async async_postObj = new LimePostToX3DH_Async();
-		HelloWorld.hello_world(LimeCurveId.C25519, "hello_world", "https://localhost:25519", async_postObj);
-		HelloWorld.hello_world(LimeCurveId.C448, "hello_world", "https://localhost:25520", async_postObj);
+		if (enableC25519) HelloWorld.hello_world(LimeCurveId.C25519, "hello_world", "https://localhost:25519", async_postObj);
+		if (enableC448) HelloWorld.hello_world(LimeCurveId.C448, "hello_world", "https://localhost:25520", async_postObj);
+
+		/*
+		 * Lime user management test
+		 */
+		if (enableC25519) LimeLimeTester.user_management(LimeCurveId.C25519, "lime_user_management", "https://localhost:25519", async_postObj);
+		if (enableC448) LimeLimeTester.user_management(LimeCurveId.C448, "lime_user_management", "https://localhost:25520", async_postObj);
 
 		/*
 		 * Lime Basic test
 		 */
-		LimeLimeTester.basic(LimeCurveId.C25519, "lime_basic", "https://localhost:25519", async_postObj);
-		LimeLimeTester.basic(LimeCurveId.C448, "lime_basic", "https://localhost:25520", async_postObj);
+		if (enableC25519) LimeLimeTester.basic(LimeCurveId.C25519, "lime_basic", "https://localhost:25519", async_postObj);
+		if (enableC448) LimeLimeTester.basic(LimeCurveId.C448, "lime_basic", "https://localhost:25520", async_postObj);
 
 		/*
 		 * Lime User not found test
 		 */
-		LimeLimeTester.user_not_found(LimeCurveId.C25519, "lime_basic", "https://localhost:25519", async_postObj);
-		LimeLimeTester.user_not_found(LimeCurveId.C448, "lime_basic", "https://localhost:25520", async_postObj);
+		if (enableC25519) LimeLimeTester.user_not_found(LimeCurveId.C25519, "lime_user_not_found", "https://localhost:25519", async_postObj);
+		if (enableC448) LimeLimeTester.user_not_found(LimeCurveId.C448, "lime_user_not_found", "https://localhost:25520", async_postObj);
 
+		/*
+		 * Lime multidevice operation queue
+		 */
+		if (enableC25519) LimeLimeTester.x3dh_multidev_operation_queue(LimeCurveId.C25519, "lime_multidev_operation_queue", "https://localhost:25519", async_postObj);
+		if (enableC448) LimeLimeTester.x3dh_multidev_operation_queue(LimeCurveId.C448, "lime_multidev_operation_queue", "https://localhost:25520", async_postObj);
 
 
+		/*
+		 * Lime getSelf Ik
+		 */
+		byte[] pattern_selfIk_C25519 = {(byte)0x55, (byte)0x6b, (byte)0x4a, (byte)0xc2, (byte)0x24, (byte)0xc1, (byte)0xd4, (byte)0xff, (byte)0xb7, (byte)0x44, (byte)0x82, (byte)0xe2, (byte)0x3c, (byte)0x75, (byte)0x1c, (byte)0x2b, (byte)0x1c, (byte)0xcb, (byte)0xf6, (byte)0xe2, (byte)0x96, (byte)0xcb, (byte)0x18, (byte)0x01, (byte)0xc6, (byte)0x76, (byte)0x2d, (byte)0x30, (byte)0xa0, (byte)0xa2, (byte)0xbb, (byte)0x27};
+		if (enableC25519) LimeLimeTester.getSelfIk(LimeCurveId.C25519, "pattern_getSelfIk.C25519.sqlite3", async_postObj, pattern_selfIk_C25519);
+		byte[] pattern_selfIk_C448 = {(byte)0xe7, (byte)0x96, (byte)0x9e, (byte)0x53, (byte)0xd3, (byte)0xbf, (byte)0xfb, (byte)0x4c, (byte)0x6d, (byte)0xdb, (byte)0x79, (byte)0xd2, (byte)0xd7, (byte)0x24, (byte)0x91, (byte)0x7b, (byte)0xa8, (byte)0x99, (byte)0x87, (byte)0x20, (byte)0x23, (byte)0xe1, (byte)0xec, (byte)0xd4, (byte)0xb5, (byte)0x76, (byte)0x0f, (byte)0xc2, (byte)0x83, (byte)0xae, (byte)0x5a, (byte)0xf9, (byte)0x1d, (byte)0x25, (byte)0x47, (byte)0xda, (byte)0x0e, (byte)0x71, (byte)0x50, (byte)0xd5, (byte)0xaf, (byte)0x79, (byte)0x92, (byte)0x48, (byte)0xb0, (byte)0xb6, (byte)0x0f, (byte)0xdc, (byte)0x6f, (byte)0x73, (byte)0x3f, (byte)0xd9, (byte)0x9c, (byte)0x2c, (byte)0x95, (byte)0xe3, (byte)0x00};
+		if (enableC448) LimeLimeTester.getSelfIk(LimeCurveId.C448, "pattern_getSelfIk.C448.sqlite3", async_postObj, pattern_selfIk_C448);
+
+		/*
+		 * Lime peer device status
+		 */
+		if (enableC25519) LimeLimeTester.peerDeviceStatus(LimeCurveId.C25519, "lime_peerDeviceStatus", "https://localhost:25519", async_postObj);
+		if (enableC448) LimeLimeTester.peerDeviceStatus(LimeCurveId.C448, "lime_peerDeviceStatus", "https://localhost:25520", async_postObj);
+
+		/*
+		 * Lime encryption policy
+		 */
+		if (enableC25519) LimeLimeTester.encryptionPolicy_suite(LimeCurveId.C25519, "lime_encryptioPolicy", "https://localhost:25519", async_postObj);
+		if (enableC448) LimeLimeTester.encryptionPolicy_suite(LimeCurveId.C448, "lime_encryptioPolicy", "https://localhost:25520", async_postObj);
+
+		/*
+		 * Lime identity verified status
+		 */
+		if (enableC25519) LimeLimeTester.identityVerifiedStatus(LimeCurveId.C25519, "lime_identityVerifiedStatus", "https://localhost:25519", async_postObj);
+		if (enableC448) LimeLimeTester.identityVerifiedStatus(LimeCurveId.C448, "lime_identityVerifiedStatus", "https://localhost:25520", async_postObj);
 
 		System.exit(0);
 	}
diff --git a/tester/java/org/linphone/limeTester/LimeTesterUtils.java b/tester/java/org/linphone/limeTester/LimeTesterUtils.java
index b71600f..db45b94 100644
--- a/tester/java/org/linphone/limeTester/LimeTesterUtils.java
+++ b/tester/java/org/linphone/limeTester/LimeTesterUtils.java
@@ -26,6 +26,7 @@ import java.io.DataOutputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.InputStream;
 import java.util.concurrent.*;
+import java.util.Arrays;
 
 /**
  * @brief For testing purpose, we just count the success and fail received
@@ -85,6 +86,27 @@ class LimeStatusCallbackImpl implements LimeStatusCallback {
 			return false;
 		}
 	}
+
+	public boolean wait_for_fail(int expected_fail) {
+		try {
+			int time = 0;
+			while (time<timeout && fail<expected_fail) {
+				time += 25;
+				Thread.sleep(25);
+			}
+		}
+		catch (InterruptedException e) {
+			System.out.println("Interrupt exception in wait for fail");
+			return false;
+		}
+
+		if (expected_fail == fail) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
 }
 
 class LimePostToX3DH_Sync implements LimePostToX3DH {
@@ -214,6 +236,12 @@ public class LimeTesterUtils {
 	public final static byte C25519 = 1;
 	public final static byte C448 = 2;
 
+	public final static String shortMessage = "Short Message";
+	// with a long one(>80 <176) optimizeUploadSzie policy shall go for the cipherMessage encryption, but the optimizeGlobalBandwith stick to DRmessage (with 2 recipients)
+	public final static String longMessage = "This message is long enough to automatically switch to cipher Message mode when at least two recipients are involved.";
+	// with a very long one(>176) all optimize policies shall go for the cipherMessage encryption(with 2 recipients)
+	public final static String veryLongMessage = "This message is long enough to automatically switch to cipher Message mode when at least two recipients are involved. This message is long enough to automatically switch to cipher Message mode when at least two recipients are involved.";
+
 	public final static String[] patterns = {
 		"Frankly, my dear, I don't give a damn.",
 		"I'm gonna make him an offer he can't refuse.",
@@ -395,4 +423,48 @@ public class LimeTesterUtils {
 				return retObj;
 		}
 	}
+
+	byte[] DR_message_extractX3DHInit(byte[] message) {
+		// check if we actually have an X3DH init message to return
+		if (DR_message_holdsX3DHInit(message).holdsX3DHInit == false) {
+			return new byte[0];
+		}
+
+		// compute size
+		int X3DH_length = 5;
+		if (message[2] == C25519) {
+			X3DH_length += 32 + 32;
+		} else { // curve 448
+			X3DH_length += 57 + 56;
+		}
+
+		if (message[3] == 0x01) { // there is an OPk id
+			X3DH_length += 4;
+		}
+
+		// copy it in buffer
+		return Arrays.copyOfRange(message, 3, 3+X3DH_length);
+	}
+
+	private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
+	public static String bytesToHex(byte[] bytes) {
+		char[] hexChars = new char[bytes.length * 2];
+		for ( int j = 0; j < bytes.length; j++ ) {
+			int v = bytes[j] & 0xFF;
+			hexChars[j * 2] = hexArray[v >>> 4];
+			hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+		}
+		return new String(hexChars);
+	}
+
+	boolean DR_message_payloadDirectEncrypt(byte[] message) {
+		// checks on length to at least perform more checks
+		if (message.length<4) return false;
+
+		// check protocol version
+		if (message[0] != DR_v01) return false;
+
+		return ((message[1]&PayloadEncryptionFlagbit)==PayloadEncryptionFlagbit);
+	}
+
 }
-- 
GitLab