diff --git a/demos/array-mpc-as-a-service/compute-party.js b/demos/array-mpc-as-a-service/compute-party.js index 1fcd3d756..9bcae9201 100644 --- a/demos/array-mpc-as-a-service/compute-party.js +++ b/demos/array-mpc-as-a-service/compute-party.js @@ -9,6 +9,8 @@ * computation_id is optional, by default it will be 'test'. */ const path = require('path'); +/* eslint-disable */ +const jiff_restAPI = require('../../lib/ext/jiff-client-restful.js'); console.log('Command line arguments: [/path/to/configuration/file.json] [computation_id]'); // Read config @@ -35,6 +37,7 @@ const jiffClient = new JIFFClient('http://localhost:8080', computation_id, { party_count: config.party_count, initialization: {role: 'compute'} // indicate to the server that this is a compute party }); +jiffClient.apply_extension(jiff_restAPI); // the computation code const compute = function () { diff --git a/lib/client/api/initialization.js b/lib/client/api/initialization.js index 76a87a864..237eddae9 100644 --- a/lib/client/api/initialization.js +++ b/lib/client/api/initialization.js @@ -22,7 +22,7 @@ module.exports = function (jiffClient) { } jiffClient.wait_callbacks.push({ parties: parties, callback: callback, initialization: wait_for_initialization }); - jiffClient.execute_wait_callbacks(); // See if the callback can be executed immediately + jiffClient.socketEvent.execute_wait_callbacks(); // See if the callback can be executed immediately }; /** diff --git a/lib/client/handlers/initialization.js b/lib/client/handlers/initialization.js index 308c01261..a31e85ca4 100644 --- a/lib/client/handlers/initialization.js +++ b/lib/client/handlers/initialization.js @@ -109,10 +109,10 @@ module.exports = function (jiffClient) { } // Resolve any pending messages that were received before the sender's public key was known - jiffClient.resolve_messages_waiting_for_keys(); + jiffClient.socketEvent.resolve_messages_waiting_for_keys(); // Resolve any pending waits that have satisfied conditions - jiffClient.execute_wait_callbacks(); + jiffClient.socketEvent.execute_wait_callbacks(); // Check if all keys have been received if (jiffClient.keymap['s1'] == null) { diff --git a/lib/client/socket/events.js b/lib/client/socket/events.js index 87b71d00f..71c1b8cb5 100644 --- a/lib/client/socket/events.js +++ b/lib/client/socket/events.js @@ -1,103 +1,106 @@ -module.exports = function (JIFFClient) { +class SocketEvent { + constructor(JIFFClient) { + this.jiffClient = JIFFClient; + } /** * Initialize socket listeners and events * @memberof module:jiff-client.JIFFClient * @method */ - JIFFClient.prototype.initSocket = function () { - const jiffClient = this; + initSocket = function () { + const jiffClient = this.jiffClient; // set on('connect') handler once! - this.socket.on('connect', jiffClient.handlers.connected); + this.jiffClient.socket.on('connect', jiffClient.handlers.connected); // Store the id when server sends it back - this.socket.on('initialization', jiffClient.handlers.initialized); + this.jiffClient.socket.on('initialization', jiffClient.handlers.initialized); // Public keys were updated on the server, and it sent us the updates - jiffClient.socket.on('public_keys', function (msg, callback) { + this.jiffClient.socket.on('public_keys', (msg, callback) => { callback(true); msg = JSON.parse(msg); - msg = jiffClient.hooks.execute_array_hooks('afterOperation', [jiffClient, 'public_keys', msg], 2); + msg = this.jiffClient.hooks.execute_array_hooks('afterOperation', [jiffClient, 'public_keys', msg], 2); - jiffClient.handlers.store_public_keys(msg.public_keys); + this.jiffClient.handlers.store_public_keys(msg.public_keys); }); // Setup receiving matching shares - this.socket.on('share', function (msg, callback) { + this.jiffClient.socket.on('share', (msg, callback) => { callback(true); // send ack to server // parse message const json_msg = JSON.parse(msg); const sender_id = json_msg['party_id']; - if (jiffClient.keymap[sender_id] != null) { - jiffClient.handlers.receive_share(json_msg); + if (this.jiffClient.keymap[sender_id] != null) { + this.jiffClient.handlers.receive_share(json_msg); } else { - if (jiffClient.messagesWaitingKeys[sender_id] == null) { - jiffClient.messagesWaitingKeys[sender_id] = []; + if (this.jiffClient.messagesWaitingKeys[sender_id] == null) { + this.jiffClient.messagesWaitingKeys[String(sender_id)] = []; } - jiffClient.messagesWaitingKeys[sender_id].push({ label: 'share', msg: json_msg }); + this.jiffClient.messagesWaitingKeys[String(sender_id)].push({ label: 'share', msg: json_msg }); } }); - this.socket.on('open', function (msg, callback) { + this.jiffClient.socket.on('open', (msg, callback) => { callback(true); // send ack to server // parse message const json_msg = JSON.parse(msg); const sender_id = json_msg['party_id']; - if (jiffClient.keymap[sender_id] != null) { - jiffClient.handlers.receive_open(json_msg); + if (this.jiffClient.keymap[sender_id] != null) { + this.jiffClient.handlers.receive_open(json_msg); } else { - if (jiffClient.messagesWaitingKeys[sender_id] == null) { - jiffClient.messagesWaitingKeys[sender_id] = []; + if (this.jiffClient.messagesWaitingKeys[sender_id] == null) { + this.jiffClient.messagesWaitingKeys[String(sender_id)] = []; } - jiffClient.messagesWaitingKeys[sender_id].push({ label: 'open', msg: json_msg }); + this.jiffClient.messagesWaitingKeys[String(sender_id)].push({ label: 'open', msg: json_msg }); } }); // handle custom messages - this.socket.on('custom', function (msg, callback) { + this.jiffClient.socket.on('custom', (msg, callback) => { callback(true); // send ack to server const json_msg = JSON.parse(msg); - const sender_id = json_msg['party_id']; + const sender_id = String(json_msg['party_id']); const encrypted = json_msg['encrypted']; - if (jiffClient.keymap[sender_id] != null || encrypted !== true) { - jiffClient.handlers.receive_custom(json_msg); + if (this.jiffClient.keymap[String(sender_id)] != null || encrypted !== true) { + this.jiffClient.handlers.receive_custom(json_msg); } else { // key must not exist yet for sender_id, and encrypted must be true - if (jiffClient.messagesWaitingKeys[sender_id] == null) { - jiffClient.messagesWaitingKeys[sender_id] = []; + if (this.jiffClient.messagesWaitingKeys[sender_id] == null) { + this.jiffClient.messagesWaitingKeys[String(sender_id)] = []; } - jiffClient.messagesWaitingKeys[sender_id].push({ label: 'custom', msg: json_msg }); + this.jiffClient.messagesWaitingKeys[String(sender_id)].push({ label: 'custom', msg: json_msg }); } }); - this.socket.on('crypto_provider', function (msg, callback) { + this.jiffClient.socket.on('crypto_provider', (msg, callback) => { callback(true); // send ack to server - jiffClient.handlers.receive_crypto_provider(JSON.parse(msg)); + this.jiffClient.handlers.receive_crypto_provider(JSON.parse(msg)); }); - this.socket.on('error', function (msg) { + this.jiffClient.socket.on('error', (msg) => { try { msg = JSON.parse(msg); - jiffClient.handlers.error(msg['label'], msg['error']); + this.jiffClient.handlers.error(msg['label'], msg['error']); } catch (error) { - jiffClient.handlers.error('socket.io', msg); + this.jiffClient.handlers.error('socket.io', msg); } }); - this.socket.on('disconnect', function (reason) { + this.jiffClient.socket.on('disconnect', (reason) => { if (reason !== 'io client disconnect') { // check that the reason is an error and not a user initiated disconnect console.log('Disconnected!', jiffClient.id, reason); } - jiffClient.hooks.execute_array_hooks('afterOperation', [jiffClient, 'disconnect', reason], -1); + this.jiffClient.hooks.execute_array_hooks('afterOperation', [this.jiffClient, 'disconnect', reason], -1); }); }; @@ -107,9 +110,9 @@ module.exports = function (JIFFClient) { * @memberof module:jiff-client.JIFFClient * @method */ - JIFFClient.prototype.execute_wait_callbacks = function () { - const copy_callbacks = this.wait_callbacks; - this.wait_callbacks = []; + execute_wait_callbacks() { + const copy_callbacks = this.jiffClient.wait_callbacks; + this.jiffClient.wait_callbacks = []; for (let i = 0; i < copy_callbacks.length; i++) { const wait = copy_callbacks[i]; const parties = wait.parties; @@ -117,22 +120,22 @@ module.exports = function (JIFFClient) { const initialization = wait.initialization; // Check if the parties to wait for are now known - let parties_satisfied = this.__initialized || !initialization; + let parties_satisfied = this.jiffClient.__initialized || !initialization; for (let j = 0; j < parties.length; j++) { - const party_id = parties[j]; - if (this.keymap == null || this.keymap[party_id] == null) { + const party_id = parties[parseInt(j, 10)]; + if (this.jiffClient.keymap == null || this.jiffClient.keymap[String(party_id)] == null) { parties_satisfied = false; break; } } if (parties_satisfied) { - callback(this); + callback(this.jiffClient); } else { - this.wait_callbacks.push(wait); + this.jiffClient.wait_callbacks.push(wait); } } - }; + } /** * Resolves all messages that were pending because their senders primary key was previously unknown. @@ -140,30 +143,32 @@ module.exports = function (JIFFClient) { * @memberof module:jiff-client.JIFFClient * @method */ - JIFFClient.prototype.resolve_messages_waiting_for_keys = function () { - for (let party_id in this.keymap) { - if (!this.keymap.hasOwnProperty(party_id)) { + resolve_messages_waiting_for_keys() { + for (let party_id in this.jiffClient.keymap) { + if (!Object.prototype.hasOwnProperty.call(this.jiffClient.keymap, String(party_id))) { continue; } - const messageQueue = this.messagesWaitingKeys[party_id]; + const messageQueue = this.jiffClient.messagesWaitingKeys[String(party_id)]; if (messageQueue == null) { continue; } for (let i = 0; i < messageQueue.length; i++) { const msg = messageQueue[i]; if (msg.label === 'share') { - this.handlers.receive_share(msg.msg); + this.jiffClient.handlers.receive_share(msg.msg); } else if (msg.label === 'open') { - this.handlers.receive_open(msg.msg); + this.jiffClient.handlers.receive_open(msg.msg); } else if (msg.label === 'custom') { - this.handlers.receive_custom(msg.msg); + this.jiffClient.handlers.receive_custom(msg.msg); } else { throw new Error('Error resolving pending message: unknown label ' + msg.label); } } - this.messagesWaitingKeys[party_id] = null; + this.jiffClient.messagesWaitingKeys[String(party_id)] = null; } - }; -}; + } +} + +module.exports = SocketEvent; diff --git a/lib/client/socket/mailbox.js b/lib/client/socket/mailbox.js index 6a183a0e3..7353b8fe6 100644 --- a/lib/client/socket/mailbox.js +++ b/lib/client/socket/mailbox.js @@ -21,124 +21,130 @@ const defaultSocketOptions = { * @constructor */ -function guardedSocket(jiffClient) { - jiffClient.options.socketOptions = Object.assign({}, defaultSocketOptions, jiffClient.options.socketOptions); - - // Create plain socket io object which we will wrap in this - const socket = io(jiffClient.hostname, jiffClient.options.socketOptions); - socket.old_disconnect = socket.disconnect; - socket.mailbox = new LinkedList(); // for outgoing messages - socket.empty_deferred = null; // gets resolved whenever the mailbox is empty - socket.jiffClient = jiffClient; - - // add functionality to socket - socket.safe_emit = safe_emit.bind(socket); - socket.resend_mailbox = resend_mailbox.bind(socket); - socket.disconnect = disconnect.bind(socket); - socket.safe_disconnect = safe_disconnect.bind(socket); - socket.is_empty = is_empty.bind(socket); - - return socket; -} +class GuardedSocket { + constructor(jiffClient) { + this.jiffClient = jiffClient; + this.options = {}; + const options = Object.assign({}, defaultSocketOptions, jiffClient.options.socketOptions); + + // Create plain socket io object which we will wrap in this + this.socket = io(jiffClient.hostname, options); + this.mailbox = new LinkedList(); // for outgoing messages + this.empty_deferred = null; // gets resolved whenever the mailbox is empty + this.old_disconnect = this.socket.disconnect; + } -/** - * Safe emit: stores message in the mailbox until acknowledgment is received, results in socket.emit(label, msg) call(s) - * @method safe_emit - * @memberof GuardedSocket - * @instance - * @param {string} label - the label given to the message - * @param {string} msg - the message to send - */ -const safe_emit = function (label, msg) { - // add message to mailbox - const mailbox_pointer = this.mailbox.add({ label: label, msg: msg }); - if (this.connected) { - let self = this; - // emit the message, if an acknowledgment is received, remove it from mailbox - this.emit(label, msg, function (status) { - if (status) { - self.mailbox.remove(mailbox_pointer); - if (this.is_empty() && self.empty_deferred != null) { - self.empty_deferred.resolve(); - } + on(instr, callback) { + this.socket.on(instr, callback); + } + + connect() { + this.socket.connect(); + } - if (label === 'free') { - this.jiffClient.hooks.execute_array_hooks('afterOperation', [this.jiffClient, 'free', msg], 2); + emit(instr, msg) { + this.socket.emit(instr, msg); + } + + /** + * Safe emit: stores message in the mailbox until acknowledgment is received, results in socket.emit(label, msg) call(s) + * @method safe_emit + * @memberof GuardedSocket + * @instance + * @param {string} label - the label given to the message + * @param {string} msg - the message to send + */ + + safe_emit(label, msg) { + // add message to mailbox + const mailbox_pointer = this.mailbox.add({ label: label, msg: msg }); + if (this.socket.connected) { + // emit the message, if an acknowledgment is received, remove it from mailbox + this.socket.emit(label, msg, (status) => { + if (status) { + this.mailbox.remove(mailbox_pointer); + if (this.is_empty() && this.empty_deferred != null) { + this.empty_deferred.resolve(); + } + + if (label === 'free') { + this.jiffClient.hooks.execute_array_hooks('afterOperation', [this.jiffClient, 'free', msg], 2); + } } - } - }); + }); + } } -}; -/** - * Re-sends all pending messages - * @method resend_mailbox - * @memberof GuardedSocket - * @instance - */ -const resend_mailbox = function () { - // Create a new mailbox, since the current mailbox will be resent and - // will contain new backups. - const old_mailbox = this.mailbox; - this.mailbox = new LinkedList(); - - // loop over all stored messages and emit them - let current_node = old_mailbox.head; - while (current_node != null) { - const label = current_node.object.label; - const msg = current_node.object.msg; - this.safe_emit(label, msg); - current_node = current_node.next; + /** + * Re-sends all pending messages + * @method resend_mailbox + * @memberof GuardedSocket + * @instance + */ + resend_mailbox() { + // Create a new mailbox, since the current mailbox will be resent and + // will contain new backups. + const old_mailbox = this.mailbox; + this.mailbox = new LinkedList(); + + // loop over all stored messages and emit them + let current_node = old_mailbox.head; + while (current_node != null) { + const label = current_node.object.label; + const msg = current_node.object.msg; + this.safe_emit(label, msg); + current_node = current_node.next; + } } -}; -/** - * Wraps socket.io regular disconnect with a call to a hook before disconnection - * @method disconnect - * @memberof GuardedSocket - * @instance - */ -const disconnect = function () { - this.jiffClient.hooks.execute_array_hooks('beforeOperation', [this.jiffClient, 'disconnect', {}], -1); - this.old_disconnect.apply(this, arguments); -}; + /** + * Wraps socket.io regular disconnect with a call to a hook before disconnection + * @method disconnect + * @memberof GuardedSocket + * @instance + */ + disconnect() { + this.jiffClient.hooks.execute_array_hooks('beforeOperation', [this.jiffClient, 'disconnect', {}], -1); + this.old_disconnect.apply(this.socket, arguments); + } -/** - * Safe disconnect: disconnect only after all messages (including free) were acknowledged and - * all pending opens were resolved - * @method safe_disconnect - * @memberof GuardedSocket - * @instance - * @param {boolean} [free=false] - if true, a free message will be issued prior to disconnecting - * @param {function()} [callback] - given callback will be executed after safe disconnection is complete - */ -const safe_disconnect = function (free, callback) { - if (this.is_empty()) { - if (free) { - this.jiffClient.free(); - free = false; - } else { - this.disconnect(); - if (callback != null) { - callback(); + /** + * Safe disconnect: disconnect only after all messages (including free) were acknowledged and + * all pending opens were resolved + * @method safe_disconnect + * @memberof GuardedSocket + * @instance + * @param {boolean} [free=false] - if true, a free message will be issued prior to disconnecting + * @param {function()} [callback] - given callback will be executed after safe disconnection is complete + */ + safe_disconnect(free, callback) { + if (this.is_empty()) { + if (free) { + this.jiffClient.free(); + free = false; + } else { + this.disconnect(); + if (callback != null) { + callback(); + } + return; } - return; } - } - this.empty_deferred = this.jiffClient.helpers.createDeferred(); - this.empty_deferred.promise.then(this.safe_disconnect.bind(this, free, callback)); -}; + this.empty_deferred = this.jiffClient.helpers.createDeferred(); + this.empty_deferred.promise.then(this.safe_disconnect.bind(this.socket, free, callback)); + } -/** - * Checks if the socket mailbox is empty (all communication was done and acknowledged), - * used in safe_disconnect - * @method is_empty - * @memberof GuardedSocket - * @instance - */ -const is_empty = function () { - return this.mailbox.head == null && this.jiffClient.counters.pending_opens === 0; -}; + /** + * Checks if the socket mailbox is empty (all communication was done and acknowledged), + * used in safe_disconnect + * @method is_empty + * @memberof GuardedSocket + * @instance + */ + is_empty() { + return this.mailbox.head == null && this.jiffClient.counters.pending_opens === 0; + } +} -module.exports = guardedSocket; +module.exports = GuardedSocket; diff --git a/lib/ext/jiff-client-websockets.js b/lib/ext/jiff-client-websockets.js index 39c618082..7a16e7207 100644 --- a/lib/ext/jiff-client-websockets.js +++ b/lib/ext/jiff-client-websockets.js @@ -54,7 +54,7 @@ * not have as many protocols. Instead these functions are routed to * when a message is received and a protocol is manually parsed. */ - jiff.initSocket = function () { + jiff.socketEvent.initSocket = function () { const jiffClient = this; /* ws uses the 'open' protocol on connection. Should not conflict with the @@ -222,7 +222,7 @@ JIFFClientInstance.socket.send(JSON.stringify({ socketProtocol: 'initialization', data: msg })); }; - JIFFClientInstance.initSocket(); + JIFFClientInstance.socketEvent.initSocket(); }; /* Functions that overwrite client/socket/mailbox.js functionality */ diff --git a/lib/jiff-client.js b/lib/jiff-client.js index cbd1c275c..6137f9506 100644 --- a/lib/jiff-client.js +++ b/lib/jiff-client.js @@ -43,9 +43,9 @@ const extensions = require('./client/arch/extensions.js'); const counters = require('./client/arch/counters.js'); // socket and events -const guardedSocket = require('./client/socket/mailbox.js'); +const GuardedSocket = require('./client/socket/mailbox.js'); const internalSocket = require('./client/socket/internal.js'); -const socketEvents = require('./client/socket/events.js'); +const SocketEvent = require('./client/socket/events.js'); // handlers for communication const handlers = require('./client/handlers.js'); @@ -439,15 +439,18 @@ function JIFFClient(hostname, computation_id, options) { * Socket wrapper between this instance and the server, based on sockets.io * @type {!GuardedSocket} */ - JIFFClientInstance.socket = guardedSocket(JIFFClientInstance); + JIFFClientInstance.socket = new GuardedSocket(JIFFClientInstance); } else { JIFFClientInstance.socket = internalSocket(JIFFClientInstance, options.__internal_socket); } + // Add socket event handlers as socketEvents + JIFFClientInstance.socketEvent = new SocketEvent(JIFFClientInstance); + // set up socket event handlers handlers(JIFFClientInstance); - JIFFClientInstance.initSocket(); + JIFFClientInstance.socketEvent.initSocket(); JIFFClientInstance.socket.connect(); }; @@ -480,9 +483,6 @@ function JIFFClient(hostname, computation_id, options) { } } -// Add socket event handlers to prototype -socketEvents(JIFFClient); - // Add extension management to prototype extensions(JIFFClient);