/**
* Interfaces for managing transport and implementation specific
* address information.
* @module kdns/contacts
*/
'use strict';
const {
compareKeyBuffers,
getRandomKeyString,
getDistance,
hash160 } = require('./keys');
class Contact {
/**
* An object containing implementation-dependant address data
* and a fingerprint.
* @constructor
* @param {object} [address] - Any transport specific information
* @param {string|function} [fingerprint] - Unique ID or function for creating one
*/
constructor(address, _fingerprint) {
this.address = address || { id: getRandomKeyString() };
this.fingerprint = typeof _fingerprint === 'function'
? _fingerprint(this.address).toString('hex')
: _fingerprint
? _fingerprint.toString('hex')
: hash160(JSON.stringify(this.address)).toString('hex');
}
}
class ContactList {
/**
* State machine used for sorting through {@link Contact}s during
* a {@link Node#iterativeFindNode} lookup.
* @constructor
* @param {string} key - 160 bit hex reference key for distance caluclation
* @param {Array<module:kdns/contacts~Contact>} [contacts] - List to initialize with
*/
constructor(key, contacts = []) {
this.key = key;
this._contacts = [];
this._contacted = new Set();
this._active = new Set();
this.add(contacts);
}
/**
* @property {module:kdns/contacts~Contact} closest - The contact closest to the reference key
*/
get closest() {
return this._contacts[0];
}
/**
* @property {module:kdns/contacts~Contact[]} active - Contacts in the list that are active
*/
get active() {
return this._contacts.filter(contact => this._active.has(contact.fingerprint));
}
/**
* @property {module:kdns/contacts~Contact[]} uncontacted - Contacts in the list that have not been
* contacted
*/
get uncontacted() {
return this._contacts.filter(contact => !this._contacted.has(contact.fingerprint));
}
/**
* Adds the given contacts to the list
* @param {module:kdns/contacts~Contact[]} contacts - Contacts to add to the list
* @returns {module:kdns/contacts~Contact[]} added - Contacts added to the list
*/
add(contacts) {
let identities = this._contacts.map(c => c.fingerprint);
let added = [];
contacts.forEach(contact => {
if (identities.indexOf(contact.fingerprint) === -1) {
this._contacts.push(contact);
identities.push(contact.fingerprint);
added.push(contact);
}
});
this._contacts.sort(this._identitySort.bind(this));
return added;
}
/**
* Marks the supplied contact as contacted
* @param {module:kdns/contacts~Contact} contact - Mark as contacted
* @returns {undefined}
*/
contacted(contact) {
this._contacted.add(contact.fingerprint);
}
/**
* Marks the supplied contact as active
* @param {module:kdns/contacts~Contact} contact - Mark as responded
* @returns {undefined}
*/
responded(contact) {
this._active.add(contact.fingerprint);
}
/**
* @private
*/
_identitySort(aContact, bContact) {
return compareKeyBuffers(
Buffer.from(getDistance(aContact.fingerprint, this.key), 'hex'),
Buffer.from(getDistance(bContact.fingerprint, this.key), 'hex')
);
}
}
module.exports.ContactList = ContactList;
module.exports.Contact = Contact;