From b7c73451e9819dba75ed1ebeddbb4b1d7087e0b5 Mon Sep 17 00:00:00 2001
From: Johan Pascal <johan.pascal@belledonne-communications.com>
Date: Tue, 22 Jan 2019 15:26:52 +0700
Subject: [PATCH] JNI: improve communication with X3DH server PostToX3DH is now
 an interface

---
 src/java/CMakeLists.txt                       |  2 +-
 src/java/org/linphone/lime/LimeManager.java   | 25 +++--
 .../org/linphone/lime/LimePostToX3DH.java     | 35 +++++++
 src/java/org/linphone/lime/PostToX3DH.java    | 93 -------------------
 src/lime_jni.cpp                              | 92 +++++++++---------
 tester/java/LimeTester.java                   | 61 +++++++++++-
 6 files changed, 161 insertions(+), 147 deletions(-)
 create mode 100644 src/java/org/linphone/lime/LimePostToX3DH.java
 delete mode 100644 src/java/org/linphone/lime/PostToX3DH.java

diff --git a/src/java/CMakeLists.txt b/src/java/CMakeLists.txt
index e0358fb..ce9e03d 100644
--- a/src/java/CMakeLists.txt
+++ b/src/java/CMakeLists.txt
@@ -30,7 +30,7 @@ set (LIME_SOURCE_FILES_JAVA
 	org/linphone/lime/LimeCallbackReturn.java
 	org/linphone/lime/LimeStatusCallback.java
 	org/linphone/lime/RecipientData.java
-	org/linphone/lime/PostToX3DH.java
+	org/linphone/lime/LimePostToX3DH.java
 	org/linphone/lime/LimeManager.java
 )
 
diff --git a/src/java/org/linphone/lime/LimeManager.java b/src/java/org/linphone/lime/LimeManager.java
index 612c7ca..79d2d49 100644
--- a/src/java/org/linphone/lime/LimeManager.java
+++ b/src/java/org/linphone/lime/LimeManager.java
@@ -19,15 +19,15 @@
 package org.linphone.lime;
 
 public class LimeManager {
+	private long nativePtr; // stores the native object pointer
 
-	public LimeManager(String db_access) {
-	    initialize(db_access);
-	}
-
-	private long peer;
-	protected native void initialize(String db_access);
+	protected native void initialize(String db_access, LimePostToX3DH postObj);
 	protected native void finalize() throws Throwable;
 
+	public LimeManager(String db_access, LimePostToX3DH postObj) {
+		initialize(db_access, postObj);
+	}
+
 	// Native functions involving enumerated paremeters not public
 	// Enumeration translation is done on java side
 	private native int n_decrypt(String localDeviceId, String recipientUserId, String senderDeviceId, byte[] DRmessage, byte[] cipherMessage, LimeOutputBuffer plainMessage);
@@ -36,7 +36,6 @@ public class LimeManager {
 	private native void n_create_user(String localDeviceId, String serverURL, int curveId, int OPkInitialBatchSize, LimeStatusCallback statusObj);
 	private native void n_update(LimeStatusCallback statusObj, int OPkServerLowLimit, int OPkBatchSize);
 
-
 	/* default value for OPkInitialBatchSize is 100 */
 	public void create_user(String localDeviceId, String serverURL, LimeCurveId curveId, LimeStatusCallback statusObj) {
 		this.n_create_user(localDeviceId, serverURL, curveId.getNative(), 100, statusObj);
@@ -156,4 +155,16 @@ public class LimeManager {
 	public void update(LimeStatusCallback statusObj) {
 		this.n_update(statusObj, 100, 25);
 	}
+
+	/**
+	 * @brief native function to process the X3DH server response
+	 *
+	 * This function must be called by the response handler to forward the response (and http answer code)
+	 *
+	 * @param[in]	ptr		a native object pointer passed to the postToX3DH method
+	 * @param[in]	responseCode	the HTTP server response code
+	 * @param[in]	response	the binary X3DH server response
+	 *
+	 */
+	public static native void process_X3DHresponse(long ptr, int responseCode, byte[] response);
 }
diff --git a/src/java/org/linphone/lime/LimePostToX3DH.java b/src/java/org/linphone/lime/LimePostToX3DH.java
new file mode 100644
index 0000000..7a13cb5
--- /dev/null
+++ b/src/java/org/linphone/lime/LimePostToX3DH.java
@@ -0,0 +1,35 @@
+/*
+	LimePostToX3DH.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;
+
+/** @brief Define an interface to communicate with the X3DH server
+ */
+public interface LimePostToX3DH {
+	/**
+	 * @brief Function called by native code to post a message to the X3DH server
+	 *
+	 * @param[in]	ptr 	a native object pointer (stored in java long), must be returned along the server response to process it
+	 * @param[in]	url	the X3DH server's URL
+	 * @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
+	 */
+	public void postToX3DHServer(long ptr, String url, String from, byte[] message);
+}
diff --git a/src/java/org/linphone/lime/PostToX3DH.java b/src/java/org/linphone/lime/PostToX3DH.java
deleted file mode 100644
index 51dc67c..0000000
--- a/src/java/org/linphone/lime/PostToX3DH.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
-	PostToX3DH.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;
-
-import java.net.URL;
-import javax.net.ssl.HttpsURLConnection;
-import java.io.DataOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-
-/** @brief Class to communicate with X3DH server
- * an object is instanciated for each POST to the server as it hold a pointer to a encapsulated c++ closure
- * the object is instanciated from the native code so do not modify class or method name
- */
-public class PostToX3DH {
-	public long m_responseFunctionPtr; // store a native pointer used to send the response to the lime library via a stateful callback
-
-	/** @brief Function call by native side to post a message to an X3DH server.
-	 * The connection is synchronous so this function also collect the answer
-	 * and send it back to the native library using the process_response native method
-	 */
-	public void postToX3DHServer(long ptr, String url, String from, byte[] message) {
-		try {
-			m_responseFunctionPtr = ptr;
-
-			// connect to the given URL
-			String local_url = new String(url.toCharArray());
-			URL obj = new URL(local_url);
-			HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
-
-			// set request header
-			con.setRequestMethod("POST");
-			con.setRequestProperty("User-Agent", "lime");
-			con.setRequestProperty("Content-type", "x3dh/octet-stream");
-			con.setRequestProperty("From", from);
-
-			// Send post request
-			con.setDoOutput(true);
-			DataOutputStream wr = new DataOutputStream(con.getOutputStream());
-			wr.write(message, 0, message.length);
-			wr.flush();
-			wr.close();
-
-			// wait for a response
-			int responseCode = con.getResponseCode();
-			InputStream in = con.getInputStream();
-			ByteArrayOutputStream response = new ByteArrayOutputStream( );
-
-			byte[] buffer = new byte[256];
-			int bufferLength;
-
-			while ((bufferLength = in.read(buffer)) != -1){
-				response.write(buffer, 0, bufferLength);
-			}
-			in.close();
-
-			// call response process native function
-			process_response(m_responseFunctionPtr, responseCode, response.toByteArray());
-			response.close();
-		}
-		catch (Exception e) {
-			System.out.println("Exception during HTTPS connection" + e.getMessage());
-		}
-	}
-
-	// native function to process response
-	public static native void process_response(long ptr, int responseCode, byte[] response);
-
-	// at construction(fired by native code only), store the native pointer(casted to a jlong)
-	// to be able to give it back to the native process_response method
-	public PostToX3DH() {
-		m_responseFunctionPtr = 0;
-	}
-	public PostToX3DH(long response) {
-		m_responseFunctionPtr = response;
-	}
-}
diff --git a/src/lime_jni.cpp b/src/lime_jni.cpp
index 13a2930..e2e3454 100644
--- a/src/lime_jni.cpp
+++ b/src/lime_jni.cpp
@@ -162,45 +162,25 @@ static lime::CurveId j2cCurveId(jni::jint curveId) {
 extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
 
 // java classes we would need to access
-struct jPostToX3DH { static constexpr auto Name() { return "org/linphone/lime/PostToX3DH"; } };
+struct jPostToX3DH { static constexpr auto Name() { return "org/linphone/lime/LimePostToX3DH"; } };
 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"; } };
 
 jni::JNIEnv& env { jni::GetEnv(*vm) };
 
-/**
- * @brief process response from X3DH server
- * This function is bind to a java PostToX3DH object process_response method
- * It :
- * - converts from jbytes(signed char) to unsigned char the response array
- * - retrieves from the given back responseHolder pointer the closure pointer to callback the line lib and call it
- */
-auto process_response = [](jni::JNIEnv &env, jni::Class<jPostToX3DH>& , jni::jlong processPtr, jni::jint responseCode, jni::Array<jni::jbyte> &response) {
-	// turn the response array into a vector of jbytes(signed char)
-	auto responseVector = std::make_shared<std::vector<uint8_t>>();
-	jbyteArray2uin8_tVector(env, response, responseVector);
-
-	// retrieve the statefull closure pointer to response processing provided by the lime lib
-	auto responseHolderPtr = reinterpret_cast<responseHolder *>(processPtr);
-	responseHolderPtr->process(responseCode, *responseVector);
-
-	delete(responseHolderPtr);
-};
-
-// bind the process_response to the java PostToX3DH.process_response method
-jni::RegisterNatives(env, *jni::Class<jPostToX3DH>::Find(env), jni::MakeNativeMethod("process_response", process_response));
 
 struct jLimeManager {
 	static constexpr auto Name() { return "org/linphone/lime/LimeManager"; } // bind this class to the java LimeManager Class
 
-	std::unique_ptr<lime::LimeManager> m_manager; // a unique pointer to the actual lime manager
+	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. */
 
 	/** @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
 	 */
-	jLimeManager(JNIEnv &env, const jni::String &db_access) {
+	jLimeManager(JNIEnv &env, const jni::String &db_access, jni::Object<jPostToX3DH> &jpostObj) : jGlobalPostObj{jni::NewGlobal<jni::EnvGettingDeleter>(env, jpostObj)} {
 		// turn the argument into a cpp string
 		std::string cpp_db_access = jni::Make<std::string>(env, db_access);
 
@@ -208,24 +188,21 @@ struct jLimeManager {
 		JavaVM *c_vm;
 		env.GetJavaVM(&c_vm);
 
-		m_manager = std::make_unique<lime::LimeManager>(cpp_db_access, [c_vm](const std::string &url, const std::string &from, const std::vector<uint8_t> &message, const lime::limeX3DHServerResponseProcess &responseProcess){
+		// closure must capture pointer to current object to be able to access the jGlobalPostObj field
+		auto thiz = this;
+
+		m_manager = std::make_unique<lime::LimeManager>(cpp_db_access, [c_vm, thiz](const std::string &url, const std::string &from, const std::vector<uint8_t> &message, const lime::limeX3DHServerResponseProcess &responseProcess){
 			//  (TODO? make sure the current process is attached?)
 			jni::JNIEnv& g_env { jni::GetEnv(*c_vm)};
-
 			// Create a Cpp object to hold the reponseProcess closure (cannot give a stateful function to the java side)
 			auto process = new responseHolder(responseProcess);
-
-			// Instantiate a java postToX3DH object
+			// retrieve the postToX3DHServer  method
 			auto PostClass = jni::Class<jPostToX3DH>::Find(g_env);
-			auto PostClassCtr  = PostClass.GetConstructor<jni::jlong>(g_env);
-			auto PostObj = PostClass.New(g_env, PostClassCtr, jni::jlong(process)); // give it a pointer(casted to jlong) to the responseHolder object
-
-			// retrieve the postToX3DHServer method
 			auto PostMethod = PostClass.GetMethod<void (jni::jlong, jni::String, jni::String, jni::Array<jni::jbyte>)>(g_env, "postToX3DHServer");
-			// Call the postToX3DHServer method on our object
-			PostObj.Call(g_env, PostMethod, jni::jlong(process), jni::Make<jni::String>(g_env, url), jni::Make<jni::String>(g_env, from), jni::Make<jni::Array<jni::jbyte>>(g_env, reinterpret_cast<const std::vector<int8_t>&>(message)));
-		});
 
+			// Call the postToX3DHServer method passing it our pointer holding the response process object
+			thiz->jGlobalPostObj.Call(g_env, PostMethod, jni::jlong(process), jni::Make<jni::String>(g_env, url), jni::Make<jni::String>(g_env, from), jni::Make<jni::Array<jni::jbyte>>(g_env, reinterpret_cast<const std::vector<int8_t>&>(message)));
+		});
 	}
 
 	jLimeManager(const jLimeManager&) = delete; // noncopyable
@@ -235,21 +212,23 @@ struct jLimeManager {
 	}
 
 
-	void create_user(jni::JNIEnv &env, const jni::String &localDeviceId, const jni::String &serverUrl, const jni::jint jcurveId, const jni::jint jOPkInitialBatchSize, jni::Object<jStatusCallback> &statusObj ) {
+	void create_user(jni::JNIEnv &env, const jni::String &localDeviceId, const jni::String &serverUrl, const jni::jint jcurveId, const jni::jint jOPkInitialBatchSize, jni::Object<jStatusCallback> &jstatusObj ) {
 		LIME_LOGD<<"JNI create_user user "<<jni::Make<std::string>(env, localDeviceId)<<" url "<<jni::Make<std::string>(env, serverUrl);
 		JavaVM *c_vm;
 		env.GetJavaVM(&c_vm);
 
-		m_manager->create_user( jni::Make<std::string>(env, localDeviceId),
-				jni::Make<std::string>(env, serverUrl),
-				j2cCurveId(jcurveId), jOPkInitialBatchSize, [c_vm, &statusObj](const lime::CallbackReturn status, const std::string message){
+		auto callback_lambda = [c_vm, &jstatusObj ](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
-					statusObj.Call(g_env, StatusMethod, c2jCallbackReturn(status), jni::Make<jni::String>(g_env, message));
-				});
+					jstatusObj.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, std::move(callback_lambda));
 	}
 
 	void delete_user(jni::JNIEnv &env, const jni::String &localDeviceId, jni::Object<jStatusCallback> &statusObj ) {
@@ -347,6 +326,8 @@ struct jLimeManager {
 			jni::Array<jni::jbyte> &jcipherMessage,
 			jni::Object<jLimeOutputBuffer> &jplainMessage) {
 
+		LIME_LOGD<<"JNI Decrypt from "<<(jni::Make<std::string>(env, jsenderDeviceId))<<" for user "<<(jni::Make<std::string>(env, jrecipientUserId))<<" (device : "<<(jni::Make<std::string>(env, jlocalDeviceId))<<")"<<std::endl;
+
 		// turn the DR and cipher message java byte array into a vector of uint8_t
 		auto DRmessage = std::make_shared<std::vector<uint8_t>>();
 		jbyteArray2uin8_tVector(env, jDRmessage, DRmessage);
@@ -378,6 +359,8 @@ struct jLimeManager {
 		JavaVM *c_vm;
 		env.GetJavaVM(&c_vm);
 
+		LIME_LOGD<<"JNI update";
+
 		m_manager->update([c_vm, &statusObj] (const lime::CallbackReturn status, const std::string message)
 				{
 					// get the env from VM
@@ -392,12 +375,14 @@ struct jLimeManager {
 				},
 				jOPkServerLowLimit, jOPkBatchSize);
 	}
+
+
 };
 
 #define METHOD(MethodPtr, name) jni::MakeNativePeerMethod<decltype(MethodPtr), (MethodPtr)>(name)
 
-jni::RegisterNativePeer<jLimeManager>(env, jni::Class<jLimeManager>::Find(env), "peer",
-	jni::MakePeer<jLimeManager, jni::String &>,
+jni::RegisterNativePeer<jLimeManager>(env, jni::Class<jLimeManager>::Find(env), "nativePtr",
+	jni::MakePeer<jLimeManager, jni::String &, jni::Object<jPostToX3DH> &>,
 	"initialize",
 	"finalize",
 	METHOD(&jLimeManager::create_user, "n_create_user"),
@@ -407,5 +392,26 @@ jni::RegisterNativePeer<jLimeManager>(env, jni::Class<jLimeManager>::Find(env),
 	METHOD(&jLimeManager::update, "n_update")
 	);
 
+// bind the process_response to the static java LimeManager.process_response method
+// This is not done in the previous RegisterNativePeer because the method is static
+/**
+ * @brief process response from X3DH server
+ * This function is bind to a java PostToX3DH object process_response method
+ * It :
+ * - converts from jbytes(signed char) to unsigned char the response array
+ * - retrieves from the given back responseHolder pointer the closure pointer to callback the line lib and call it
+ */
+auto process_X3DHresponse= [](jni::JNIEnv &env, jni::Class<jLimeManager>&, jni::jlong processPtr, jni::jint responseCode, jni::Array<jni::jbyte> &response) {
+	// turn the response array into a vector of jbytes(signed char)
+	auto responseVector = std::make_shared<std::vector<uint8_t>>();
+	jbyteArray2uin8_tVector(env, response, responseVector);
+		// retrieve the statefull closure pointer to response processing provided by the lime lib
+	auto responseHolderPtr = reinterpret_cast<responseHolder *>(processPtr);
+	responseHolderPtr->process(responseCode, *responseVector);
+		delete(responseHolderPtr);
+	};
+
+jni::RegisterNatives(env, *jni::Class<jLimeManager>::Find(env), jni::MakeNativeMethod("process_X3DHresponse", process_X3DHresponse));
+
 return jni::Unwrap(jni::jni_version_1_2);
 } // JNI_OnLoad
diff --git a/tester/java/LimeTester.java b/tester/java/LimeTester.java
index b45219d..d76b112 100644
--- a/tester/java/LimeTester.java
+++ b/tester/java/LimeTester.java
@@ -6,6 +6,11 @@ import java.security.cert.X509Certificate;
 
 import java.io.File;
 
+import java.net.URL;
+import javax.net.ssl.HttpsURLConnection;
+import java.io.DataOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
 /**
  * @brief For testing purpose, we just count the success and fail received
  */
@@ -38,6 +43,54 @@ class LimeStatusCallbackImpl implements LimeStatusCallback {
 	}
 }
 
+class LimePostToX3DH_Sync implements LimePostToX3DH {
+	/** @brief Function call by native side to post a message to an X3DH server.
+	 * The connection is synchronous so this function also collect the answer
+	 * and send it back to the native library using the process_response native method
+	 */
+	public void postToX3DHServer(long ptr, String url, String from, byte[] message) {
+		try {
+			// connect to the given URL
+			String local_url = new String(url.toCharArray());
+			URL obj = new URL(local_url);
+			HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
+
+			// set request header
+			con.setRequestMethod("POST");
+			con.setRequestProperty("User-Agent", "lime");
+			con.setRequestProperty("Content-type", "x3dh/octet-stream");
+			con.setRequestProperty("From", from);
+
+			// Send post request
+			con.setDoOutput(true);
+			DataOutputStream wr = new DataOutputStream(con.getOutputStream());
+			wr.write(message, 0, message.length);
+			wr.flush();
+			wr.close();
+
+			// wait for a response
+			int responseCode = con.getResponseCode();
+			InputStream in = con.getInputStream();
+			ByteArrayOutputStream response = new ByteArrayOutputStream( );
+
+			byte[] buffer = new byte[256];
+			int bufferLength;
+
+			while ((bufferLength = in.read(buffer)) != -1){
+				response.write(buffer, 0, bufferLength);
+			}
+			in.close();
+
+			// call response process native function
+			LimeManager.process_X3DHresponse(ptr, responseCode, response.toByteArray());
+			response.close();
+		}
+		catch (Exception e) {
+			System.out.println("Exception during HTTPS connection" + e.getMessage());
+		}
+	}
+}
+
 public class LimeTester {
 	public static void main(String[] args) {
 		// Load lime library
@@ -78,13 +131,15 @@ public class LimeTester {
 		int expected_success = 0;
 		int expected_fail = 0;
 
+		LimeStatusCallbackImpl statusCallback = new LimeStatusCallbackImpl();
+		LimePostToX3DH_Sync postObj = new LimePostToX3DH_Sync();
+
 		String AliceDeviceId = "alice"+UUID.randomUUID().toString();
 		String BobDeviceId = "bob"+UUID.randomUUID().toString();
-		LimeManager aliceManager = new LimeManager("testdb_Alice.sqlite3");
-		LimeManager bobManager = new LimeManager("testdb_Bob.sqlite3");
+		LimeManager aliceManager = new LimeManager("testdb_Alice.sqlite3", postObj);
+		LimeManager bobManager = new LimeManager("testdb_Bob.sqlite3", postObj);
 
 		expected_success+= 2;
-		LimeStatusCallbackImpl statusCallback = new LimeStatusCallbackImpl();
 		aliceManager.create_user(AliceDeviceId, "https://localhost:25519", LimeCurveId.C25519, statusCallback);
 		bobManager.create_user(BobDeviceId, "https://localhost:25519", LimeCurveId.C25519, 10, statusCallback);
 		assert (statusCallback.success == expected_success);
-- 
GitLab