From cc5dab4da03ffd6579436cc117d37a6575995598 Mon Sep 17 00:00:00 2001 From: ahrazzle <38987266+ahrazzle@users.noreply.github.com> Date: Tue, 27 Oct 2026 23:01:34 -0500 Subject: [PATCH] Add draft NTP wire contract and executable conformance vectors --- .github/workflows/wire-vectors.yml | 10 +++ .gitignore | 2 + README.md | 15 ++++- WIRE-CONTRACT.md | 24 +++++++ tests/check_vectors.py | 41 ++++++++++++ tests/test_wire_oracle.py | 96 +++++++++++++++++++++++++++ tests/wire_oracle.py | 102 +++++++++++++++++++++++++++++ tests/wire_vectors.jsonl | 79 ++++++++++++++++++++++ 8 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/wire-vectors.yml create mode 100644 WIRE-CONTRACT.md create mode 100644 tests/check_vectors.py create mode 100644 tests/test_wire_oracle.py create mode 100644 tests/wire_oracle.py create mode 100644 tests/wire_vectors.jsonl diff --git a/.github/workflows/wire-vectors.yml b/.github/workflows/wire-vectors.yml new file mode 100644 index 0000000..f33a149 --- /dev/null +++ b/.github/workflows/wire-vectors.yml @@ -0,0 +1,10 @@ +name: Draft wire vectors +on: [push, pull_request] +jobs: + vectors: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - run: python3 -m unittest discover -s tests -v diff --git a/.gitignore b/.gitignore index 01f8cdb..69e4f22 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .lake/ + +__pycache__/ diff --git a/README.md b/README.md index fba35fb..9966044 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,20 @@ A Lean parser for the NTP wire packet. This is a Proven Commons project: it rebuilds a widely used format in Lean and proves the new code follows its written specification. -Status: not verified. No code has landed yet. Plan issue 1 holds the first slice, and it is unclaimed. The specification has not been approved by a reviewer, so do not call this verified. +Status: not verified. The first parser slice is claimed in plan issue 1 and remains under its author's ownership. The specification has not been approved by a reviewer. + +## Wire contract under review + +[WIRE-CONTRACT.md](WIRE-CONTRACT.md) proposes a contract for the parser author to review. It comes with 79 fixed packet vectors, a Python reference oracle, and a JSON adapter runner. It adds no Lean implementation or proof. The protocol contract still needs independent review before integration. + +Run the fixtures and boundary checks: + +``` +python3 -m unittest discover -s tests -v +python3 tests/check_vectors.py +``` + +A future parser adapter can consume the same fixtures with `python3 tests/check_vectors.py -- COMMAND ARGUMENTS`. The runner checks every response and fails on a missing answer, wrong answer, nonzero exit, or timeout. ## First slice diff --git a/WIRE-CONTRACT.md b/WIRE-CONTRACT.md new file mode 100644 index 0000000..a67ffee --- /dev/null +++ b/WIRE-CONTRACT.md @@ -0,0 +1,24 @@ +# Draft wire contract + +This is a review target for the existing parser author. Python fixtures and an executable oracle are included; no Lean parser or proof is added. Issue 1 remains open. + +The header follows [RFC 5905 sections 6 and 7.3](https://www.rfc-editor.org/rfc/rfc5905.html#section-7.3). Offsets are bytes, integers are big endian, and timestamp order is reference, origin, receive, transmit. + +| Offset | Bytes | Field | +|---|---|---| +| 0 | 1 | Leap: bits 7–6; version: 5–3; mode: 2–0 | +| 1 | 1 | Stratum | +| 2 | 1 | Signed poll exponent | +| 3 | 1 | Signed precision exponent | +| 4 | 4 | Root delay bits | +| 8 | 4 | Root dispersion bits | +| 12 | 4 | Reference identifier bytes | +| 16, 24, 32, 40 | 8 each | Seconds (32 bits), fraction (32 bits) | + +Version 3 or 4 is this draft's supported set. All modes and strata are preserved; clock-selection validity is separate. Root fixed-point fields remain raw bits. A zero 64-bit timestamp means unknown. A nonzero fraction with zero seconds is known. Conversion requires an external integer era and returns exact rational Unix seconds. No era is guessed from the clock or packet. + +Extension framing follows [RFC 7822 section 3](https://www.rfc-editor.org/rfc/rfc7822.html#section-3). Each extension includes its four-byte header in its length, is at least 16 bytes, has four-byte alignment, and stays before the MAC. Without a MAC, the final extension is at least 28 bytes. Unknown types remain opaque, including padding. + +The caller supplies `mac_length`: 0, 4, 20, or 24. The oracle preserves those bytes, including a four-byte crypto-NAK, without authenticating them. It does not guess whether a 20-byte tail is an extension or MAC. Negotiated longer MACs and extension-specific requirements need separate context and are outside this contract. + +Run `python3 -m unittest discover -s tests -v`. Feed JSON lines to `python3 tests/wire_oracle.py`. Each fixture has `id`, `request`, and a complete `expect` response. A future parser adapter can use this schema unchanged. Independent review of this contract is required before integration. diff --git a/tests/check_vectors.py b/tests/check_vectors.py new file mode 100644 index 0000000..d88c82e --- /dev/null +++ b/tests/check_vectors.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +"""Run the fixed contract vectors against a JSON-line parser adapter.""" + +import argparse +import json +from pathlib import Path +import subprocess +import sys + + +def main(): + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument('command', nargs=argparse.REMAINDER, + help='adapter command, optionally after --') + args = ap.parse_args() + command = args.command + if command[:1] == ['--']: + command = command[1:] + if not command: + command = [sys.executable, str(Path(__file__).with_name('wire_oracle.py'))] + cases = [json.loads(line) for line in Path(__file__).with_name('wire_vectors.jsonl').read_text().splitlines()] + try: + run = subprocess.run(command, input=''.join(json.dumps(c['request'])+'\n' for c in cases), + capture_output=True, text=True, timeout=30) + if run.returncode: + raise ValueError(f'adapter exit {run.returncode}: {run.stderr[:300]}') + answers = [json.loads(line) for line in run.stdout.splitlines()] + if len(answers) != len(cases): + raise ValueError(f'{len(answers)} answers for {len(cases)} vectors') + failures = [c['id'] for c, got in zip(cases, answers) if got != c['expect']] + except (OSError, subprocess.TimeoutExpired, ValueError) as e: + print(f'FAIL: {e}', file=sys.stderr) + return 1 + for name in failures: + print(f'FAIL: {name}', file=sys.stderr) + print(f'{len(cases)-len(failures)}/{len(cases)} wire vectors agree') + return bool(failures) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tests/test_wire_oracle.py b/tests/test_wire_oracle.py new file mode 100644 index 0000000..e846170 --- /dev/null +++ b/tests/test_wire_oracle.py @@ -0,0 +1,96 @@ +import json +from pathlib import Path +import random +import struct +import subprocess +import sys +import unittest +from fractions import Fraction + +from wire_oracle import WireError, answer, decode, read_header, timestamp_unix + +HERE = Path(__file__).parent + + +class WireContractTests(unittest.TestCase): + def test_fixed_vectors(self): + for line in (HERE / 'wire_vectors.jsonl').read_text().splitlines(): + case = json.loads(line) + with self.subTest(case=case['id']): + self.assertEqual(answer(case['request']), case['expect']) + + def test_header_against_struct_unpack(self): + rng = random.Random(20261028) + for _ in range(2000): + raw = bytearray(rng.randbytes(48)) + raw[0] = (raw[0] & 0xc7) | (rng.choice([3, 4]) << 3) + first, stratum, poll, precision, delay, dispersion, ref, *stamps = struct.unpack('!BBbbII4s4Q', raw) + got = read_header(raw) + self.assertEqual([got[k] for k in ('leap','version','mode','stratum','poll','precision')], + [first >> 6, (first >> 3) & 7, first & 7, stratum, poll, precision]) + self.assertEqual((got['root_delay_bits'], got['root_dispersion_bits'], got['reference_id_hex']), + (delay, dispersion, ref.hex())) + self.assertEqual([s['raw'] for s in got['timestamps']], stamps) + + def test_explicit_era_and_unknown(self): + for era in (-1, 0, 1, 2): + self.assertIsNone(timestamp_unix(0, era)) + self.assertEqual(timestamp_unix(1, era), Fraction(era * 2**32 - 2208988800) + Fraction(1, 2**32)) + self.assertEqual(timestamp_unix(2208988800 << 32, 0), 0) + self.assertEqual(timestamp_unix(1, 1)-timestamp_unix(1, 0), 2**32) + for era in (None, True, 1.5): + with self.assertRaises(WireError): + timestamp_unix(1, era) + for raw in (-1, 2**64, True): + with self.assertRaises(WireError): + timestamp_unix(raw, 0) + + def test_all_extension_lengths_at_boundary(self): + header = bytes.fromhex('23' + '00'*47) + for length in range(0, 81): + field = b'\xfe\xfe' + length.to_bytes(2, 'big') + bytes(max(0, length-4)) + for mac in (0, 4, 20, 24): + packet = header + field + bytes(mac) + expected = length >= (16 if mac else 28) and length % 4 == 0 + try: + got = decode(packet, mac) + except WireError: + self.assertFalse(expected, (length, mac)) + else: + self.assertTrue(expected, (length, mac)) + self.assertEqual(got['extensions'][0]['length'], length) + self.assertEqual(got['mac_length'], mac) + # A maximum-size aligned extension; no heuristic 24-byte-tail cutoff. + field = b'\xfe\xfe\xff\xfc' + bytes(65528) + self.assertEqual(decode(header + field, 0)['extensions'][0]['length'], 65532) + + def test_mac_ambiguity_is_context(self): + header = bytes.fromhex('23' + '00'*47) + tail = b'\xfe\xfe\x00\x14' + bytes(16) + self.assertEqual(decode(header+tail, 20)['mac_hex'], tail.hex()) + with self.assertRaisesRegex(WireError, 'short_final_extension_without_mac'): + decode(header+tail, 0) + for n in (-1, 8, 16, 28, None, True): + self.assertEqual(answer({'packet_hex':header.hex(), 'mac_length':n}), + {'error':'unsupported_mac_length'}) + + def test_cli_and_adapter_failure_detection(self): + good = subprocess.run([sys.executable, str(HERE/'check_vectors.py')], capture_output=True) + self.assertEqual(good.returncode, 0, good.stderr) + bad = subprocess.run([sys.executable, str(HERE/'check_vectors.py'), '--', sys.executable, + '-c', 'print("{}")'], capture_output=True) + self.assertNotEqual(bad.returncode, 0) + self.assertIn(b'answers for', bad.stderr) + wrong = subprocess.run([sys.executable, str(HERE/'check_vectors.py'), '--', sys.executable, + '-c', 'import sys; [print("{}") for line in sys.stdin]'], + capture_output=True) + self.assertNotEqual(wrong.returncode, 0) + self.assertIn(b'header-distinct-fields', wrong.stderr) + + def test_malformed_requests(self): + for request in (None, [], {}, {'packet_hex':'xx','mac_length':0}): + self.assertEqual(answer(request), {'error':'malformed_request'}) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/wire_oracle.py b/tests/wire_oracle.py new file mode 100644 index 0000000..97f23c0 --- /dev/null +++ b/tests/wire_oracle.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +"""Draft NTP wire-contract oracle. No authentication or era inference. + +JSON lines in: {"packet_hex": "...", "mac_length": 0}. +JSON lines out: the fixture's expected object, or {"error": "..."}. +RFC 5905 sections 6/7.3; RFC 7822 section 3. See WIRE-CONTRACT.md. +""" + +import json +import sys +from fractions import Fraction + + +class WireError(ValueError): + """An input outside this explicit draft contract.""" + + +def read_header(packet): + if len(packet) < 48: + raise WireError("short_header") + first = packet[0] + if (first >> 3) & 7 not in (3, 4): + raise WireError("unsupported_version") + signed = lambda n: n if n < 128 else n - 256 + timestamps = [] + for offset in (16, 24, 32, 40): + raw = int.from_bytes(packet[offset:offset + 8], "big") + timestamps.append({"raw": raw, "seconds": raw >> 32, + "fraction": raw & 0xffffffff, "unknown": raw == 0}) + return {"leap": first >> 6, "version": (first >> 3) & 7, "mode": first & 7, + "stratum": packet[1], "poll": signed(packet[2]), + "precision": signed(packet[3]), + "root_delay_bits": int.from_bytes(packet[4:8], "big"), + "root_dispersion_bits": int.from_bytes(packet[8:12], "big"), + "reference_id_hex": packet[12:16].hex(), "timestamps": timestamps} + + +def timestamp_unix(raw, era): + """Exact Unix seconds, given an external era, or None for unknown.""" + if type(raw) is not int or not 0 <= raw < 2 ** 64: + raise WireError("timestamp_range") + if type(era) is not int: + raise WireError("era_required") + if raw == 0: + return None + return Fraction(era * 2 ** 32 + (raw >> 32) - 2208988800) + Fraction(raw & 0xffffffff, 2 ** 32) + + +def decode(packet, mac_length): + """Frame using caller-supplied MAC length; never guess from tail bytes.""" + if type(mac_length) is not int or mac_length not in (0, 4, 20, 24): + raise WireError("unsupported_mac_length") + header = read_header(packet) + end = len(packet) - mac_length + if end < 48: + raise WireError("short_mac") + offset = 48 + extensions = [] + while offset < end: + if end - offset < 4: + raise WireError("short_extension_header") + field_type = int.from_bytes(packet[offset:offset + 2], "big") + length = int.from_bytes(packet[offset + 2:offset + 4], "big") + if length < 16: + raise WireError("short_extension") + if length % 4: + raise WireError("unaligned_extension") + if length > end - offset: + raise WireError("extension_overrun") + extensions.append({"offset": offset, "type": field_type, "length": length, + "value_and_padding_hex": packet[offset + 4:offset + length].hex()}) + offset += length + if not mac_length and extensions and extensions[-1]["length"] < 28: + raise WireError("short_final_extension_without_mac") + return {"header": header, "extensions": extensions, + "mac_hex": packet[end:].hex() if mac_length else "", + "mac_length": mac_length} + + +def answer(request): + try: + packet = bytes.fromhex(request["packet_hex"]) + return decode(packet, request["mac_length"]) + except WireError as e: + return {"error": str(e)} + except (KeyError, TypeError, ValueError): + return {"error": "malformed_request"} + + +def main(): + for line in sys.stdin: + try: + request = json.loads(line) + except ValueError: + result = {"error": "malformed_request"} + else: + result = answer(request) + print(json.dumps(result, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/tests/wire_vectors.jsonl b/tests/wire_vectors.jsonl new file mode 100644 index 0000000..4dddb58 --- /dev/null +++ b/tests/wire_vectors.jsonl @@ -0,0 +1,79 @@ +{"expect": {"extensions": [], "header": {"leap": 0, "mode": 3, "poll": -128, "precision": -1, "reference_id_hex": "52415445", "root_delay_bits": 2147549186, "root_dispersion_bits": 4294901761, "stratum": 16, "timestamps": [{"fraction": 0, "raw": 0, "seconds": 0, "unknown": true}, {"fraction": 1, "raw": 1, "seconds": 0, "unknown": false}, {"fraction": 0, "raw": 9223372036854775808, "seconds": 2147483648, "unknown": false}, {"fraction": 4294967295, "raw": 18446744073709551615, "seconds": 4294967295, "unknown": false}], "version": 4}, "mac_hex": "", "mac_length": 0}, "id": "header-distinct-fields", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000ffffffffffffffff"}} +{"expect": {"error": "short_header"}, "id": "header-short-0", "request": {"mac_length": 0, "packet_hex": ""}} +{"expect": {"error": "short_header"}, "id": "header-short-1", "request": {"mac_length": 0, "packet_hex": "23"}} +{"expect": {"error": "short_header"}, "id": "header-short-2", "request": {"mac_length": 0, "packet_hex": "2310"}} +{"expect": {"error": "short_header"}, "id": "header-short-3", "request": {"mac_length": 0, "packet_hex": "231080"}} +{"expect": {"error": "short_header"}, "id": "header-short-4", "request": {"mac_length": 0, "packet_hex": "231080ff"}} +{"expect": {"error": "short_header"}, "id": "header-short-5", "request": {"mac_length": 0, "packet_hex": "231080ff80"}} +{"expect": {"error": "short_header"}, "id": "header-short-6", "request": {"mac_length": 0, "packet_hex": "231080ff8001"}} +{"expect": {"error": "short_header"}, "id": "header-short-7", "request": {"mac_length": 0, "packet_hex": "231080ff800100"}} +{"expect": {"error": "short_header"}, "id": "header-short-8", "request": {"mac_length": 0, "packet_hex": "231080ff80010002"}} +{"expect": {"error": "short_header"}, "id": "header-short-9", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ff"}} +{"expect": {"error": "short_header"}, "id": "header-short-10", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff"}} +{"expect": {"error": "short_header"}, "id": "header-short-11", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff00"}} +{"expect": {"error": "short_header"}, "id": "header-short-12", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff0001"}} +{"expect": {"error": "short_header"}, "id": "header-short-13", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152"}} +{"expect": {"error": "short_header"}, "id": "header-short-14", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff00015241"}} +{"expect": {"error": "short_header"}, "id": "header-short-15", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff0001524154"}} +{"expect": {"error": "short_header"}, "id": "header-short-16", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445"}} +{"expect": {"error": "short_header"}, "id": "header-short-17", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff00015241544500"}} +{"expect": {"error": "short_header"}, "id": "header-short-18", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff0001524154450000"}} +{"expect": {"error": "short_header"}, "id": "header-short-19", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000"}} +{"expect": {"error": "short_header"}, "id": "header-short-20", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff00015241544500000000"}} +{"expect": {"error": "short_header"}, "id": "header-short-21", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff0001524154450000000000"}} +{"expect": {"error": "short_header"}, "id": "header-short-22", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000"}} +{"expect": {"error": "short_header"}, "id": "header-short-23", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff00015241544500000000000000"}} +{"expect": {"error": "short_header"}, "id": "header-short-24", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff0001524154450000000000000000"}} +{"expect": {"error": "short_header"}, "id": "header-short-25", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000"}} +{"expect": {"error": "short_header"}, "id": "header-short-26", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff00015241544500000000000000000000"}} +{"expect": {"error": "short_header"}, "id": "header-short-27", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff0001524154450000000000000000000000"}} +{"expect": {"error": "short_header"}, "id": "header-short-28", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000"}} +{"expect": {"error": "short_header"}, "id": "header-short-29", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff00015241544500000000000000000000000000"}} +{"expect": {"error": "short_header"}, "id": "header-short-30", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff0001524154450000000000000000000000000000"}} +{"expect": {"error": "short_header"}, "id": "header-short-31", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000"}} +{"expect": {"error": "short_header"}, "id": "header-short-32", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff00015241544500000000000000000000000000000001"}} +{"expect": {"error": "short_header"}, "id": "header-short-33", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff0001524154450000000000000000000000000000000180"}} +{"expect": {"error": "short_header"}, "id": "header-short-34", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000"}} +{"expect": {"error": "short_header"}, "id": "header-short-35", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff00015241544500000000000000000000000000000001800000"}} +{"expect": {"error": "short_header"}, "id": "header-short-36", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff0001524154450000000000000000000000000000000180000000"}} +{"expect": {"error": "short_header"}, "id": "header-short-37", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000"}} +{"expect": {"error": "short_header"}, "id": "header-short-38", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff00015241544500000000000000000000000000000001800000000000"}} +{"expect": {"error": "short_header"}, "id": "header-short-39", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff0001524154450000000000000000000000000000000180000000000000"}} +{"expect": {"error": "short_header"}, "id": "header-short-40", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000"}} +{"expect": {"error": "short_header"}, "id": "header-short-41", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000ff"}} +{"expect": {"error": "short_header"}, "id": "header-short-42", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000ffff"}} +{"expect": {"error": "short_header"}, "id": "header-short-43", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000ffffff"}} +{"expect": {"error": "short_header"}, "id": "header-short-44", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000ffffffff"}} +{"expect": {"error": "short_header"}, "id": "header-short-45", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000ffffffffff"}} +{"expect": {"error": "short_header"}, "id": "header-short-46", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000ffffffffffff"}} +{"expect": {"error": "short_header"}, "id": "header-short-47", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000ffffffffffffff"}} +{"expect": {"error": "unsupported_version"}, "id": "version-0", "request": {"mac_length": 0, "packet_hex": "031080ff80010002ffff000152415445000000000000000000000000000000018000000000000000ffffffffffffffff"}} +{"expect": {"error": "unsupported_version"}, "id": "version-1", "request": {"mac_length": 0, "packet_hex": "0b1080ff80010002ffff000152415445000000000000000000000000000000018000000000000000ffffffffffffffff"}} +{"expect": {"error": "unsupported_version"}, "id": "version-2", "request": {"mac_length": 0, "packet_hex": "131080ff80010002ffff000152415445000000000000000000000000000000018000000000000000ffffffffffffffff"}} +{"expect": {"extensions": [], "header": {"leap": 0, "mode": 3, "poll": -128, "precision": -1, "reference_id_hex": "52415445", "root_delay_bits": 2147549186, "root_dispersion_bits": 4294901761, "stratum": 16, "timestamps": [{"fraction": 0, "raw": 0, "seconds": 0, "unknown": true}, {"fraction": 1, "raw": 1, "seconds": 0, "unknown": false}, {"fraction": 0, "raw": 9223372036854775808, "seconds": 2147483648, "unknown": false}, {"fraction": 4294967295, "raw": 18446744073709551615, "seconds": 4294967295, "unknown": false}], "version": 3}, "mac_hex": "", "mac_length": 0}, "id": "version-3", "request": {"mac_length": 0, "packet_hex": "1b1080ff80010002ffff000152415445000000000000000000000000000000018000000000000000ffffffffffffffff"}} +{"expect": {"extensions": [], "header": {"leap": 0, "mode": 3, "poll": -128, "precision": -1, "reference_id_hex": "52415445", "root_delay_bits": 2147549186, "root_dispersion_bits": 4294901761, "stratum": 16, "timestamps": [{"fraction": 0, "raw": 0, "seconds": 0, "unknown": true}, {"fraction": 1, "raw": 1, "seconds": 0, "unknown": false}, {"fraction": 0, "raw": 9223372036854775808, "seconds": 2147483648, "unknown": false}, {"fraction": 4294967295, "raw": 18446744073709551615, "seconds": 4294967295, "unknown": false}], "version": 4}, "mac_hex": "", "mac_length": 0}, "id": "version-4", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000ffffffffffffffff"}} +{"expect": {"error": "unsupported_version"}, "id": "version-5", "request": {"mac_length": 0, "packet_hex": "2b1080ff80010002ffff000152415445000000000000000000000000000000018000000000000000ffffffffffffffff"}} +{"expect": {"error": "unsupported_version"}, "id": "version-6", "request": {"mac_length": 0, "packet_hex": "331080ff80010002ffff000152415445000000000000000000000000000000018000000000000000ffffffffffffffff"}} +{"expect": {"error": "unsupported_version"}, "id": "version-7", "request": {"mac_length": 0, "packet_hex": "3b1080ff80010002ffff000152415445000000000000000000000000000000018000000000000000ffffffffffffffff"}} +{"expect": {"extensions": [], "header": {"leap": 0, "mode": 3, "poll": -128, "precision": -1, "reference_id_hex": "52415445", "root_delay_bits": 2147549186, "root_dispersion_bits": 4294901761, "stratum": 16, "timestamps": [{"fraction": 0, "raw": 0, "seconds": 0, "unknown": true}, {"fraction": 1, "raw": 1, "seconds": 0, "unknown": false}, {"fraction": 0, "raw": 9223372036854775808, "seconds": 2147483648, "unknown": false}, {"fraction": 4294967295, "raw": 18446744073709551615, "seconds": 4294967295, "unknown": false}], "version": 4}, "mac_hex": "00000000", "mac_length": 4}, "id": "opaque-mac-4", "request": {"mac_length": 4, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000ffffffffffffffff00000000"}} +{"expect": {"extensions": [{"length": 16, "offset": 48, "type": 65278, "value_and_padding_hex": "000000000000000000000000"}], "header": {"leap": 0, "mode": 3, "poll": -128, "precision": -1, "reference_id_hex": "52415445", "root_delay_bits": 2147549186, "root_dispersion_bits": 4294901761, "stratum": 16, "timestamps": [{"fraction": 0, "raw": 0, "seconds": 0, "unknown": true}, {"fraction": 1, "raw": 1, "seconds": 0, "unknown": false}, {"fraction": 0, "raw": 9223372036854775808, "seconds": 2147483648, "unknown": false}, {"fraction": 4294967295, "raw": 18446744073709551615, "seconds": 4294967295, "unknown": false}], "version": 4}, "mac_hex": "00000000", "mac_length": 4}, "id": "extension-16-with-mac-4", "request": {"mac_length": 4, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000fffffffffffffffffefe001000000000000000000000000000000000"}} +{"expect": {"extensions": [], "header": {"leap": 0, "mode": 3, "poll": -128, "precision": -1, "reference_id_hex": "52415445", "root_delay_bits": 2147549186, "root_dispersion_bits": 4294901761, "stratum": 16, "timestamps": [{"fraction": 0, "raw": 0, "seconds": 0, "unknown": true}, {"fraction": 1, "raw": 1, "seconds": 0, "unknown": false}, {"fraction": 0, "raw": 9223372036854775808, "seconds": 2147483648, "unknown": false}, {"fraction": 4294967295, "raw": 18446744073709551615, "seconds": 4294967295, "unknown": false}], "version": 4}, "mac_hex": "fefe001400000000000000000000000000000000", "mac_length": 20}, "id": "opaque-mac-20", "request": {"mac_length": 20, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000fffffffffffffffffefe001400000000000000000000000000000000"}} +{"expect": {"extensions": [{"length": 16, "offset": 48, "type": 65278, "value_and_padding_hex": "000000000000000000000000"}], "header": {"leap": 0, "mode": 3, "poll": -128, "precision": -1, "reference_id_hex": "52415445", "root_delay_bits": 2147549186, "root_dispersion_bits": 4294901761, "stratum": 16, "timestamps": [{"fraction": 0, "raw": 0, "seconds": 0, "unknown": true}, {"fraction": 1, "raw": 1, "seconds": 0, "unknown": false}, {"fraction": 0, "raw": 9223372036854775808, "seconds": 2147483648, "unknown": false}, {"fraction": 4294967295, "raw": 18446744073709551615, "seconds": 4294967295, "unknown": false}], "version": 4}, "mac_hex": "fefe001400000000000000000000000000000000", "mac_length": 20}, "id": "extension-16-with-mac-20", "request": {"mac_length": 20, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000fffffffffffffffffefe0010000000000000000000000000fefe001400000000000000000000000000000000"}} +{"expect": {"extensions": [], "header": {"leap": 0, "mode": 3, "poll": -128, "precision": -1, "reference_id_hex": "52415445", "root_delay_bits": 2147549186, "root_dispersion_bits": 4294901761, "stratum": 16, "timestamps": [{"fraction": 0, "raw": 0, "seconds": 0, "unknown": true}, {"fraction": 1, "raw": 1, "seconds": 0, "unknown": false}, {"fraction": 0, "raw": 9223372036854775808, "seconds": 2147483648, "unknown": false}, {"fraction": 4294967295, "raw": 18446744073709551615, "seconds": 4294967295, "unknown": false}], "version": 4}, "mac_hex": "fefe00180000000000000000000000000000000000000000", "mac_length": 24}, "id": "opaque-mac-24", "request": {"mac_length": 24, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000fffffffffffffffffefe00180000000000000000000000000000000000000000"}} +{"expect": {"extensions": [{"length": 16, "offset": 48, "type": 65278, "value_and_padding_hex": "000000000000000000000000"}], "header": {"leap": 0, "mode": 3, "poll": -128, "precision": -1, "reference_id_hex": "52415445", "root_delay_bits": 2147549186, "root_dispersion_bits": 4294901761, "stratum": 16, "timestamps": [{"fraction": 0, "raw": 0, "seconds": 0, "unknown": true}, {"fraction": 1, "raw": 1, "seconds": 0, "unknown": false}, {"fraction": 0, "raw": 9223372036854775808, "seconds": 2147483648, "unknown": false}, {"fraction": 4294967295, "raw": 18446744073709551615, "seconds": 4294967295, "unknown": false}], "version": 4}, "mac_hex": "fefe00180000000000000000000000000000000000000000", "mac_length": 24}, "id": "extension-16-with-mac-24", "request": {"mac_length": 24, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000fffffffffffffffffefe0010000000000000000000000000fefe00180000000000000000000000000000000000000000"}} +{"expect": {"extensions": [], "header": {"leap": 0, "mode": 3, "poll": -128, "precision": -1, "reference_id_hex": "52415445", "root_delay_bits": 2147549186, "root_dispersion_bits": 4294901761, "stratum": 16, "timestamps": [{"fraction": 0, "raw": 0, "seconds": 0, "unknown": true}, {"fraction": 1, "raw": 1, "seconds": 0, "unknown": false}, {"fraction": 0, "raw": 9223372036854775808, "seconds": 2147483648, "unknown": false}, {"fraction": 4294967295, "raw": 18446744073709551615, "seconds": 4294967295, "unknown": false}], "version": 4}, "mac_hex": "00000001", "mac_length": 4}, "id": "opaque-four-byte-nonzero", "request": {"mac_length": 4, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000ffffffffffffffff00000001"}} +{"expect": {"error": "short_mac"}, "id": "missing-declared-mac", "request": {"mac_length": 4, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000ffffffffffffffff"}} +{"expect": {"error": "short_final_extension_without_mac"}, "id": "final-extension-16-no-mac", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000fffffffffffffffffefe0010000000000000000000000000"}} +{"expect": {"error": "short_final_extension_without_mac"}, "id": "final-extension-20-no-mac", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000fffffffffffffffffefe001400000000000000000000000000000000"}} +{"expect": {"error": "short_final_extension_without_mac"}, "id": "final-extension-24-no-mac", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000fffffffffffffffffefe00180000000000000000000000000000000000000000"}} +{"expect": {"extensions": [{"length": 28, "offset": 48, "type": 65278, "value_and_padding_hex": "000000000000000000000000000000000000000000000000"}], "header": {"leap": 0, "mode": 3, "poll": -128, "precision": -1, "reference_id_hex": "52415445", "root_delay_bits": 2147549186, "root_dispersion_bits": 4294901761, "stratum": 16, "timestamps": [{"fraction": 0, "raw": 0, "seconds": 0, "unknown": true}, {"fraction": 1, "raw": 1, "seconds": 0, "unknown": false}, {"fraction": 0, "raw": 9223372036854775808, "seconds": 2147483648, "unknown": false}, {"fraction": 4294967295, "raw": 18446744073709551615, "seconds": 4294967295, "unknown": false}], "version": 4}, "mac_hex": "", "mac_length": 0}, "id": "final-extension-28-no-mac", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000fffffffffffffffffefe001c000000000000000000000000000000000000000000000000"}} +{"expect": {"extensions": [{"length": 32, "offset": 48, "type": 65278, "value_and_padding_hex": "00000000000000000000000000000000000000000000000000000000"}], "header": {"leap": 0, "mode": 3, "poll": -128, "precision": -1, "reference_id_hex": "52415445", "root_delay_bits": 2147549186, "root_dispersion_bits": 4294901761, "stratum": 16, "timestamps": [{"fraction": 0, "raw": 0, "seconds": 0, "unknown": true}, {"fraction": 1, "raw": 1, "seconds": 0, "unknown": false}, {"fraction": 0, "raw": 9223372036854775808, "seconds": 2147483648, "unknown": false}, {"fraction": 4294967295, "raw": 18446744073709551615, "seconds": 4294967295, "unknown": false}], "version": 4}, "mac_hex": "", "mac_length": 0}, "id": "final-extension-32-no-mac", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000fffffffffffffffffefe002000000000000000000000000000000000000000000000000000000000"}} +{"expect": {"extensions": [{"length": 16, "offset": 48, "type": 65278, "value_and_padding_hex": "000000000000000000000000"}, {"length": 28, "offset": 64, "type": 65278, "value_and_padding_hex": "000000000000000000000000000000000000000000000000"}], "header": {"leap": 0, "mode": 3, "poll": -128, "precision": -1, "reference_id_hex": "52415445", "root_delay_bits": 2147549186, "root_dispersion_bits": 4294901761, "stratum": 16, "timestamps": [{"fraction": 0, "raw": 0, "seconds": 0, "unknown": true}, {"fraction": 1, "raw": 1, "seconds": 0, "unknown": false}, {"fraction": 0, "raw": 9223372036854775808, "seconds": 2147483648, "unknown": false}, {"fraction": 4294967295, "raw": 18446744073709551615, "seconds": 4294967295, "unknown": false}], "version": 4}, "mac_hex": "", "mac_length": 0}, "id": "two-extensions-no-mac", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000fffffffffffffffffefe0010000000000000000000000000fefe001c000000000000000000000000000000000000000000000000"}} +{"expect": {"error": "short_final_extension_without_mac"}, "id": "two-short-extensions-no-mac", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000fffffffffffffffffefe0010000000000000000000000000fefe0010000000000000000000000000"}} +{"expect": {"error": "short_extension"}, "id": "extension-short-length-0", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000fffffffffffffffffefe0000000000000000000000000000000000000000000000000000"}} +{"expect": {"error": "short_extension"}, "id": "extension-short-length-4", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000fffffffffffffffffefe0004000000000000000000000000000000000000000000000000"}} +{"expect": {"error": "short_extension"}, "id": "extension-short-length-12", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000fffffffffffffffffefe000c000000000000000000000000000000000000000000000000"}} +{"expect": {"error": "unaligned_extension"}, "id": "extension-unaligned-length", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000fffffffffffffffffefe001d00000000000000000000000000000000000000000000000000000000"}} +{"expect": {"error": "extension_overrun"}, "id": "extension-overrun", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000fffffffffffffffffefe0020000000000000000000000000000000000000000000000000"}} +{"expect": {"error": "short_extension_header"}, "id": "extension-partial-header", "request": {"mac_length": 0, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000fffffffffffffffffefe"}} +{"expect": {"error": "extension_overrun"}, "id": "extension-cannot-consume-mac", "request": {"mac_length": 20, "packet_hex": "231080ff80010002ffff000152415445000000000000000000000000000000018000000000000000fffffffffffffffffefe001c0000000000000000000000000000000000000000000000000000000000000000"}}