From 07072912cdf680766d8c36f6333b77f9aa4f027c Mon Sep 17 00:00:00 2001
From: Mark Haines <mark.haines@matrix.org>
Date: Tue, 23 Jun 2015 17:50:30 +0100
Subject: [PATCH] Add javascript bindings using emscripten

---
 javascript/axolotl_post.js | 247 +++++++++++++++++++++++++++++++++++++
 javascript/axolotl_pre.js  |   1 +
 javascript/build.py        |  69 +++++++++++
 3 files changed, 317 insertions(+)
 create mode 100644 javascript/axolotl_post.js
 create mode 100644 javascript/axolotl_pre.js
 create mode 100644 javascript/build.py

diff --git a/javascript/axolotl_post.js b/javascript/axolotl_post.js
new file mode 100644
index 0000000..7902725
--- /dev/null
+++ b/javascript/axolotl_post.js
@@ -0,0 +1,247 @@
+var runtime = Module['Runtime'];
+var malloc = Module['_malloc'];
+var free = Module['_free'];
+var Pointer_stringify = Module['Pointer_stringify'];
+var AXOLOTL_ERROR = Module['_axolotl_error']();
+
+function stack(size_or_array) {
+    return Module['allocate'](size_or_array, 'i8', Module['ALLOC_STACK']);
+}
+
+function array_from_string(string) {
+    return Module['intArrayFromString'](string, true);
+}
+
+function random_stack(size) {
+    var ptr = stack(size);
+    var array = new Uint8Array(Module['HEAPU8'].buffer, ptr, size);
+    window.crypto.getRandomValues(array);
+}
+
+function restore_stack(wrapped) {
+    return function() {
+        var sp = runtime.stackSave();
+        try {
+            return wrapped.apply(this, arguments);
+        } finally {
+            runtime.stackRestore(sp);
+        }
+    }
+}
+
+function Account() {
+    var size = Module['_axolotl_account_size']();
+    this.buf = malloc(size);
+    this.ptr = Module['_axolotl_account'](this.buf);
+}
+
+function account_method(wrapped) {
+    return function() {
+        var result = wrapped.apply(this, arguments);
+        if (result === AXOLOTL_ERROR) {
+            var message = Pointer_stringify(
+                Module['_axolotl_account_last_error'](arguments[0])
+            );
+            throw "AXOLOTL." + message;
+        }
+        return result;
+    }
+}
+
+Account.prototype['free'] = function() {
+    free(this.ptr);
+}
+
+Account.prototype['create'] = restore_stack(function() {
+    var random_length = account_method(
+        Module['_axolotl_create_account_random_length']
+    )(this.ptr);
+    var random = random_stack(random_length);
+    account_method(Module['_axolotl_create_account'])(
+        this.ptr, random, random_length
+    );
+});
+
+Account.prototype['identity_keys'] = restore_stack(function() {
+    var keys_length = account_method(
+        Module['_axolotl_account_identity_keys_length']
+    )(this.ptr);
+    var keys = stack(keys_length);
+    account_method(Module['_axolotl_account_identity_keys'])(
+        this.ptr, keys, keys_length
+    );
+    return Pointer_stringify(keys, keys_length);
+});
+
+Account.prototype['one_time_keys'] = restore_stack(function() {
+    var keys_length = account_method(
+        Module['_axolotl_account_one_time_keys_length']
+    )(this.ptr);
+    var keys = stack(keys_length);
+    account_method(Module['_axolotl_account_one_time_keys'])(
+        this.ptr, keys, keys_length
+    );
+    return Pointer_stringify(keys, keys_length);
+});
+
+Account.prototype['pickle'] = restore_stack(function(key) {
+    var key_array = array_from_string(key);
+    var pickle_length = account_method(
+        Module['_axolotl_pickle_account_length']
+    )(this.ptr);
+    var key_buffer = stack(key_array);
+    var pickle_buffer = stack(pickle_length);
+    account_method(Module['_axolotl_pickle_account'])(
+        this.ptr, key_buffer, key_array.length, pickle_buffer, pickle_length
+    );
+    return Pointer_stringify(pickle_buffer, pickle_length);
+});
+
+Account.prototype['unpickle'] = restore_stack(function(key, pickle) {
+    var key_array = array_from_string(key);
+    var key_buffer = stack(key_array);
+    var pickle_array = array_from_string(pickle);
+    var pickle_buffer = stack(pickle_length);
+    account_method(Module['_axolotl_unpickle_account'])(
+        this.ptr, key_buffer, key_array.length, pickle_buffer,
+        pickle_array.length
+    );
+});
+
+function Session() {
+    var size = Module['_axolotl_session_size']();
+    this.buf = malloc(size);
+    this.ptr = Module['_axolotl_session'](this.buf);
+}
+
+function session_method(wrapped) {
+    return function() {
+        var result = wrapped.apply(this, arguments);
+        if (result === AXOLOTL_ERROR) {
+            var message = Pointer_stringify(
+                Module['_axolotl_session_last_error'](arguments[0])
+            );
+            throw "AXOLOTL." + message;
+        }
+        return result;
+    }
+}
+
+Session.prototype['free'] = function() {
+    free(this.ptr);
+}
+
+Session.prototype['pickle'] = restore_stack(function(key) {
+    var key_array = array_from_string(key);
+    var pickle_length = session_method(
+        Module['_axolotl_pickle_session_length']
+    )(this.ptr);
+    var key_buffer = stack(key_array);
+    var pickle_buffer = stack(pickle_length);
+    session_method(Module['_axolotl_pickle_session'])(
+        this.ptr, key_buffer, key_array.length, pickle_buffer, pickle_length
+    );
+    return Pointer_stringify(pickle_buffer, pickle_length);
+});
+
+Session.prototype['unpickle'] = restore_stack(function(key, pickle) {
+    var key_array = array_from_string(key);
+    var key_buffer = stack(key_array);
+    var pickle_array = array_from_string(pickle);
+    var pickle_buffer = stack(pickle_array);
+    session_method(Module['_axolotl_unpickle_session'])(
+        this.ptr, key_buffer, key_array.length, pickle_buffer,
+        pickle_array.length
+    );
+});
+
+Session.prototype['create_outbound'] = restore_stack(function(
+    account, their_identity_key, their_one_time_key_id, their_one_time_key
+) {
+    var random_length = session_method(
+        Module['_axolotl_create_outbound_session_random_length']
+    )(this.ptr);
+    var random = random_stack(random_length);
+    var identity_key_array = array_from_string(their_identity_key);
+    var one_time_key_array = array_from_string(their_one_time_key);
+    var identity_key_buffer = stack(identity_key_array);
+    var one_time_key_buffer = stack(one_time_key_array);
+    session_method(Module['_axolotl_create_outbound_session'])(
+        this.ptr, account.ptr,
+        identity_key_buffer, identity_key_array.length,
+        their_one_time_key_id,
+        one_time_key_buffer, one_time_key_array.length,
+        random, random_length
+    );
+});
+
+Session.prototype['create_inbound'] = restore_stack(function(
+    account, one_time_key_message
+) {
+    var message_array = array_from_string(one_time_key_message);
+    var message_buffer = stack(message_array);
+    session_method(Module['_axolotl_create_inbound_session'])(
+        this.ptr, account.ptr, message_buffer, message_array.length
+    );
+});
+
+Session.prototype['matches_inbound'] = restore_stack(function(
+    account, one_time_key_message
+) {
+    var message_array = array_from_string(one_time_key_message);
+    var message_buffer = stack(message_array);
+    return session_method(Module['_axolotl_matches_inbound_session'])(
+        this.ptr, account.ptr, message_buffer, message_array.length
+    ) ? true : false;
+});
+
+Session.prototype['encrypt'] = restore_stack(function(
+    plaintext
+) {
+    var random_length = session_method(
+        Module['_axolotl_encrypt_random_length']
+    )(this.ptr);
+    var message_type = session_method(
+        Module['_axolotl_encrypt_message_type']
+    )(this.ptr);
+    var plaintext_array = array_from_string(plaintext);
+    var message_length = session_method(
+        Module['_axolotl_encrypt_message_length']
+    )(this.ptr, plaintext_array.length);
+    var random = random_stack(random_length);
+    var plaintext_buffer = stack(plaintext_array);
+    var message_buffer = stack(message_length);
+    session_method(Module['_axolotl_encrypt'])(
+        this.ptr,
+        plaintext_buffer, plaintext_array.length,
+        random, random_length,
+        message_buffer, message_length
+    );
+    return {
+        "type": message_type,
+        "body": Pointer_stringify(message_buffer, message_length)
+    };
+});
+
+Session.prototype['decrypt'] = restore_stack(function(
+    message_type, message
+) {
+    var message_array = array_from_string(message);
+    var message_buffer = stack(message_array);
+    var max_plaintext_length = session_method(
+        Module['_axolotl_decrypt_max_plaintext_length']
+    )(this.ptr, message_type, message_buffer, message_array.length);
+    // caculating the length destroys the input buffer.
+    // So we copy the array to a new buffer
+    var message_buffer = stack(message_array);
+    var plaintext_buffer = stack(max_plaintext_length);
+    var plaintext_length = session_method(Module["_axolotl_decrypt"])(
+        this.ptr, message_type,
+        message_buffer, message.length,
+        plaintext_buffer, max_plaintext_length
+    );
+    return Pointer_stringify(plaintext_buffer, plaintext_length);
+});
+
+return {"Account": Account, "Session": Session};
+}();
diff --git a/javascript/axolotl_pre.js b/javascript/axolotl_pre.js
new file mode 100644
index 0000000..69df923
--- /dev/null
+++ b/javascript/axolotl_pre.js
@@ -0,0 +1 @@
+Axolotl = function() {
diff --git a/javascript/build.py b/javascript/build.py
new file mode 100644
index 0000000..2b754e9
--- /dev/null
+++ b/javascript/build.py
@@ -0,0 +1,69 @@
+#! /usr/bin/python
+# Copyright 2015 OpenMarket Ltd
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import subprocess
+import glob
+import os
+import sys
+import re
+import json
+
+source_files = glob.glob("src/*.cpp")
+pre_js, = glob.glob("javascript/*pre.js")
+post_js, = glob.glob("javascript/*post.js")
+
+
+functions = set()
+RE_FUNCTION=re.compile("(axolotl_[^( ]*)\\(")
+with open("include/axolotl/axolotl.hh") as header:
+    for line in header:
+        match = RE_FUNCTION.search(line)
+        if match:
+            functions.add(match.groups()[0])
+
+
+exported_functions = os.path.abspath("build/exported_functions.json")
+with open(exported_functions, "w") as json_file:
+    json.dump(["_" + function for function in functions], json_file)
+
+
+emcc = os.environ.get("EMCC", "emcc")
+
+compile_args = [emcc]
+compile_args += """
+    -O3
+    -Iinclude
+    -Ilib
+    -std=c++11
+    --closure 1
+    --memory-init-file 0
+    -s NO_FILESYSTEM=1
+    -s NO_BROWSER=1
+    -s INVOKE_RUN=0
+""".split()
+compile_args += source_files
+compile_args += ("--pre-js", pre_js)
+compile_args += ("--post-js", post_js)
+compile_args += ("-s", "EXPORTED_FUNCTIONS=@" + exported_functions)
+
+library = "build/axolotl.js"
+
+def run(args):
+    print args
+    print " ".join(args)
+    subprocess.check_call(args)
+
+run(compile_args + ["-o", library])
+
-- 
GitLab