@labacacia/nps-sdk 1.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +33 -0
- package/LICENSE +170 -0
- package/NOTICE +7 -0
- package/README.md +153 -0
- package/dist/codec-CmHeovTV.d.cts +120 -0
- package/dist/codec-CmHeovTV.d.ts +120 -0
- package/dist/core/anchor-cache.d.ts +42 -0
- package/dist/core/anchor-cache.d.ts.map +1 -0
- package/dist/core/anchor-cache.js +104 -0
- package/dist/core/anchor-cache.js.map +1 -0
- package/dist/core/cache.d.ts +14 -0
- package/dist/core/cache.d.ts.map +1 -0
- package/dist/core/cache.js +80 -0
- package/dist/core/cache.js.map +1 -0
- package/dist/core/canonical-json.d.ts +12 -0
- package/dist/core/canonical-json.d.ts.map +1 -0
- package/dist/core/canonical-json.js +44 -0
- package/dist/core/canonical-json.js.map +1 -0
- package/dist/core/codec.d.ts +32 -0
- package/dist/core/codec.d.ts.map +1 -0
- package/dist/core/codec.js +119 -0
- package/dist/core/codec.js.map +1 -0
- package/dist/core/codecs/index.d.ts +4 -0
- package/dist/core/codecs/index.d.ts.map +1 -0
- package/dist/core/codecs/index.js +6 -0
- package/dist/core/codecs/index.js.map +1 -0
- package/dist/core/codecs/ncp-codec.d.ts +39 -0
- package/dist/core/codecs/ncp-codec.d.ts.map +1 -0
- package/dist/core/codecs/ncp-codec.js +93 -0
- package/dist/core/codecs/ncp-codec.js.map +1 -0
- package/dist/core/codecs/tier1-json-codec.d.ts +10 -0
- package/dist/core/codecs/tier1-json-codec.d.ts.map +1 -0
- package/dist/core/codecs/tier1-json-codec.js +28 -0
- package/dist/core/codecs/tier1-json-codec.js.map +1 -0
- package/dist/core/codecs/tier2-msgpack-codec.d.ts +10 -0
- package/dist/core/codecs/tier2-msgpack-codec.d.ts.map +1 -0
- package/dist/core/codecs/tier2-msgpack-codec.js +26 -0
- package/dist/core/codecs/tier2-msgpack-codec.js.map +1 -0
- package/dist/core/crypto-provider.d.ts +31 -0
- package/dist/core/crypto-provider.d.ts.map +1 -0
- package/dist/core/crypto-provider.js +10 -0
- package/dist/core/crypto-provider.js.map +1 -0
- package/dist/core/exceptions.d.ts +27 -0
- package/dist/core/exceptions.d.ts.map +1 -0
- package/dist/core/exceptions.js +52 -0
- package/dist/core/exceptions.js.map +1 -0
- package/dist/core/frame-header.d.ts +87 -0
- package/dist/core/frame-header.d.ts.map +1 -0
- package/dist/core/frame-header.js +185 -0
- package/dist/core/frame-header.js.map +1 -0
- package/dist/core/frame-registry.d.ts +35 -0
- package/dist/core/frame-registry.d.ts.map +1 -0
- package/dist/core/frame-registry.js +63 -0
- package/dist/core/frame-registry.js.map +1 -0
- package/dist/core/frames.d.ts +80 -0
- package/dist/core/frames.d.ts.map +1 -0
- package/dist/core/frames.js +153 -0
- package/dist/core/frames.js.map +1 -0
- package/dist/core/index.cjs +371 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +41 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +10 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/registry.d.ts +11 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +17 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/status-codes.d.ts +28 -0
- package/dist/core/status-codes.d.ts.map +1 -0
- package/dist/core/status-codes.js +38 -0
- package/dist/core/status-codes.js.map +1 -0
- package/dist/frames-B3qLdl_g.d.cts +77 -0
- package/dist/frames-Ff7-ZPUl.d.ts +77 -0
- package/dist/index.cjs +1556 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/ncp/frames/anchor-frame.d.ts +29 -0
- package/dist/ncp/frames/anchor-frame.d.ts.map +1 -0
- package/dist/ncp/frames/anchor-frame.js +54 -0
- package/dist/ncp/frames/anchor-frame.js.map +1 -0
- package/dist/ncp/frames/caps-frame.d.ts +29 -0
- package/dist/ncp/frames/caps-frame.d.ts.map +1 -0
- package/dist/ncp/frames/caps-frame.js +29 -0
- package/dist/ncp/frames/caps-frame.js.map +1 -0
- package/dist/ncp/frames/diff-frame.d.ts +32 -0
- package/dist/ncp/frames/diff-frame.d.ts.map +1 -0
- package/dist/ncp/frames/diff-frame.js +37 -0
- package/dist/ncp/frames/diff-frame.js.map +1 -0
- package/dist/ncp/frames/error-frame.d.ts +16 -0
- package/dist/ncp/frames/error-frame.d.ts.map +1 -0
- package/dist/ncp/frames/error-frame.js +13 -0
- package/dist/ncp/frames/error-frame.js.map +1 -0
- package/dist/ncp/frames/hello-frame.d.ts +21 -0
- package/dist/ncp/frames/hello-frame.d.ts.map +1 -0
- package/dist/ncp/frames/hello-frame.js +25 -0
- package/dist/ncp/frames/hello-frame.js.map +1 -0
- package/dist/ncp/frames/stream-frame.d.ts +16 -0
- package/dist/ncp/frames/stream-frame.d.ts.map +1 -0
- package/dist/ncp/frames/stream-frame.js +18 -0
- package/dist/ncp/frames/stream-frame.js.map +1 -0
- package/dist/ncp/frames.d.ts +76 -0
- package/dist/ncp/frames.d.ts.map +1 -0
- package/dist/ncp/frames.js +147 -0
- package/dist/ncp/frames.js.map +1 -0
- package/dist/ncp/handshake.d.ts +30 -0
- package/dist/ncp/handshake.d.ts.map +1 -0
- package/dist/ncp/handshake.js +80 -0
- package/dist/ncp/handshake.js.map +1 -0
- package/dist/ncp/index.cjs +188 -0
- package/dist/ncp/index.cjs.map +1 -0
- package/dist/ncp/index.d.cts +6 -0
- package/dist/ncp/index.d.ts +11 -0
- package/dist/ncp/index.d.ts.map +1 -0
- package/dist/ncp/index.js +13 -0
- package/dist/ncp/index.js.map +1 -0
- package/dist/ncp/ncp-error-codes.d.ts +22 -0
- package/dist/ncp/ncp-error-codes.d.ts.map +1 -0
- package/dist/ncp/ncp-error-codes.js +32 -0
- package/dist/ncp/ncp-error-codes.js.map +1 -0
- package/dist/ncp/ncp-patch-format.d.ts +7 -0
- package/dist/ncp/ncp-patch-format.d.ts.map +1 -0
- package/dist/ncp/ncp-patch-format.js +13 -0
- package/dist/ncp/ncp-patch-format.js.map +1 -0
- package/dist/ncp/registry.d.ts +3 -0
- package/dist/ncp/registry.d.ts.map +1 -0
- package/dist/ncp/registry.js +12 -0
- package/dist/ncp/registry.js.map +1 -0
- package/dist/ncp/stream-manager.d.ts +57 -0
- package/dist/ncp/stream-manager.d.ts.map +1 -0
- package/dist/ncp/stream-manager.js +163 -0
- package/dist/ncp/stream-manager.js.map +1 -0
- package/dist/ndp/frames.d.ts +56 -0
- package/dist/ndp/frames.d.ts.map +1 -0
- package/dist/ndp/frames.js +87 -0
- package/dist/ndp/frames.js.map +1 -0
- package/dist/ndp/index.cjs +252 -0
- package/dist/ndp/index.cjs.map +1 -0
- package/dist/ndp/index.d.cts +86 -0
- package/dist/ndp/index.d.ts +5 -0
- package/dist/ndp/index.d.ts.map +1 -0
- package/dist/ndp/index.js +7 -0
- package/dist/ndp/index.js.map +1 -0
- package/dist/ndp/ndp-registry.d.ts +11 -0
- package/dist/ndp/ndp-registry.d.ts.map +1 -0
- package/dist/ndp/ndp-registry.js +79 -0
- package/dist/ndp/ndp-registry.js.map +1 -0
- package/dist/ndp/registry.d.ts +3 -0
- package/dist/ndp/registry.d.ts.map +1 -0
- package/dist/ndp/registry.js +10 -0
- package/dist/ndp/registry.js.map +1 -0
- package/dist/ndp/validator.d.ts +18 -0
- package/dist/ndp/validator.d.ts.map +1 -0
- package/dist/ndp/validator.js +48 -0
- package/dist/ndp/validator.js.map +1 -0
- package/dist/nip/frames.d.ts +44 -0
- package/dist/nip/frames.d.ts.map +1 -0
- package/dist/nip/frames.js +81 -0
- package/dist/nip/frames.js.map +1 -0
- package/dist/nip/identity.d.ts +18 -0
- package/dist/nip/identity.d.ts.map +1 -0
- package/dist/nip/identity.js +94 -0
- package/dist/nip/identity.js.map +1 -0
- package/dist/nip/index.cjs +214 -0
- package/dist/nip/index.cjs.map +1 -0
- package/dist/nip/index.d.cts +65 -0
- package/dist/nip/index.d.ts +4 -0
- package/dist/nip/index.d.ts.map +1 -0
- package/dist/nip/index.js +6 -0
- package/dist/nip/index.js.map +1 -0
- package/dist/nip/registry.d.ts +3 -0
- package/dist/nip/registry.d.ts.map +1 -0
- package/dist/nip/registry.js +10 -0
- package/dist/nip/registry.js.map +1 -0
- package/dist/nop/client.d.ts +34 -0
- package/dist/nop/client.d.ts.map +1 -0
- package/dist/nop/client.js +90 -0
- package/dist/nop/client.js.map +1 -0
- package/dist/nop/frames.d.ts +65 -0
- package/dist/nop/frames.d.ts.map +1 -0
- package/dist/nop/frames.js +148 -0
- package/dist/nop/frames.js.map +1 -0
- package/dist/nop/index.cjs +762 -0
- package/dist/nop/index.cjs.map +1 -0
- package/dist/nop/index.d.cts +155 -0
- package/dist/nop/index.d.ts +5 -0
- package/dist/nop/index.d.ts.map +1 -0
- package/dist/nop/index.js +7 -0
- package/dist/nop/index.js.map +1 -0
- package/dist/nop/models.d.ts +58 -0
- package/dist/nop/models.d.ts.map +1 -0
- package/dist/nop/models.js +50 -0
- package/dist/nop/models.js.map +1 -0
- package/dist/nop/nop-types.d.ts +136 -0
- package/dist/nop/nop-types.d.ts.map +1 -0
- package/dist/nop/nop-types.js +44 -0
- package/dist/nop/nop-types.js.map +1 -0
- package/dist/nop/registry.d.ts +3 -0
- package/dist/nop/registry.d.ts.map +1 -0
- package/dist/nop/registry.js +11 -0
- package/dist/nop/registry.js.map +1 -0
- package/dist/nwp/client.d.ts +22 -0
- package/dist/nwp/client.d.ts.map +1 -0
- package/dist/nwp/client.js +101 -0
- package/dist/nwp/client.js.map +1 -0
- package/dist/nwp/frames.d.ts +46 -0
- package/dist/nwp/frames.d.ts.map +1 -0
- package/dist/nwp/frames.js +81 -0
- package/dist/nwp/frames.js.map +1 -0
- package/dist/nwp/index.cjs +658 -0
- package/dist/nwp/index.cjs.map +1 -0
- package/dist/nwp/index.d.cts +65 -0
- package/dist/nwp/index.d.ts +4 -0
- package/dist/nwp/index.d.ts.map +1 -0
- package/dist/nwp/index.js +6 -0
- package/dist/nwp/index.js.map +1 -0
- package/dist/nwp/registry.d.ts +3 -0
- package/dist/nwp/registry.d.ts.map +1 -0
- package/dist/nwp/registry.js +9 -0
- package/dist/nwp/registry.js.map +1 -0
- package/dist/setup.d.ts +10 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +29 -0
- package/dist/setup.js.map +1 -0
- package/nip-ca-server/Dockerfile +27 -0
- package/nip-ca-server/README.md +45 -0
- package/nip-ca-server/db/001_init.sql +25 -0
- package/nip-ca-server/docker-compose.yml +29 -0
- package/nip-ca-server/package.json +23 -0
- package/nip-ca-server/src/ca.ts +155 -0
- package/nip-ca-server/src/db.ts +104 -0
- package/nip-ca-server/src/index.ts +157 -0
- package/nip-ca-server/tsconfig.json +13 -0
- package/package.json +47 -0
- package/src/core/anchor-cache.ts +129 -0
- package/src/core/cache.ts +93 -0
- package/src/core/canonical-json.ts +50 -0
- package/src/core/codec.ts +158 -0
- package/src/core/codecs/index.ts +5 -0
- package/src/core/codecs/ncp-codec.ts +170 -0
- package/src/core/codecs/tier1-json-codec.ts +33 -0
- package/src/core/codecs/tier2-msgpack-codec.ts +30 -0
- package/src/core/crypto-provider.ts +47 -0
- package/src/core/exceptions.ts +57 -0
- package/src/core/frame-header.ts +282 -0
- package/src/core/frame-registry.ts +91 -0
- package/src/core/frames.ts +183 -0
- package/src/core/index.ts +10 -0
- package/src/core/registry.ts +28 -0
- package/src/core/status-codes.ts +46 -0
- package/src/index.ts +10 -0
- package/src/ncp/frames/anchor-frame.ts +87 -0
- package/src/ncp/frames/caps-frame.ts +59 -0
- package/src/ncp/frames/diff-frame.ts +69 -0
- package/src/ncp/frames/error-frame.ts +26 -0
- package/src/ncp/frames/hello-frame.ts +50 -0
- package/src/ncp/frames/stream-frame.ts +35 -0
- package/src/ncp/frames.ts +199 -0
- package/src/ncp/handshake.ts +95 -0
- package/src/ncp/index.ts +12 -0
- package/src/ncp/ncp-error-codes.ts +34 -0
- package/src/ncp/ncp-patch-format.ts +16 -0
- package/src/ncp/registry.ts +14 -0
- package/src/ncp/stream-manager.ts +212 -0
- package/src/ndp/frames.ts +124 -0
- package/src/ndp/index.ts +7 -0
- package/src/ndp/ndp-registry.ts +82 -0
- package/src/ndp/registry.ts +12 -0
- package/src/ndp/validator.ts +64 -0
- package/src/nip/frames.ts +106 -0
- package/src/nip/identity.ts +113 -0
- package/src/nip/index.ts +6 -0
- package/src/nip/registry.ts +12 -0
- package/src/nop/client.ts +103 -0
- package/src/nop/frames.ts +181 -0
- package/src/nop/index.ts +7 -0
- package/src/nop/models.ts +79 -0
- package/src/nop/nop-types.ts +208 -0
- package/src/nop/registry.ts +13 -0
- package/src/nwp/client.ts +114 -0
- package/src/nwp/frames.ts +116 -0
- package/src/nwp/index.ts +6 -0
- package/src/nwp/registry.ts +11 -0
- package/src/setup.ts +32 -0
- package/tests/core/anchor-cache.test.ts +242 -0
- package/tests/core/codec.test.ts +205 -0
- package/tests/core/frame-registry.test.ts +46 -0
- package/tests/core.test.ts +327 -0
- package/tests/ncp/diff-binary-bitset.test.ts +107 -0
- package/tests/ncp/e2e-enc-reject.test.ts +93 -0
- package/tests/ncp/err-error-frame.test.ts +152 -0
- package/tests/ncp/frames.test.ts +359 -0
- package/tests/ncp/framing.test.ts +233 -0
- package/tests/ncp/hello-frame.test.ts +122 -0
- package/tests/ncp/inline-anchor.test.ts +88 -0
- package/tests/ncp/security.test.ts +184 -0
- package/tests/ncp/stream-window.test.ts +167 -0
- package/tests/ncp/stream.test.ts +242 -0
- package/tests/ncp/version-negotiation.test.ts +123 -0
- package/tests/ndp.test.ts +271 -0
- package/tests/nip.test.ts +184 -0
- package/tests/nop.test.ts +344 -0
- package/tests/nwp.test.ts +237 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +20 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD
|
|
3
|
+
//
|
|
4
|
+
// NCP Test Cases — Frame types + Tier-1 JSON codec
|
|
5
|
+
// Covers: NCP-E-01, E-03, NCP-A-01 to A-04, NCP-C-01 to C-04, NCP-D-01 to D-04
|
|
6
|
+
// Source: test/ncp_test_cases.md §2, §3
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect } from "vitest";
|
|
9
|
+
import { NcpError } from "../../src/core/frame-header.js";
|
|
10
|
+
import { encodeJson, decodeJson } from "../../src/core/codecs/tier1-json-codec.js";
|
|
11
|
+
import {
|
|
12
|
+
computeAnchorId,
|
|
13
|
+
validateAnchorFrame,
|
|
14
|
+
type AnchorFrame,
|
|
15
|
+
type FrameSchema,
|
|
16
|
+
} from "../../src/ncp/frames/anchor-frame.js";
|
|
17
|
+
import { validateCapsFrame, type CapsFrame } from "../../src/ncp/frames/caps-frame.js";
|
|
18
|
+
import { validateDiffSeq, type DiffFrame } from "../../src/ncp/frames/diff-frame.js";
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Helpers
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
const testSchema: FrameSchema = {
|
|
25
|
+
fields: [
|
|
26
|
+
{ name: "id", type: "uint64", semantic: "entity.id" },
|
|
27
|
+
{ name: "name", type: "string", semantic: "entity.label" },
|
|
28
|
+
{ name: "price", type: "decimal", semantic: "commerce.price.usd" },
|
|
29
|
+
{ name: "stock", type: "uint64", semantic: "commerce.inventory.count" },
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function makeValidAnchor(): AnchorFrame {
|
|
34
|
+
const anchor_id = computeAnchorId(testSchema);
|
|
35
|
+
return { frame: "0x01", anchor_id, schema: testSchema, ttl: 3600 };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ===========================================================================
|
|
39
|
+
// Group 2: Encoding Tiers (Tier-1 JSON)
|
|
40
|
+
// ===========================================================================
|
|
41
|
+
|
|
42
|
+
describe("Group 2: Encoding — Tier-1 JSON", () => {
|
|
43
|
+
// -----------------------------------------------------------------------
|
|
44
|
+
// NCP-E-01: Tier-1 JSON (Valid)
|
|
45
|
+
// Spec: §3.2, §8 — Flags T0/T1 = 00
|
|
46
|
+
// -----------------------------------------------------------------------
|
|
47
|
+
it("NCP-E-01: encodes and decodes valid JSON payload", () => {
|
|
48
|
+
const anchor = makeValidAnchor();
|
|
49
|
+
const bytes = encodeJson(anchor);
|
|
50
|
+
const decoded = decodeJson(bytes) as AnchorFrame;
|
|
51
|
+
|
|
52
|
+
expect(decoded.frame).toBe("0x01");
|
|
53
|
+
expect(decoded.anchor_id).toBe(anchor.anchor_id);
|
|
54
|
+
expect(decoded.schema.fields).toHaveLength(4);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// -----------------------------------------------------------------------
|
|
58
|
+
// NCP-E-03: Tier-1 JSON (Malformed)
|
|
59
|
+
// Spec: §3.2, §8 — Invalid JSON → NPS-CLIENT-BAD-FRAME
|
|
60
|
+
// -----------------------------------------------------------------------
|
|
61
|
+
it("NCP-E-03: rejects malformed JSON payload", () => {
|
|
62
|
+
const bad = new TextEncoder().encode('{"frame":"0x01", "anchor_id":');
|
|
63
|
+
expect(() => decodeJson(bad)).toThrow(NcpError);
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
decodeJson(bad);
|
|
67
|
+
} catch (e) {
|
|
68
|
+
expect((e as NcpError).code).toBe("NPS-CLIENT-BAD-FRAME");
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// ===========================================================================
|
|
74
|
+
// Group 3.1: AnchorFrame (0x01)
|
|
75
|
+
// ===========================================================================
|
|
76
|
+
|
|
77
|
+
describe("Group 3.1: AnchorFrame", () => {
|
|
78
|
+
// -----------------------------------------------------------------------
|
|
79
|
+
// NCP-A-01: Valid Anchor
|
|
80
|
+
// Spec: §4.1 — anchor_id = SHA-256(JCS(schema))
|
|
81
|
+
// -----------------------------------------------------------------------
|
|
82
|
+
it("NCP-A-01: computes correct anchor_id via JCS + SHA-256", () => {
|
|
83
|
+
const anchor_id = computeAnchorId(testSchema);
|
|
84
|
+
|
|
85
|
+
expect(anchor_id).toMatch(/^sha256:[a-f0-9]{64}$/);
|
|
86
|
+
|
|
87
|
+
// Same schema produces same ID (deterministic)
|
|
88
|
+
expect(computeAnchorId(testSchema)).toBe(anchor_id);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("NCP-A-01: different field order produces same anchor_id (JCS normalises)", () => {
|
|
92
|
+
const schemaA: FrameSchema = {
|
|
93
|
+
fields: [
|
|
94
|
+
{ name: "id", type: "uint64" },
|
|
95
|
+
{ name: "name", type: "string" },
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
const schemaB: FrameSchema = {
|
|
99
|
+
fields: [
|
|
100
|
+
{ name: "name", type: "string" },
|
|
101
|
+
{ name: "id", type: "uint64" },
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// NOTE: JCS sorts object keys, but array order is preserved.
|
|
106
|
+
// Different field ORDER in the array produces different hashes.
|
|
107
|
+
// Only different key ORDER within objects is normalised.
|
|
108
|
+
const idA = computeAnchorId(schemaA);
|
|
109
|
+
const idB = computeAnchorId(schemaB);
|
|
110
|
+
|
|
111
|
+
// Array order matters — these are different schemas
|
|
112
|
+
expect(idA).not.toBe(idB);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("NCP-A-01: validates and caches valid anchor frame", () => {
|
|
116
|
+
const anchor = makeValidAnchor();
|
|
117
|
+
expect(() => validateAnchorFrame(anchor)).not.toThrow();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// -----------------------------------------------------------------------
|
|
121
|
+
// NCP-A-02: Anchor ID Mismatch
|
|
122
|
+
// Spec: §4.1 — Provided anchor_id != SHA-256(JCS(schema))
|
|
123
|
+
// -----------------------------------------------------------------------
|
|
124
|
+
it("NCP-A-02: rejects anchor with wrong anchor_id", () => {
|
|
125
|
+
const anchor: AnchorFrame = {
|
|
126
|
+
frame: "0x01",
|
|
127
|
+
anchor_id: "sha256:0000000000000000000000000000000000000000000000000000000000000000",
|
|
128
|
+
schema: testSchema,
|
|
129
|
+
ttl: 3600,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
expect(() => validateAnchorFrame(anchor)).toThrow(NcpError);
|
|
133
|
+
try {
|
|
134
|
+
validateAnchorFrame(anchor);
|
|
135
|
+
} catch (e) {
|
|
136
|
+
expect((e as NcpError).code).toBe("NCP-ANCHOR-SCHEMA-INVALID");
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// -----------------------------------------------------------------------
|
|
141
|
+
// NCP-A-03: Anchor Poisoning — tested in Step 7 (AnchorCache)
|
|
142
|
+
// -----------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
// -----------------------------------------------------------------------
|
|
145
|
+
// NCP-A-04: Invalid Schema Field
|
|
146
|
+
// Spec: §4.1 — Unsupported type (e.g. "tensor")
|
|
147
|
+
// -----------------------------------------------------------------------
|
|
148
|
+
it("NCP-A-04: rejects schema with unsupported field type", () => {
|
|
149
|
+
const badSchema: FrameSchema = {
|
|
150
|
+
fields: [
|
|
151
|
+
{ name: "id", type: "uint64" },
|
|
152
|
+
{ name: "embedding", type: "tensor" }, // not in valid types
|
|
153
|
+
],
|
|
154
|
+
};
|
|
155
|
+
const anchor_id = computeAnchorId(badSchema);
|
|
156
|
+
const anchor: AnchorFrame = {
|
|
157
|
+
frame: "0x01",
|
|
158
|
+
anchor_id,
|
|
159
|
+
schema: badSchema,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
expect(() => validateAnchorFrame(anchor)).toThrow(NcpError);
|
|
163
|
+
try {
|
|
164
|
+
validateAnchorFrame(anchor);
|
|
165
|
+
} catch (e) {
|
|
166
|
+
expect((e as NcpError).code).toBe("NCP-ANCHOR-SCHEMA-INVALID");
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("NCP-A-04: accepts all valid field types", () => {
|
|
171
|
+
const allTypes: FrameSchema = {
|
|
172
|
+
fields: [
|
|
173
|
+
{ name: "a", type: "string" },
|
|
174
|
+
{ name: "b", type: "uint64" },
|
|
175
|
+
{ name: "c", type: "int64" },
|
|
176
|
+
{ name: "d", type: "decimal" },
|
|
177
|
+
{ name: "e", type: "bool" },
|
|
178
|
+
{ name: "f", type: "timestamp" },
|
|
179
|
+
{ name: "g", type: "bytes" },
|
|
180
|
+
{ name: "h", type: "object" },
|
|
181
|
+
{ name: "i", type: "array" },
|
|
182
|
+
],
|
|
183
|
+
};
|
|
184
|
+
const anchor_id = computeAnchorId(allTypes);
|
|
185
|
+
const anchor: AnchorFrame = { frame: "0x01", anchor_id, schema: allTypes };
|
|
186
|
+
expect(() => validateAnchorFrame(anchor)).not.toThrow();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// NCP-A-05 and NCP-A-06 — tested in Step 7 (AnchorCache)
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// ===========================================================================
|
|
193
|
+
// Group 3.4: CapsFrame (0x04)
|
|
194
|
+
// ===========================================================================
|
|
195
|
+
|
|
196
|
+
describe("Group 3.4: CapsFrame", () => {
|
|
197
|
+
// -----------------------------------------------------------------------
|
|
198
|
+
// NCP-C-01: Empty Data
|
|
199
|
+
// Spec: §4.4 — count=0, data=[] is valid
|
|
200
|
+
// -----------------------------------------------------------------------
|
|
201
|
+
it("NCP-C-01: accepts empty data with count=0", () => {
|
|
202
|
+
const caps: CapsFrame = {
|
|
203
|
+
frame: "0x04",
|
|
204
|
+
anchor_ref: "sha256:abc123",
|
|
205
|
+
count: 0,
|
|
206
|
+
data: [],
|
|
207
|
+
};
|
|
208
|
+
expect(() => validateCapsFrame(caps)).not.toThrow();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// -----------------------------------------------------------------------
|
|
212
|
+
// NCP-C-02: Count Mismatch
|
|
213
|
+
// Spec: §4.4 — count MUST equal len(data)
|
|
214
|
+
// -----------------------------------------------------------------------
|
|
215
|
+
it("NCP-C-02: rejects count mismatch", () => {
|
|
216
|
+
const caps: CapsFrame = {
|
|
217
|
+
frame: "0x04",
|
|
218
|
+
anchor_ref: "sha256:abc123",
|
|
219
|
+
count: 3,
|
|
220
|
+
data: [{ id: 1 }, { id: 2 }], // 2 items but count says 3
|
|
221
|
+
};
|
|
222
|
+
expect(() => validateCapsFrame(caps)).toThrow(NcpError);
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
validateCapsFrame(caps);
|
|
226
|
+
} catch (e) {
|
|
227
|
+
expect((e as NcpError).code).toBe("NPS-CLIENT-BAD-FRAME");
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// -----------------------------------------------------------------------
|
|
232
|
+
// NCP-C-03: Cursor Handling
|
|
233
|
+
// Spec: §4.4 — Base64-URL next_cursor
|
|
234
|
+
// -----------------------------------------------------------------------
|
|
235
|
+
it("NCP-C-03: preserves Base64-URL cursor in round-trip", () => {
|
|
236
|
+
const caps: CapsFrame = {
|
|
237
|
+
frame: "0x04",
|
|
238
|
+
anchor_ref: "sha256:abc123",
|
|
239
|
+
count: 2,
|
|
240
|
+
data: [{ id: 1001 }, { id: 1002 }],
|
|
241
|
+
next_cursor: "eyJpZCI6MTAwM30", // {"id":1003}
|
|
242
|
+
};
|
|
243
|
+
expect(() => validateCapsFrame(caps)).not.toThrow();
|
|
244
|
+
|
|
245
|
+
const bytes = encodeJson(caps);
|
|
246
|
+
const decoded = decodeJson(bytes) as CapsFrame;
|
|
247
|
+
expect(decoded.next_cursor).toBe("eyJpZCI6MTAwM30");
|
|
248
|
+
|
|
249
|
+
// Decode the cursor
|
|
250
|
+
const cursorData = JSON.parse(
|
|
251
|
+
Buffer.from(decoded.next_cursor!, "base64url").toString(),
|
|
252
|
+
);
|
|
253
|
+
expect(cursorData.id).toBe(1003);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// -----------------------------------------------------------------------
|
|
257
|
+
// NCP-C-04: Token Budget Hint
|
|
258
|
+
// Spec: §4.4 — token_est is informational
|
|
259
|
+
// -----------------------------------------------------------------------
|
|
260
|
+
it("NCP-C-04: preserves token_est in round-trip", () => {
|
|
261
|
+
const caps: CapsFrame = {
|
|
262
|
+
frame: "0x04",
|
|
263
|
+
anchor_ref: "sha256:abc123",
|
|
264
|
+
count: 1,
|
|
265
|
+
data: [{ id: 1 }],
|
|
266
|
+
token_est: 180,
|
|
267
|
+
};
|
|
268
|
+
const bytes = encodeJson(caps);
|
|
269
|
+
const decoded = decodeJson(bytes) as CapsFrame;
|
|
270
|
+
expect(decoded.token_est).toBe(180);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// ===========================================================================
|
|
275
|
+
// Group 3.2: DiffFrame (0x02)
|
|
276
|
+
// ===========================================================================
|
|
277
|
+
|
|
278
|
+
describe("Group 3.2: DiffFrame", () => {
|
|
279
|
+
// -----------------------------------------------------------------------
|
|
280
|
+
// NCP-D-01: Valid Patch
|
|
281
|
+
// Spec: §4.2 — RFC 6902 JSON Patch applied to anchor data
|
|
282
|
+
// -----------------------------------------------------------------------
|
|
283
|
+
it("NCP-D-01: round-trips valid DiffFrame with JSON patch", () => {
|
|
284
|
+
const diff: DiffFrame = {
|
|
285
|
+
frame: "0x02",
|
|
286
|
+
anchor_ref: "sha256:abc123",
|
|
287
|
+
base_seq: 42,
|
|
288
|
+
patch_format: "json_patch",
|
|
289
|
+
entity_id: "product:1001",
|
|
290
|
+
patch: [
|
|
291
|
+
{ op: "replace", path: "/price", value: 299.0 },
|
|
292
|
+
{ op: "replace", path: "/stock", value: 48 },
|
|
293
|
+
],
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const bytes = encodeJson(diff);
|
|
297
|
+
const decoded = decodeJson(bytes) as DiffFrame;
|
|
298
|
+
|
|
299
|
+
expect(decoded.anchor_ref).toBe("sha256:abc123");
|
|
300
|
+
expect(decoded.base_seq).toBe(42);
|
|
301
|
+
expect(decoded.patch).toHaveLength(2);
|
|
302
|
+
expect(decoded.patch[0]!.op).toBe("replace");
|
|
303
|
+
expect(decoded.patch[0]!.value).toBe(299.0);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// -----------------------------------------------------------------------
|
|
307
|
+
// NCP-D-02: Sequence Gap
|
|
308
|
+
// Spec: §4.2 — base_seq must match receiver's current sequence
|
|
309
|
+
// -----------------------------------------------------------------------
|
|
310
|
+
it("NCP-D-02: rejects sequence gap", () => {
|
|
311
|
+
const diff: DiffFrame = {
|
|
312
|
+
frame: "0x02",
|
|
313
|
+
anchor_ref: "sha256:abc123",
|
|
314
|
+
base_seq: 3,
|
|
315
|
+
patch: [{ op: "replace", path: "/price", value: 100 }],
|
|
316
|
+
};
|
|
317
|
+
const currentSeq = 5;
|
|
318
|
+
|
|
319
|
+
expect(() => validateDiffSeq(diff, currentSeq)).toThrow(NcpError);
|
|
320
|
+
try {
|
|
321
|
+
validateDiffSeq(diff, currentSeq);
|
|
322
|
+
} catch (e) {
|
|
323
|
+
expect((e as NcpError).code).toBe("NCP-STREAM-SEQ-GAP");
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it("NCP-D-02: accepts matching sequence", () => {
|
|
328
|
+
const diff: DiffFrame = {
|
|
329
|
+
frame: "0x02",
|
|
330
|
+
anchor_ref: "sha256:abc123",
|
|
331
|
+
base_seq: 5,
|
|
332
|
+
patch: [{ op: "replace", path: "/price", value: 100 }],
|
|
333
|
+
};
|
|
334
|
+
expect(() => validateDiffSeq(diff, 5)).not.toThrow();
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// -----------------------------------------------------------------------
|
|
338
|
+
// NCP-D-03: Patch Target Invalid
|
|
339
|
+
// Spec: §4.2 — Patch path not in schema
|
|
340
|
+
// NOTE: Full validation requires schema context. Tested with fast-json-patch
|
|
341
|
+
// at integration level. Here we verify the DiffFrame structure.
|
|
342
|
+
// -----------------------------------------------------------------------
|
|
343
|
+
it("NCP-D-03: DiffFrame preserves patch paths for validation", () => {
|
|
344
|
+
const diff: DiffFrame = {
|
|
345
|
+
frame: "0x02",
|
|
346
|
+
anchor_ref: "sha256:abc123",
|
|
347
|
+
base_seq: 0,
|
|
348
|
+
patch: [{ op: "replace", path: "/nonexistent_field", value: 42 }],
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const bytes = encodeJson(diff);
|
|
352
|
+
const decoded = decodeJson(bytes) as DiffFrame;
|
|
353
|
+
expect(decoded.patch[0]!.path).toBe("/nonexistent_field");
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// -----------------------------------------------------------------------
|
|
357
|
+
// NCP-D-04: Missing Ref — tested in Step 7 (AnchorCache lookup)
|
|
358
|
+
// -----------------------------------------------------------------------
|
|
359
|
+
});
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD
|
|
3
|
+
//
|
|
4
|
+
// NCP Test Cases — Group 1: Framing & Header (NCP-F-01 to NCP-F-07)
|
|
5
|
+
// Source: test/ncp_test_cases.md §1
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from "vitest";
|
|
8
|
+
import {
|
|
9
|
+
parseFrameHeader,
|
|
10
|
+
writeFrameHeader,
|
|
11
|
+
buildFlags,
|
|
12
|
+
EncodingTier,
|
|
13
|
+
FrameType,
|
|
14
|
+
NcpError,
|
|
15
|
+
DEFAULT_HEADER_SIZE,
|
|
16
|
+
EXTENDED_HEADER_SIZE,
|
|
17
|
+
} from "../../src/core/frame-header.js";
|
|
18
|
+
|
|
19
|
+
describe("Group 1: Framing & Header", () => {
|
|
20
|
+
// -----------------------------------------------------------------------
|
|
21
|
+
// NCP-F-01: Standard Header (Valid)
|
|
22
|
+
// Spec: §3.1 — Default 4-byte header
|
|
23
|
+
// -----------------------------------------------------------------------
|
|
24
|
+
it("NCP-F-01: decodes standard 4-byte header", () => {
|
|
25
|
+
// AnchorFrame (0x01), flags=0x00 (JSON, no flags), payload=32 bytes
|
|
26
|
+
const buf = new Uint8Array([0x01, 0x00, 0x00, 0x20]);
|
|
27
|
+
const h = parseFrameHeader(buf);
|
|
28
|
+
|
|
29
|
+
expect(h.frameType).toBe(FrameType.Anchor);
|
|
30
|
+
expect(h.tier).toBe(EncodingTier.Json);
|
|
31
|
+
expect(h.isFinal).toBe(false);
|
|
32
|
+
expect(h.isEncrypted).toBe(false);
|
|
33
|
+
expect(h.isExtended).toBe(false);
|
|
34
|
+
expect(h.payloadLength).toBe(32);
|
|
35
|
+
expect(h.headerSize).toBe(DEFAULT_HEADER_SIZE);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// -----------------------------------------------------------------------
|
|
39
|
+
// NCP-F-02: Extended Header (Valid)
|
|
40
|
+
// Spec: §3.1 — Extended 8-byte header (EXT=1)
|
|
41
|
+
// -----------------------------------------------------------------------
|
|
42
|
+
it("NCP-F-02: decodes extended 8-byte header with EXT=1", () => {
|
|
43
|
+
// AnchorFrame, EXT=1 (0x80), payload=65536 (just over default max)
|
|
44
|
+
const buf = new Uint8Array([0x01, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00]);
|
|
45
|
+
const h = parseFrameHeader(buf);
|
|
46
|
+
|
|
47
|
+
expect(h.frameType).toBe(FrameType.Anchor);
|
|
48
|
+
expect(h.isExtended).toBe(true);
|
|
49
|
+
expect(h.payloadLength).toBe(65536);
|
|
50
|
+
expect(h.headerSize).toBe(EXTENDED_HEADER_SIZE);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// -----------------------------------------------------------------------
|
|
54
|
+
// NCP-F-03: EXT Flag Mismatch (Low)
|
|
55
|
+
// Spec: §3.1, §3.3 — EXT=1 with small payload is valid but inefficient
|
|
56
|
+
// -----------------------------------------------------------------------
|
|
57
|
+
it("NCP-F-03: accepts EXT=1 with small payload (inefficient but valid)", () => {
|
|
58
|
+
// Extended header, payload=100 bytes (well under 64KB)
|
|
59
|
+
const buf = new Uint8Array([0x01, 0x80, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00]);
|
|
60
|
+
const h = parseFrameHeader(buf);
|
|
61
|
+
|
|
62
|
+
expect(h.isExtended).toBe(true);
|
|
63
|
+
expect(h.payloadLength).toBe(100);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// -----------------------------------------------------------------------
|
|
67
|
+
// NCP-F-04: EXT Flag Mismatch (High) — NCP-FRAME-PAYLOAD-TOO-LARGE
|
|
68
|
+
// NOTE: This test belongs at the codec level (Step 5). Default header
|
|
69
|
+
// uint16 maxes at 65535 — can't physically express > 64KB.
|
|
70
|
+
// Codec validates payload against negotiated max_frame_payload.
|
|
71
|
+
// -----------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
// -----------------------------------------------------------------------
|
|
74
|
+
// NCP-F-05: Buffer Underrun
|
|
75
|
+
// Spec: §3.1 — Incomplete data
|
|
76
|
+
// -----------------------------------------------------------------------
|
|
77
|
+
it("NCP-F-05: errors when buffer too small for header", () => {
|
|
78
|
+
// Only 1 byte — need at least 2 for type + flags
|
|
79
|
+
expect(() => parseFrameHeader(new Uint8Array([0x01]))).toThrow(NcpError);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("NCP-F-05: errors when buffer too small for default header", () => {
|
|
83
|
+
// 3 bytes — need 4 for default header
|
|
84
|
+
expect(() => parseFrameHeader(new Uint8Array([0x01, 0x00, 0x00]))).toThrow(
|
|
85
|
+
NcpError,
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("NCP-F-05: errors when buffer too small for extended header", () => {
|
|
90
|
+
// 4 bytes with EXT=1 — need 8
|
|
91
|
+
expect(() =>
|
|
92
|
+
parseFrameHeader(new Uint8Array([0x01, 0x80, 0x00, 0x00])),
|
|
93
|
+
).toThrow(NcpError);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// -----------------------------------------------------------------------
|
|
97
|
+
// NCP-F-06: Buffer Overrun
|
|
98
|
+
// Spec: §3.1 — Extra bytes are buffered for next frame
|
|
99
|
+
// -----------------------------------------------------------------------
|
|
100
|
+
it("NCP-F-06: parses header from oversized buffer", () => {
|
|
101
|
+
// 4-byte header + 250 extra bytes — header parses, remainder ignored
|
|
102
|
+
const buf = new Uint8Array(254);
|
|
103
|
+
buf[0] = 0x01; // AnchorFrame
|
|
104
|
+
buf[1] = 0x00; // no flags
|
|
105
|
+
buf[2] = 0x00;
|
|
106
|
+
buf[3] = 0x64; // payload = 100
|
|
107
|
+
|
|
108
|
+
const h = parseFrameHeader(buf);
|
|
109
|
+
expect(h.payloadLength).toBe(100);
|
|
110
|
+
// Codec would read buf[4..104] as payload, buffer buf[104..] for next frame
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// -----------------------------------------------------------------------
|
|
114
|
+
// NCP-F-07: Reserved Flags Non-Zero
|
|
115
|
+
// Spec: §3.2 — Bits 4-6 MUST be 0
|
|
116
|
+
// -----------------------------------------------------------------------
|
|
117
|
+
it("NCP-F-07: rejects non-zero reserved flag bits (bit 4)", () => {
|
|
118
|
+
const buf = new Uint8Array([0x01, 0x10, 0x00, 0x20]); // bit 4 set
|
|
119
|
+
expect(() => parseFrameHeader(buf)).toThrow(NcpError);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("NCP-F-07: rejects non-zero reserved flag bits (bit 5)", () => {
|
|
123
|
+
const buf = new Uint8Array([0x01, 0x20, 0x00, 0x20]); // bit 5 set
|
|
124
|
+
expect(() => parseFrameHeader(buf)).toThrow(NcpError);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("NCP-F-07: rejects non-zero reserved flag bits (bit 6)", () => {
|
|
128
|
+
const buf = new Uint8Array([0x01, 0x40, 0x00, 0x20]); // bit 6 set
|
|
129
|
+
expect(() => parseFrameHeader(buf)).toThrow(NcpError);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("NCP-F-07: error code is NCP-FRAME-FLAGS-INVALID", () => {
|
|
133
|
+
const buf = new Uint8Array([0x01, 0x20, 0x00, 0x20]);
|
|
134
|
+
try {
|
|
135
|
+
parseFrameHeader(buf);
|
|
136
|
+
expect.unreachable("should have thrown");
|
|
137
|
+
} catch (e) {
|
|
138
|
+
expect(e).toBeInstanceOf(NcpError);
|
|
139
|
+
expect((e as NcpError).code).toBe("NCP-FRAME-FLAGS-INVALID");
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// -----------------------------------------------------------------------
|
|
144
|
+
// Additional: Round-trip, ENC flag, write validation
|
|
145
|
+
// -----------------------------------------------------------------------
|
|
146
|
+
it("round-trips default header", () => {
|
|
147
|
+
const flags = buildFlags({ tier: EncodingTier.MsgPack, final: true });
|
|
148
|
+
const original = {
|
|
149
|
+
frameType: FrameType.Caps,
|
|
150
|
+
flags,
|
|
151
|
+
payloadLength: 1024,
|
|
152
|
+
tier: EncodingTier.MsgPack,
|
|
153
|
+
isFinal: true,
|
|
154
|
+
isEncrypted: false,
|
|
155
|
+
isExtended: false,
|
|
156
|
+
headerSize: DEFAULT_HEADER_SIZE,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const buf = new Uint8Array(DEFAULT_HEADER_SIZE);
|
|
160
|
+
writeFrameHeader(original, buf);
|
|
161
|
+
const parsed = parseFrameHeader(buf);
|
|
162
|
+
|
|
163
|
+
expect(parsed.frameType).toBe(original.frameType);
|
|
164
|
+
expect(parsed.payloadLength).toBe(original.payloadLength);
|
|
165
|
+
expect(parsed.tier).toBe(EncodingTier.MsgPack);
|
|
166
|
+
expect(parsed.isFinal).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("round-trips extended header", () => {
|
|
170
|
+
const flags = buildFlags({ tier: EncodingTier.Json, extended: true });
|
|
171
|
+
const original = {
|
|
172
|
+
frameType: FrameType.Stream,
|
|
173
|
+
flags,
|
|
174
|
+
payloadLength: 100_000,
|
|
175
|
+
tier: EncodingTier.Json,
|
|
176
|
+
isFinal: false,
|
|
177
|
+
isEncrypted: false,
|
|
178
|
+
isExtended: true,
|
|
179
|
+
headerSize: EXTENDED_HEADER_SIZE,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const buf = new Uint8Array(EXTENDED_HEADER_SIZE);
|
|
183
|
+
writeFrameHeader(original, buf);
|
|
184
|
+
const parsed = parseFrameHeader(buf);
|
|
185
|
+
|
|
186
|
+
expect(parsed.frameType).toBe(original.frameType);
|
|
187
|
+
expect(parsed.payloadLength).toBe(100_000);
|
|
188
|
+
expect(parsed.isExtended).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("detects ENC flag", () => {
|
|
192
|
+
const flags = buildFlags({ encrypted: true });
|
|
193
|
+
const buf = new Uint8Array([0x01, flags, 0x00, 0x20]);
|
|
194
|
+
const h = parseFrameHeader(buf);
|
|
195
|
+
expect(h.isEncrypted).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("write errors on undersized buffer", () => {
|
|
199
|
+
const h = {
|
|
200
|
+
frameType: FrameType.Anchor,
|
|
201
|
+
flags: 0x00,
|
|
202
|
+
payloadLength: 32,
|
|
203
|
+
tier: EncodingTier.Json,
|
|
204
|
+
isFinal: false,
|
|
205
|
+
isEncrypted: false,
|
|
206
|
+
isExtended: false,
|
|
207
|
+
headerSize: DEFAULT_HEADER_SIZE,
|
|
208
|
+
};
|
|
209
|
+
expect(() => writeFrameHeader(h, new Uint8Array(2))).toThrow();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("extended header reserved bytes are zero", () => {
|
|
213
|
+
const flags = buildFlags({ extended: true });
|
|
214
|
+
const h = {
|
|
215
|
+
frameType: FrameType.Anchor,
|
|
216
|
+
flags,
|
|
217
|
+
payloadLength: 70_000,
|
|
218
|
+
tier: EncodingTier.Json,
|
|
219
|
+
isFinal: false,
|
|
220
|
+
isEncrypted: false,
|
|
221
|
+
isExtended: true,
|
|
222
|
+
headerSize: EXTENDED_HEADER_SIZE,
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const buf = new Uint8Array(EXTENDED_HEADER_SIZE);
|
|
226
|
+
buf[6] = 0xff; // pre-fill to prove write zeros
|
|
227
|
+
buf[7] = 0xff;
|
|
228
|
+
writeFrameHeader(h, buf);
|
|
229
|
+
|
|
230
|
+
expect(buf[6]).toBe(0);
|
|
231
|
+
expect(buf[7]).toBe(0);
|
|
232
|
+
});
|
|
233
|
+
});
|