Skip to content

Commit bce01ea

Browse files
committed
Initial commit
1 parent 6acda63 commit bce01ea

File tree

11 files changed

+2217
-0
lines changed

11 files changed

+2217
-0
lines changed

.babelrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"presets": ["es2015"]
3+
}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
dist

.npmignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/index.js

.travis.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
language: node_js
2+
3+
cache:
4+
yarn: true
5+
directories:
6+
- node_modules
7+
8+
script: yarn test

README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
![NPM downloads](https://img.shields.io/npm/dt/ntp-packet-parser.svg)
2+
![Travis](https://img.shields.io/travis/buffcode/npm-packet-parser.svg)
3+
![GitHub release](https://img.shields.io/github/release/buffcode/npm-packet-parser.svg)
4+
![Dependencies](https://img.shields.io/david/buffcode/npm-packet-parser.svg)
5+
6+
# NTP packet parser
7+
8+
Library to parse NTP response packets according to [RFC 5905](https://www.ietf.org/rfc/rfc5905.txt) into a easy-to-use structure.
9+
It does not apply any validations or calculations regarding the time but solely parses the data.
10+
11+
## Installation
12+
```bash
13+
yarn add ntp-packet-parser
14+
```
15+
16+
## Usage
17+
### ES6 style
18+
```js
19+
import NtpPacketParser from "ntp-packet-parser";
20+
21+
/** const udpPacket = new Buffer(...); **/
22+
const result = NtpPacketParser.parse(udpPacket);
23+
```
24+
25+
### Legacy style
26+
```js
27+
var NtpPacketParser = require("ntp-packet-parser");
28+
29+
/** const udpPacket = new Buffer(...); **/
30+
var result = NtpPacketParser.parse(udpPacket);
31+
```
32+
33+
## Structure
34+
The response from `NtpPacketParser.parse` will always return the following object structure:
35+
36+
| Property | Type | Description |
37+
| :--- | :--- | :--- |
38+
| leapIndicator | `Integer` | Warning of an impending leap second to be inserted or deleted in the last minute of the current month |
39+
| version | `Integer` | NTP version number, currently 4 |
40+
| mode | `Integer` | Request/response mode |
41+
| stratum | `Integer` | Stratum of the server |
42+
| poll | `Integer` | Integer representing the maximum interval between successive messages (Note: you need to apply `Math.log2` to get the real value) |
43+
| precision | `Integer` | Integer representing the precision of the system clock (Note: you need to apply `Math.log2` to get the real value) |
44+
| rootDelay | `Date` | Total round-trip delay to the reference clock |
45+
| rootDispersion | `Date` | Total dispersion to the reference clock |
46+
| referenceId | `String` | String to identify the particular server or reference clock |
47+
| referenceTimestamp | `Date` | Time when the system clock was last set or corrected |
48+
| originTimestamp | `Date` | Time at the client when the request departed for the server |
49+
| receiveTimestamp | `Date` | Time at the server when the request arrived from the client |
50+
| transmitTimestamp | `Date` | Time at the server when the response left for the client |
51+
52+
To get the relative time for any Date property, calculate the difference between "Jan 01 1900 GMT" and the given date.
53+
54+
```js
55+
let rootDelayInMilliseconds = result.rootDelay.getTime() - new Date("Jan 01 1900 GMT").getTime();
56+
```
57+
58+
For further explanations on the possible values of these properties please refer to [RFC 5909](https://www.ietf.org/rfc/rfc5905.txt), Page 19ff.
59+
60+
## Testing
61+
Some tests regarding structure, response and error handling exist. To run them locally:
62+
```js
63+
yarn test
64+
```
65+
66+
## Contributing
67+
Please file a PR against `master`.
68+
69+
## License
70+
[GNU General Public License Version 3](LICENSE)

index.js

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
"use strict";
2+
3+
/**
4+
* @typedef {Object} NTPPacket
5+
* @property {int} leapIndicator
6+
* @property {int} version
7+
* @property {int} mode
8+
* @property {int} stratum
9+
* @property {int} poll
10+
* @property {int} precision
11+
* @property {Date} rootDelay
12+
* @property {Date} rootDispersion
13+
* @property {String} referenceId
14+
* @property {Date} referenceTimestamp
15+
* @property {Date} originTimestamp
16+
* @property {Date} receiveTimestamp
17+
* @property {Date} transmitTimestamp
18+
*/
19+
20+
export default class NtpPacketParser {
21+
/**
22+
* Returns the structure of the UDP packet for parsing
23+
* @returns {Object}
24+
*/
25+
static get packetStruct() {
26+
return [
27+
{ name: "leapIndicator", bits: 2 },
28+
{ name: "version", bits: 3 },
29+
{ name: "mode", bits: 3 },
30+
{ name: "stratum", bits: 8 },
31+
{ name: "poll", bits: 8 },
32+
{ name: "precision", bits: 8 },
33+
{
34+
name: "rootDelay",
35+
bits: 32,
36+
converter: NtpPacketParser._fromNtpTimestamp
37+
},
38+
{
39+
name: "rootDispersion",
40+
bits: 32,
41+
converter: NtpPacketParser._fromNtpTimestamp
42+
},
43+
{
44+
name: "referenceId",
45+
bits: 32,
46+
converter: (v, s) => this._ntpIdentifier(s.stratum, v)
47+
},
48+
{
49+
name: "referenceTimestamp",
50+
bits: 64,
51+
converter: NtpPacketParser._fromNtpTimestamp
52+
},
53+
{
54+
name: "originTimestamp",
55+
bits: 64,
56+
converter: NtpPacketParser._fromNtpTimestamp
57+
},
58+
{
59+
name: "receiveTimestamp",
60+
bits: 64,
61+
converter: NtpPacketParser._fromNtpTimestamp
62+
},
63+
{
64+
name: "transmitTimestamp",
65+
bits: 64,
66+
converter: NtpPacketParser._fromNtpTimestamp
67+
}
68+
];
69+
}
70+
71+
/**
72+
* Returns the selected bits in binary notation
73+
* @param msg
74+
* @param {int} start
75+
* @param {int} length
76+
* @returns {string} Bits in binary notation
77+
* @private
78+
*/
79+
static _getBits(msg, start, length) {
80+
let bits = "";
81+
const pad = "00000000";
82+
83+
for (let i = 0; i < msg.length; i++) {
84+
let bitsUnpadded = (msg[i] >>> 0).toString(2);
85+
bits += pad.substring(0, pad.length - bitsUnpadded.length) + bitsUnpadded;
86+
}
87+
88+
return bits.slice(start, start + length);
89+
}
90+
91+
/**
92+
* Converts a NTP identifier from binary notation to ASCII
93+
* @param {int} stratum
94+
* @param {String} value Bits in binary notation
95+
* @returns {string}
96+
* @private
97+
*/
98+
static _ntpIdentifier(stratum, value) {
99+
if (stratum != 1) {
100+
return parseInt(value, 2).toString();
101+
}
102+
let chars = [
103+
value.slice(0, 8),
104+
value.slice(8, 16),
105+
value.slice(16, 24),
106+
value.slice(24, 32)
107+
];
108+
109+
chars = chars.map(function(v) {
110+
return String.fromCharCode(parseInt(v, 2));
111+
});
112+
113+
return chars.join("").replace(/\0+$/, "");
114+
}
115+
116+
/**
117+
* Converts a NTP timestamp from binary notation to a Date object
118+
* @param {String} value Bits in binary notation
119+
* @returns {Date}
120+
* @private
121+
*/
122+
static _fromNtpTimestamp(value) {
123+
if (value.length % 2 !== 0) {
124+
throw new Error(
125+
"Invalid timestamp format, expected even number of characters"
126+
);
127+
}
128+
129+
const seconds = parseInt(value, 2) / Math.pow(2, value.length / 2),
130+
date = new Date("Jan 01 1900 GMT");
131+
132+
date.setUTCMilliseconds(date.getUTCMilliseconds() + seconds * 1000);
133+
134+
return date;
135+
}
136+
137+
/**
138+
* Parses an UDP packet buffer and returns a NTPPacket struct
139+
* @param {Buffer} udpPacket
140+
* @returns {NTPPacket}
141+
*/
142+
static parse(udpPacket) {
143+
let data = [];
144+
NtpPacketParser.packetStruct.forEach(item => {
145+
data[item.name] = undefined;
146+
});
147+
148+
let offset = 0;
149+
NtpPacketParser.packetStruct.forEach(item => {
150+
data[item.name] = NtpPacketParser._getBits(udpPacket, offset, item.bits);
151+
if (item.converter) {
152+
data[item.name] = item.converter(data[item.name], data);
153+
} else {
154+
data[item.name] = parseInt(data[item.name], 2);
155+
}
156+
offset += item.bits;
157+
});
158+
159+
return data;
160+
}
161+
}

package.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "ntp-packet-parser",
3+
"version": "0.1",
4+
"description": "A parser for NTP UDP packets",
5+
"main": "dist/index.js",
6+
"scripts": {
7+
"test": "mocha --compilers js:babel-core/register --recursive test",
8+
"build": "babel index.js --presets babel-preset-es2015 --out-dir dist && prettier --write dist/index.js",
9+
"prettier": "prettier --write index.js test/**/*.js test/**/*.json",
10+
"prettier:lint": "prettier --list-different index.js"
11+
},
12+
"keywords": [
13+
"ntp",
14+
"clock",
15+
"sync",
16+
"parser",
17+
"udp"
18+
],
19+
"author": "Laurens Stötzel",
20+
"repository": {
21+
"type": "git",
22+
"url": "https://github.com/buffcode/ntp-packet-parser.git"
23+
},
24+
"devDependencies": {
25+
"babel-cli": "6",
26+
"babel-preset-es2015": "6",
27+
"mocha": "^3.5.0",
28+
"prettier": "^1.6.1"
29+
}
30+
}

test/packets.valid.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
module.exports = [
2+
{
3+
buffer: [
4+
28,
5+
1,
6+
48,
7+
234,
8+
0,
9+
0,
10+
0,
11+
0,
12+
0,
13+
0,
14+
0,
15+
68,
16+
80,
17+
84,
18+
66,
19+
0,
20+
221,
21+
82,
22+
40,
23+
120,
24+
122,
25+
239,
26+
46,
27+
145,
28+
48,
29+
48,
30+
48,
31+
48,
32+
48,
33+
48,
34+
48,
35+
10,
36+
221,
37+
82,
38+
40,
39+
124,
40+
17,
41+
15,
42+
55,
43+
20,
44+
221,
45+
82,
46+
40,
47+
124,
48+
17,
49+
25,
50+
203,
51+
213
52+
],
53+
expected: {
54+
leapIndicator: 0,
55+
version: 3,
56+
mode: 4,
57+
stratum: 1,
58+
poll: 48,
59+
precision: 234,
60+
rootDelay: new Date("1900-01-01T00:00:00.000Z"),
61+
rootDispersion: new Date("1900-01-01T00:00:00.001Z"),
62+
referenceId: "PTB",
63+
referenceTimestamp: new Date("2017-08-31T06:17:28.480Z"),
64+
originTimestamp: new Date("1925-08-15T05:27:12.188Z"),
65+
receiveTimestamp: new Date("2017-08-31T06:17:32.066Z"),
66+
transmitTimestamp: new Date("2017-08-31T06:17:32.066Z")
67+
}
68+
}
69+
];

0 commit comments

Comments
 (0)