Comparing version 1.1.3 to 2.0.1
{ | ||
"name": "ntp-time", | ||
"version": "1.1.3", | ||
"version": "2.0.1", | ||
"description": "A ntp library for nodejs", | ||
@@ -16,3 +16,3 @@ "main": "index.js", | ||
"devDependencies": { | ||
"jest": "^26.4.2" | ||
"jest": "^27.5.1" | ||
}, | ||
@@ -19,0 +19,0 @@ "scripts": { |
@@ -24,4 +24,4 @@ # ntp-time | ||
```javascript | ||
const NTP = require("ntp-time").Client; | ||
const client = new NTP("a.st1.ntp.br", 123, { timeout: 5000 }); | ||
const NTP = require('ntp-time').Client; | ||
const client = new NTP('a.st1.ntp.br', 123, { timeout: 5000 }); | ||
@@ -42,3 +42,3 @@ async function sync() { | ||
.syncTime() | ||
.then((time) => console.log(time)) // time is the whole NTP packet | ||
.then(time => console.log(time)) // time is the whole NTP packet | ||
.catch(console.log); | ||
@@ -54,3 +54,3 @@ ``` | ||
```javascript | ||
const NTPServer = require("ntp-time").Server; | ||
const NTPServer = require('ntp-time').Server; | ||
const server = new NTPServer(); | ||
@@ -60,12 +60,16 @@ | ||
server.handle((message, response) => { | ||
console.log("Server message:", message); | ||
message.transmitTimestamp = Date.now(); | ||
console.log('Server message:', message); | ||
message.transmitTimestamp = Math.floor(Date.now() / 1000); | ||
response(message); | ||
}); | ||
server.listen(3000, (err) => { | ||
// Check if node has the necessary permissions | ||
// to listen on ports less than 1024 | ||
// https://stackoverflow.com/questions/413807/is-there-a-way-for-non-root-processes-to-bind-to-privileged-ports-on-linux | ||
server.listen(123, err => { | ||
if (err) throw err; | ||
console.log("Server listening"); | ||
console.log('Server listening'); | ||
}); | ||
@@ -72,0 +76,0 @@ ``` |
@@ -1,19 +0,21 @@ | ||
const udp = require("dgram"); | ||
const udp = require('dgram'); | ||
const Packet = require("./packet"); | ||
const { NTPPacket, MODES } = require('./packet'); | ||
const NTP_DELTA = 2208988800; | ||
function createPacket() { | ||
const packet = new Packet(); | ||
const packet = new NTPPacket(MODES.CLIENT); | ||
packet.mode = Packet.MODES.CLIENT; | ||
packet.originateTimestamp = Date.now(); | ||
packet.originateTimestamp = Math.floor(Date.now() / 1000); | ||
return packet.toBuffer(); | ||
return packet.bufferize(packet); | ||
} | ||
function parse(buffer) { | ||
const message = Packet.parse(buffer); | ||
message.destinationTimestamp = Date.now(); | ||
message.time = new Date(message.transmitTimestamp); | ||
const message = NTPPacket.parse(buffer); | ||
message.destinationTimestamp = Math.floor(Date.now() / 1000) + NTP_DELTA; | ||
message.time = new Date(Math.floor((message.rxTimestamp - NTP_DELTA) * 1000)); | ||
// Timestamp Name ID When Generated | ||
@@ -26,4 +28,4 @@ // ------------------------------------------------------------ | ||
const T1 = message.originateTimestamp; | ||
const T2 = message.receiveTimestamp; | ||
const T3 = message.transmitTimestamp; | ||
const T2 = message.rxTimestamp; | ||
const T3 = message.txTimestamp; | ||
const T4 = message.destinationTimestamp; | ||
@@ -42,3 +44,3 @@ | ||
constructor( | ||
server = "pool.ntp.org", | ||
server = 'pool.ntp.org', | ||
port = 123, | ||
@@ -49,3 +51,3 @@ options = { timeout: 3000 } | ||
this.port = port; | ||
this.socket = udp.createSocket("udp4"); | ||
this.socket = udp.createSocket('udp4'); | ||
this.options = options; | ||
@@ -58,3 +60,3 @@ | ||
return new Promise((resolve, reject) => { | ||
this.socket = udp.createSocket("udp4"); | ||
this.socket = udp.createSocket('udp4'); | ||
@@ -69,3 +71,3 @@ const { | ||
this.socket.send(packet, 0, packet.length, port, server, (err) => { | ||
this.socket.send(packet, 0, packet.length, port, server, err => { | ||
if (err) return reject(err); | ||
@@ -81,3 +83,3 @@ | ||
this.socket.once("message", (data) => { | ||
this.socket.once('message', data => { | ||
clearTimeout(timer); | ||
@@ -88,2 +90,3 @@ | ||
this.socket.close(); | ||
return resolve(message); | ||
@@ -90,0 +93,0 @@ }); |
@@ -1,212 +0,139 @@ | ||
const assert = require("assert"); | ||
/** | ||
1 2 3 | ||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
|LI | VN |Mode | Stratum | Poll | Precision | | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
| Root Delay | | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
| Root Dispersion | | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
| Reference Identifier | | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
| | | ||
| Reference Timestamp (64) | | ||
| | | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
| | | ||
| Originate Timestamp (64) | | ||
| | | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
| | | ||
| Receive Timestamp (64) | | ||
| | | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
| | | ||
| Transmit Timestamp (64) | | ||
| | | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
| Key Identifier (optional) (32) | | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
| | | ||
| | | ||
| Message Digest (optional) (128) | | ||
| | | ||
| | | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
*/ | ||
function toMsecs(buffer, offset) { | ||
let seconds = 0; | ||
let fraction = 0; | ||
/** | ||
* 1900 ~ 1970 | ||
* @docs https://tools.ietf.org/html/rfc4330#section-3 | ||
*/ | ||
const SEVENTY_YEARS = 2208988800; | ||
for (let i = 0; i < 4; ++i) { | ||
seconds = seconds * 256 + buffer[offset + i]; | ||
} | ||
/** | ||
* @rfc https://tools.ietf.org/html/rfc4330 | ||
*/ | ||
class Packet { | ||
constructor() { | ||
Object.assign(this, { | ||
leapIndicator: 0, | ||
version: 4, | ||
mode: 3, | ||
stratum: 0, | ||
pollInterval: 6, | ||
precision: 236, | ||
referenceIdentifier: 0, | ||
referenceTimestamp: 0, | ||
originateTimestamp: 0, | ||
receiveTimestamp: 0, | ||
transmitTimestamp: 0 | ||
}); | ||
} | ||
for (let i = 4; i < 8; ++i) { | ||
fraction = fraction * 256 + buffer[offset + i]; | ||
} | ||
static parse(buffer) { | ||
assert.equal(buffer.length, 48, "Invalid Package"); | ||
const packet = new Packet(); | ||
packet.leapIndicator = buffer[0] >> 6; | ||
packet.version = (buffer[0] & 0x38) >> 3; | ||
packet.mode = buffer[0] & 0x7; | ||
packet.stratum = buffer[1]; | ||
packet.pollInterval = buffer[2]; | ||
packet.precision = buffer[3]; | ||
packet.rootDelay = buffer.slice(4, 8); | ||
packet.rootDispersion = buffer.slice(8, 12); | ||
packet.referenceIdentifier = buffer.slice(12, 16); | ||
packet.referenceTimestamp = toMsecs(buffer, 16); | ||
packet.originateTimestamp = toMsecs(buffer, 24); | ||
packet.receiveTimestamp = toMsecs(buffer, 32); | ||
packet.transmitTimestamp = toMsecs(buffer, 40); | ||
return packet; | ||
} | ||
return seconds + fraction / Math.pow(2, 32); | ||
} | ||
toBuffer() { | ||
const buffer = Buffer.alloc(48).fill(0x00); | ||
buffer[0] = 0; // 0b11100011; // LI, Version, Mode | ||
buffer[0] += this.leapIndicator << 6; | ||
buffer[0] += this.version << 3; | ||
buffer[0] += this.mode << 0; | ||
buffer[1] = this.stratum; | ||
buffer[2] = this.pollInterval; | ||
buffer[3] = this.precision; | ||
buffer.writeUInt32BE(this.rootDelay, 4); | ||
buffer.writeUInt32BE(this.rootDispersion, 8); | ||
buffer.writeUInt32BE(this.referenceIdentifier, 12); | ||
writeMsecs(buffer, 16, this.referenceTimestamp); | ||
writeMsecs(buffer, 24, this.originateTimestamp); | ||
writeMsecs(buffer, 32, this.receiveTimestamp); | ||
writeMsecs(buffer, 40, this.transmitTimestamp); | ||
return buffer; | ||
} | ||
const toFrac = ts => { | ||
return Math.floor(Math.abs(ts - Math.floor(ts)) * Math.pow(2, 32)); | ||
}; | ||
toJSON() { | ||
const output = Object.assign({}, this); | ||
const { leapIndicator, version } = this; | ||
output.version = version; | ||
output.leapIndicator = { | ||
0: "no-warning", | ||
1: "last-minute-61", | ||
2: "last-minute-59", | ||
3: "alarm" | ||
}[leapIndicator]; | ||
const sysToNTP = timestamp => timestamp + NTPPacket.NTP_DELTA; | ||
const { mode } = this; | ||
switch (mode) { | ||
case 1: | ||
output.mode = "symmetric-active"; | ||
break; | ||
case 2: | ||
output.mode = "symmetric-passive"; | ||
break; | ||
case 3: | ||
output.mode = "client"; | ||
break; | ||
case 4: | ||
output.mode = "server"; | ||
break; | ||
case 5: | ||
output.mode = "broadcast"; | ||
break; | ||
case 0: | ||
case 6: | ||
case 7: | ||
output.mode = "reserved"; | ||
break; | ||
} | ||
const writeInMillis = (buffer, offset, ts, addDelta) => { | ||
const seconds = addDelta ? ts + NTP_DELTA : ts; | ||
const fraction = toFrac(ts); | ||
const { stratum } = this; | ||
if (stratum === 0) { | ||
output.stratum = "death"; | ||
} else if (stratum === 1) { | ||
output.stratum = "primary"; | ||
} else if (stratum <= 15) { | ||
output.stratum = "secondary"; | ||
} else { | ||
output.stratum = "reserved"; | ||
} | ||
// seconds | ||
buffer[offset + 0] = (seconds & 0xff000000) >> 24; | ||
buffer[offset + 1] = (seconds & 0x00ff0000) >> 16; | ||
buffer[offset + 2] = (seconds & 0x0000ff00) >> 8; | ||
buffer[offset + 3] = seconds & 0x000000ff; | ||
output.referenceTimestamp = new Date(this.referenceTimestamp); | ||
output.originateTimestamp = new Date(this.originateTimestamp); | ||
output.receiveTimestamp = new Date(this.receiveTimestamp); | ||
output.transmitTimestamp = new Date(this.transmitTimestamp); | ||
output.destinationTimestamp = new Date(this.destinationTimestamp); | ||
return output; | ||
} | ||
} | ||
// 1 2 3 | ||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | ||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
// | Seconds | | ||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
// | Seconds Fraction (0-padded) | | ||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
// fraction | ||
buffer[offset + 4] = (fraction & 0xff000000) >> 24; | ||
buffer[offset + 5] = (fraction & 0x00ff0000) >> 16; | ||
buffer[offset + 6] = (fraction & 0x0000ff00) >> 8; | ||
buffer[offset + 7] = fraction & 0x000000ff; | ||
function toMsecs(buffer, offset) { | ||
let seconds = 0; | ||
let fraction = 0; | ||
for (let i = 0; i < 4; ++i) { | ||
seconds = seconds * 256 + buffer[offset + i]; | ||
} | ||
return buffer; | ||
}; | ||
for (let i = 4; i < 8; ++i) { | ||
fraction = fraction * 256 + buffer[offset + i]; | ||
} | ||
const MODES = { | ||
CLIENT: 3, | ||
SERVER: 4 | ||
}; | ||
return (seconds - SEVENTY_YEARS + fraction / Math.pow(2, 32)) * 1000; | ||
} | ||
const NTP_DELTA = 2208988800; | ||
function writeMsecs(buffer, offset, ts) { | ||
// const buffer = Buffer.alloc(8); // 64bits | ||
const seconds = Math.floor(ts / 1000) + SEVENTY_YEARS; | ||
const fraction = Math.round(((ts % 1000) / 1000) * Math.pow(2, 32)); | ||
// seconds | ||
buffer[offset + 0] = (seconds & 0xff000000) >> 24; | ||
buffer[offset + 1] = (seconds & 0x00ff0000) >> 16; | ||
buffer[offset + 2] = (seconds & 0x0000ff00) >> 8; | ||
buffer[offset + 3] = seconds & 0x000000ff; | ||
// fraction | ||
buffer[offset + 4] = (fraction & 0xff000000) >> 24; | ||
buffer[offset + 5] = (fraction & 0x00ff0000) >> 16; | ||
buffer[offset + 6] = (fraction & 0x0000ff00) >> 8; | ||
buffer[offset + 7] = fraction & 0x000000ff; | ||
return buffer; | ||
class NTPPacket { | ||
constructor(mode) { | ||
Object.assign(this, { | ||
mode: mode || 4, | ||
leap: 0, | ||
version: 3, | ||
stratum: 0, | ||
poll: 0, | ||
precision: 0, | ||
rootDelay: 0, | ||
rootDispersion: 0, | ||
referenceId: 0, | ||
referenceTimestamp: 0, | ||
originateTimestamp: 0, | ||
rxTimestamp: 0, | ||
txTimestamp: 0 | ||
}); | ||
} | ||
static parse(data) { | ||
if (data.length < 48) { | ||
throw new Error('Invalid NTP Package'); | ||
} | ||
const packet = new NTPPacket(4); | ||
// Control bytes | ||
packet.leap = (data[0] >> 6) & 0x3; | ||
packet.version = (data[0] >> 3) & 0x7; | ||
packet.mode = data[0] & 0x7; | ||
packet.stratum = parseInt(data[1]) || 2; | ||
packet.poll = parseInt(data[2]) || 10; | ||
packet.precision = parseInt(data[3]); | ||
packet.rootDelay = data.slice(4, 8).readFloatBE(0) / 2 ** 16; | ||
packet.rootDispersion = data.slice(8, 12).readFloatBE(0) / 2 ** 16; | ||
packet.referenceId = data.slice(12, 16); | ||
// Timestamps where the 4 first bytes are the | ||
// int part and the 4 last are the frac part | ||
// const refTimestampHigh = data.slice(16, 20).readUint32BE(); | ||
// const refTimestampLow = data.slice(20, 24).readFloatBE(); | ||
packet.referenceTimestamp = toMsecs(data, 16); | ||
// const origTimestampHigh = data.slice(24, 28).readUint32BE(); | ||
// const origTimestampLow = data.slice(28, 32).readUint32BE(); | ||
packet.originateTimestamp = toMsecs(data, 24); | ||
// const rxTimestampHigh = data.slice(32, 36).readUint32BE(); | ||
// const rxTimestampLow = data.slice(36, 40).readUint32BE(); | ||
packet.rxTimestamp = toMsecs(data, 32); | ||
// const txTimestampHigh = data.slice(40, 44).readUint32BE(); | ||
// const txTimestampLow = data.slice(44, 48).readUint32BE(); | ||
packet.txTimestamp = toMsecs(data, 40); | ||
return packet; | ||
} | ||
bufferize(packet) { | ||
const buffer = Buffer.alloc(48).fill(0x00); | ||
buffer[0] = (packet.leap << 6) | (packet.version << 3) | (this.mode << 0); | ||
buffer[1] = packet.stratum; | ||
buffer[2] = packet.poll; | ||
buffer[3] = packet.precision; | ||
buffer.writeUInt32BE(packet.rootDelay, 4); | ||
buffer.writeUInt32BE(packet.rootDispersion, 8); | ||
buffer.writeUInt32BE(packet.referenceId, 12); | ||
// Reference Timestamp | ||
writeInMillis(buffer, 16, packet.referenceTimestamp, true); | ||
// Originate timestamp | ||
writeInMillis( | ||
buffer, | ||
24, | ||
packet.originateTimestamp, | ||
this.mode !== MODES.SERVER // Don't add NTP_DELTA if the packet is server mode | ||
); | ||
// RX Timestamp | ||
writeInMillis(buffer, 32, packet.rxTimestamp, true); | ||
// TX Timestamp | ||
writeInMillis(buffer, 40, packet.txTimestamp, true); | ||
return buffer; | ||
} | ||
} | ||
// Mode Meaning | ||
// ------------------------------------ | ||
// 0 reserved | ||
// 1 symmetric active | ||
// 2 symmetric passive | ||
// 3 client | ||
// 4 server | ||
// 5 broadcast | ||
// 6 reserved for NTP control message | ||
// 7 reserved for private use | ||
Packet.MODES = { | ||
CLIENT: 3, | ||
SERVER: 4 | ||
}; | ||
module.exports = Packet; | ||
module.exports = { NTPPacket, sysToNTP, MODES }; |
@@ -1,4 +0,4 @@ | ||
const udp = require("dgram"); | ||
const EventEmitter = require("events"); | ||
const Packet = require("./packet"); | ||
const udp = require('dgram'); | ||
const EventEmitter = require('events'); | ||
const { NTPPacket, MODES } = require('./packet'); | ||
@@ -9,4 +9,4 @@ class Server extends EventEmitter { | ||
this.socket = udp.createSocket("udp4"); | ||
this.socket.on("message", this.parse.bind(this)); | ||
this.socket = udp.createSocket('udp4'); | ||
this.socket.on('message', this.parse.bind(this)); | ||
@@ -17,13 +17,15 @@ return this; | ||
handle(handler) { | ||
this.on("request", handler); | ||
this.on('request', handler); | ||
} | ||
send(rinfo, message, callback) { | ||
if (message instanceof Packet) { | ||
message.mode = Packet.MODES.SERVER; // mark mode as server | ||
message = message.toBuffer(); | ||
if (message instanceof NTPPacket) { | ||
const sendPackage = new NTPPacket(MODES.SERVER).bufferize(message); | ||
this.socket.send(sendPackage, rinfo.port, rinfo.server, callback); | ||
return this; | ||
} else { | ||
throw new Error('Invalid response packet'); | ||
} | ||
this.socket.send(message, rinfo.port, rinfo.server, callback); | ||
return this; | ||
} | ||
@@ -42,6 +44,11 @@ | ||
parse(message, rinfo) { | ||
const packet = Packet.parse(message); | ||
packet.receiveTimestamp = Date.now(); | ||
const packet = NTPPacket.parse(message); | ||
const rxTimestamp = Math.floor(Date.now() / 1000); | ||
this.emit("request", packet, this.send.bind(this, rinfo)); | ||
packet.originateTimestamp = Math.floor(packet.txTimestamp); | ||
packet.referenceTimestamp = rxTimestamp - 5; | ||
packet.rxTimestamp = rxTimestamp; | ||
this.emit('request', packet, this.send.bind(this, rinfo)); | ||
return; | ||
@@ -48,0 +55,0 @@ } |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
75
11049
255
3