client/preprocessing/api.js

var linkedList = require('../../common/linkedlist.js');

module.exports = function (jiffClient) {
  var isRunning = false;
  var userCallbacks = [];
  var preprocessingTasks = [linkedList()];

  /**
   * Checks if the given operation uses preprocessed values
   * @method has_preprocessing
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @param {string} op - name of the operation to check
   * @return {boolean} true if the op uses preprocessing, false otherwise
   */
  jiffClient.has_preprocessing = function (op) {
    for (var i = 0; i < jiffClient.extensions.length; i++) {
      if (jiffClient.preprocessing_function_map[jiffClient.extensions[i]][op] != null) {
        return true;
      }
    }

    return false;
  };

  /**
   * Get a preprocessed share/value by associated op_id. If value does not exist
   * Fallback to some user specified way for creating it
   * @method get_preprocessing
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @param {string} op_id - the op_id associated with the preprocessed value/share
   * @return {object} the preprocessed share(s)
   */
  jiffClient.get_preprocessing = function (op_id) {
    var values = jiffClient.preprocessing_table[op_id];
    if (values != null) {
      return values;
    }
    if (jiffClient.crypto_provider === true) {
      return null;
    }
    throw new Error('No preprocessed value(s) that correspond to the op_id "' + op_id + '"');
  };

  /**
   * Store a pair of op_id and associated pre-processed value/share
   * The value/share can be accessed later during the computation through jiffClient.get_preprocessing(op_id)
   * @method store_preprocessing
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @param {string} op_id - the op_id associated with the preprocessed value/share
   * @param {SecretShare} share - the share/value to store
   */
  jiffClient.store_preprocessing = function (op_id, share) {
    if (share != null) {
      jiffClient.preprocessing_table[op_id] = share;
    }
  };

  /**
   * Generate values used for JIFF operations in advance of the computation.
   *
   * Calling this function does not begin preprocessing, it just creates a preprocessing task. After you created
   * your desired tasks, you can ask JIFF to execute them via {@link executePreprocessing}.
   *
   * @method preprocessing
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @param {string} dependent_op - name of the operation that will later use the pre_processed values
   * @param {Number} [count=1] - number of times the protocol should be performed, number of values that will be generated
   * @param {Object} [protocols=defaults] - a mapping from base preprocessing elements ('beaver', 'bits', 'sampling') to functions that can pre-process them
   *                               the function must implement the same interface as the JIFF provided protocols (e.g. jiffClient.protocols.generate_beaver_bgw),
   *                               missing mappings indicate that JIFF must use the default protocols
   * @param {Number} [threshold=receivers_list.length] - the threshold of the preprocessed shares
   * @param {Array} [receivers_list=all_parties] - the parties that will receive the preprocsssed shares
   * @param {Array} [compute_list=all_parties] - the parties that will compute the preprocsssed shares
   * @param {Number} [Zp=jiffClient.Zp] - the Zp of the preprocessed shares
   * @param {Array} [id_list=auto_gen()] - array of ids to be used sequentially to identify the pre_processed values
   * @param {Object} [params={}] - any additional protocol-specific parameters
   * @return {promise} a promise that is resolved when preprocessing is completed, null if this is called by a party that is neither a compute nor receiver party
   * @see {@link executePreprocessing}
   */
  jiffClient.preprocessing = function (dependent_op, count, protocols, threshold, receivers_list, compute_list, Zp, id_list, params) {
    // defaults!
    if (receivers_list == null) {
      receivers_list = [];
      for (var p = 1; p <= jiffClient.party_count; p++) {
        receivers_list.push(p);
      }
    } else {
      jiffClient.helpers.sort_ids(receivers_list);
    }
    if (compute_list == null) {
      compute_list = [];
      for (var c = 1; c <= jiffClient.party_count; c++) {
        compute_list.push(c);
      }
    } else {
      jiffClient.helpers.sort_ids(compute_list);
    }

    // not a receiver nor a sender
    if (receivers_list.indexOf(jiffClient.id) === -1 && compute_list.indexOf(jiffClient.id) === -1) {
      return null;
    }

    // more defaults
    if (Zp == null) {
      Zp = jiffClient.Zp;
    }
    if (threshold == null) {
      threshold = receivers_list.length;
    }
    if (protocols == null) {
      protocols = jiffClient.default_preprocessing_protocols;
    }

    // actual preprocessing
    if (count == null || count <= 0) {
      count = 1;
    }
    if (params == null) {
      params = {};
    }
    if (params['namespace'] == null) {
      params['namespace'] = jiffClient.extensions[jiffClient.extensions.length - 1];
    }

    // Create preprocessing tasks
    var task = {
      dependent_op : dependent_op,
      count : count,
      threshold : threshold,
      receivers_list : receivers_list,
      compute_list : compute_list,
      Zp : Zp,
      id_list : id_list,
      id : null,
      params : params,
      protocols : protocols,
      deferred: new jiffClient.helpers.Deferred()
    };

    preprocessingTasks[preprocessingTasks.length - 1].add(task);

    return task.deferred.promise;
  };

  /**
   * Ask JIFF to start executing preprocessing for tasks previously added by {@link preprocessing}.
   *
   * Calls the provided callback when the preprocessing tasks are all done.
   *
   * @method executePreprocessing
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @param callback {!Function} - the callback to execute when preprocessing is finished.
   * {@link preprocessing}
   */
  jiffClient.executePreprocessing = function (callback) {
    userCallbacks.push(callback);
    preprocessingTasks.push(linkedList());

    if (!isRunning) {
      __executePreprocessing();
    }
  };

  // called only when preprocessing can run RIGHT NOW
  var __executePreprocessing = function () {
    isRunning = true;

    jiffClient.currentPreprocessingTasks = preprocessingTasks.shift();
    var currentCallback = userCallbacks.shift();

    jiffClient.preprocessingCallback = function () {
      if (currentCallback != null) {
        currentCallback.apply(null, currentCallback);
      }

      if (userCallbacks.length > 0) {
        __executePreprocessing();
      } else {
        isRunning = false;
      }
    };

    jiffClient.preprocessingDaemon();
  };
};