/*!
* timedata.js - time management for hsd
* Copyright (c) 2017-2018, Christopher Jeffrey (MIT License).
* https://github.com/handshake-org/hsd
*/
'use strict';
const EventEmitter = require('events');
const util = require('../utils/util');
const binary = require('../utils/binary');
/**
* Time Data
* An object which handles "adjusted time". This may not
* look it, but this is actually a semi-consensus-critical
* piece of code. It handles version packets from peers
* and calculates what to offset our system clock's time by.
* @alias module:protocol.TimeData
* @extends EventEmitter
* @property {Array} samples
* @property {Object} known
* @property {Number} limit
* @property {Number} offset
*/
class TimeData extends EventEmitter {
/**
* Create time data.
* @constructor
* @param {Number} [limit=200]
*/
constructor(limit) {
super();
if (limit == null)
limit = 200;
this.samples = [];
this.known = new Map();
this.limit = limit;
this.offset = 0;
this.checked = false;
}
/**
* Add time data.
* @param {String} id
* @param {Number} time
*/
add(id, time) {
if (this.samples.length >= this.limit)
return;
if (this.known.has(id))
return;
const sample = time - util.now();
this.known.set(id, sample);
binary.insert(this.samples, sample, compare);
this.emit('sample', sample, this.samples.length);
if (this.samples.length >= 5 && this.samples.length % 2 === 1) {
let median = this.samples[this.samples.length >>> 1];
if (Math.abs(median) >= 70 * 60) {
if (!this.checked) {
let match = false;
for (const offset of this.samples) {
if (offset !== 0 && Math.abs(offset) < 5 * 60) {
match = true;
break;
}
}
if (!match) {
this.checked = true;
this.emit('mismatch');
}
}
median = 0;
}
this.offset = median;
this.emit('offset', this.offset);
}
}
/**
* Get the current adjusted time.
* @returns {Number} Adjusted Time.
*/
now() {
return util.now() + this.offset;
}
/**
* Adjust a timestamp.
* @param {Number} time
* @returns {Number} Adjusted Time.
*/
adjust(time) {
return time + this.offset;
}
/**
* Unadjust a timestamp.
* @param {Number} time
* @returns {Number} Local Time.
*/
local(time) {
return time - this.offset;
}
/**
* Get the current adjusted time in milliseconds.
* @returns {Number} Adjusted Time.
*/
ms() {
return Date.now() + this.offset * 1000;
}
}
/*
* Helpers
*/
function compare(a, b) {
return a - b;
}
/*
* Expose
*/
module.exports = TimeData;