rules-kademlia.js

'use strict';

const assert = require('node:assert');
const utils = require('./utils');


/**
 * Represent kademlia protocol handlers
 */
class KademliaRules {

  /**
   * Constructs a kademlia rules instance in the context of a
   * {@link KademliaNode}
   * @constructor
   * @param {KademliaNode} node
   */
  constructor(node) {
    this.node = node;
  }

  /**
   * This RPC involves one node sending a PING message to another, which
   * presumably replies with a PONG. This has a two-fold effect: the
   * recipient of the PING must update the bucket corresponding to the
   * sender; and, if there is a reply, the sender must update the bucket
   * appropriate to the recipient.
   * @param {AbstractNode~request} request
   * @param {AbstractNode~response} response
   */
  ping(request, response) {
    response.send([]);
  }

  /**
   * The sender of the STORE RPC provides a key and a block of data and
   * requires that the recipient store the data and make it available for
   * later retrieval by that key.
   * @param {AbstractNode~request} request
   * @param {AbstractNode~response} response
   * @param {AbstractNode~next} next
   */
  store(request, response, next) {
    const [key, item] = request.params;

    try {
      assert(typeof item === 'object',
        'Invalid storage item supplied');
      assert(typeof item.timestamp === 'number',
        'Invalid timestamp supplied');
      assert(utils.keyStringIsValid(item.publisher),
        'Invalid publisher identity supplied');
      assert(utils.keyStringIsValid(key),
        'Invalid item key supplied');
      assert(typeof item.value !== 'undefined',
        'Invalid item value supplied');
    } catch (err) {
      return next(err);
    }

    this.node.storage.put(key, item, { valueEncoding: 'json' }, (err) => {
      if (err) {
        return next(err);
      }

      response.send([key, item]); // NB: Echo back what was stored
    });
  }

  /**
   * The FIND_NODE RPC includes a 160-bit key. The recipient of the RPC returns
   * up to K contacts that it knows to be closest to the key. The recipient
   * must return K contacts if at all possible. It may only return fewer than K
   * if it is returning all of the contacts that it has knowledge of.
   * @param {AbstractNode~request} request
   * @param {AbstractNode~response} response
   * @param {AbstractNode~next} next
   */
  findNode(request, response, next) {
    const [key] = request.params;

    if (!utils.keyStringIsValid(key)) {
      return next(new Error('Invalid lookup key supplied'));
    }

    response.send([...this.node.router.getClosestContactsToKey(key).entries()]);
  }

  /**
   * A FIND_VALUE RPC includes a B=160-bit key. If a corresponding value is
   * present on the recipient, the associated data is returned. Otherwise the
   * RPC is equivalent to a FIND_NODE and a set of K contacts is returned.
   * @param {AbstractNode~request} request
   * @param {AbstractNode~response} response
   * @param {AbstractNode~next} next
   */
  findValue(request, response, next) {
    const [key] = request.params;

    if (!utils.keyStringIsValid(key)) {
      return next(new Error('Invalid lookup key supplied'));
    }

    this.node.storage.get(key, { valueEncoding: 'json' }, (err, item) => {
      if (err) {
        return this.findNode(request, response, next);
      }

      response.send(item);
    });
  }

}

module.exports = KademliaRules;