@taprootio/docs-artifact 1.0.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.
Files changed (36) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +362 -0
  3. package/bin/taproot-docs-conformance.js +20 -0
  4. package/bin/taproot-docs-validate.js +17 -0
  5. package/conformance.d.ts +11 -0
  6. package/fixtures/README.md +24 -0
  7. package/fixtures/conformance.json +1630 -0
  8. package/fixtures/invalid/duplicate-json-key.json +1 -0
  9. package/fixtures/invalid/hash-drift/taproot-docs/fragments/welcome.html +1 -0
  10. package/fixtures/invalid/size-drift/taproot-docs/fragments/welcome.html +1 -0
  11. package/fixtures/invalid/unsafe-markup/taproot-docs/fragments/welcome.html +1 -0
  12. package/fixtures/valid/complete/taproot-docs/assets/pixel.png.base64 +1 -0
  13. package/fixtures/valid/complete/taproot-docs/fragments/button.en-us.html +1 -0
  14. package/fixtures/valid/complete/taproot-docs/fragments/button.fr-fr.html +1 -0
  15. package/fixtures/valid/complete/taproot-docs/fragments/getting-started.en-us.html +3 -0
  16. package/fixtures/valid/complete/taproot-docs/fragments/getting-started.fr-fr.html +3 -0
  17. package/fixtures/valid/complete/taproot-docs-manifest.json +231 -0
  18. package/fixtures/valid/minimal/taproot-docs/fragments/welcome.html +1 -0
  19. package/fixtures/valid/minimal/taproot-docs-manifest.json +80 -0
  20. package/index.d.ts +204 -0
  21. package/node.d.ts +6 -0
  22. package/package.json +54 -0
  23. package/schema/taproot-docs-manifest.schema.json +487 -0
  24. package/src/artifact-validator.js +870 -0
  25. package/src/binary.js +67 -0
  26. package/src/conformance.js +578 -0
  27. package/src/constants.js +104 -0
  28. package/src/errors.js +139 -0
  29. package/src/index.js +18 -0
  30. package/src/json.js +516 -0
  31. package/src/manifest-validator.js +650 -0
  32. package/src/markup.js +578 -0
  33. package/src/node-internal.js +4 -0
  34. package/src/node.js +513 -0
  35. package/src/path.js +103 -0
  36. package/src/text.js +30 -0
package/src/binary.js ADDED
@@ -0,0 +1,67 @@
1
+ const PlainArrayBuffer = ArrayBuffer;
2
+ const PlainUint8Array = Uint8Array;
3
+ const typedArrayPrototype = Object.getPrototypeOf(PlainUint8Array.prototype);
4
+ const typedArrayBuffer = Object.getOwnPropertyDescriptor(typedArrayPrototype, "buffer").get;
5
+ const typedArrayByteOffset = Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteOffset").get;
6
+ const typedArrayByteLength = Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteLength").get;
7
+ const typedArrayName = Object.getOwnPropertyDescriptor(typedArrayPrototype, Symbol.toStringTag).get;
8
+ const typedArraySet = PlainUint8Array.prototype.set;
9
+ const arrayBufferByteLength = Object.getOwnPropertyDescriptor(PlainArrayBuffer.prototype, "byteLength").get;
10
+ const sharedArrayBufferByteLength = typeof SharedArrayBuffer === "undefined"
11
+ ? undefined
12
+ : Object.getOwnPropertyDescriptor(SharedArrayBuffer.prototype, "byteLength").get;
13
+
14
+ export function classifyBinaryInput(input) {
15
+ if (input === null || (typeof input !== "object" && typeof input !== "function")) return { kind: "other" };
16
+ try {
17
+ const name = typedArrayName.call(input);
18
+ if (name !== undefined) return { kind: name === "Uint8Array" ? "uint8array" : "unsupported_binary" };
19
+ } catch {
20
+ // Continue with the remaining internal-slot probes.
21
+ }
22
+ try {
23
+ arrayBufferByteLength.call(input);
24
+ return { kind: "arraybuffer" };
25
+ } catch {
26
+ // Continue with the remaining internal-slot probes.
27
+ }
28
+ if (sharedArrayBufferByteLength) {
29
+ try {
30
+ sharedArrayBufferByteLength.call(input);
31
+ return { kind: "unsupported_binary" };
32
+ } catch {
33
+ // Continue with ordinary object classification.
34
+ }
35
+ }
36
+ return { kind: "other" };
37
+ }
38
+
39
+ export function snapshotBinaryInput(input, maximumBytes, classification = classifyBinaryInput(input)) {
40
+ try {
41
+ if (classification.kind === "uint8array") {
42
+ const byteLength = typedArrayByteLength.call(input);
43
+ if (byteLength > maximumBytes) return { kind: "too_large", byteLength };
44
+ const buffer = typedArrayBuffer.call(input);
45
+ const byteOffset = typedArrayByteOffset.call(input);
46
+ const bufferByteLength = arrayBufferByteLength.call(buffer);
47
+ if (byteOffset + byteLength > bufferByteLength) return { kind: "invalid" };
48
+ const source = new PlainUint8Array(buffer, byteOffset, byteLength);
49
+ const bytes = new PlainUint8Array(byteLength);
50
+ typedArraySet.call(bytes, source);
51
+ if (typedArrayByteLength.call(bytes) !== byteLength) return { kind: "invalid" };
52
+ return { kind: "bytes", bytes, byteLength };
53
+ }
54
+ if (classification.kind === "arraybuffer") {
55
+ const byteLength = arrayBufferByteLength.call(input);
56
+ if (byteLength > maximumBytes) return { kind: "too_large", byteLength };
57
+ const source = new PlainUint8Array(input);
58
+ const bytes = new PlainUint8Array(byteLength);
59
+ typedArraySet.call(bytes, source);
60
+ if (typedArrayByteLength.call(bytes) !== byteLength) return { kind: "invalid" };
61
+ return { kind: "bytes", bytes, byteLength };
62
+ }
63
+ } catch {
64
+ return { kind: "invalid" };
65
+ }
66
+ return { kind: "invalid" };
67
+ }
@@ -0,0 +1,578 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFile } from "node:fs/promises";
3
+ import { deflateSync } from "node:zlib";
4
+ import { LIMITS } from "./constants.js";
5
+
6
+ const FIXTURES_ROOT = new URL("../fixtures/", import.meta.url);
7
+
8
+ function generatedGif(specification) {
9
+ const header = Buffer.alloc(13);
10
+ header.write("GIF89a", 0, 6, "ascii");
11
+ header.writeUInt16LE(specification.width, 6);
12
+ header.writeUInt16LE(specification.height, 8);
13
+ const palette = specification.palette ?? "global";
14
+ if (palette === "global") header[10] = 0x80;
15
+ const frame = Buffer.from([
16
+ 0x2c,
17
+ 0x00, 0x00, 0x00, 0x00,
18
+ 0x00, 0x00, 0x00, 0x00,
19
+ palette === "local" ? 0x80 : 0x00,
20
+ ...(palette === "local" ? [0x00, 0x00, 0x00, 0xff, 0xff, 0xff] : []),
21
+ 0x02, 0x01, 0x4c, 0x00,
22
+ ]);
23
+ frame.writeUInt16LE(specification.frameWidth ?? specification.width, 5);
24
+ frame.writeUInt16LE(specification.frameHeight ?? specification.height, 7);
25
+ let extension = Buffer.alloc(0);
26
+ if (specification.extension === "gce") extension = Buffer.from([0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00]);
27
+ if (specification.extension === "application") {
28
+ extension = Buffer.concat([Buffer.from([0x21, 0xff, 0x0b]), Buffer.from("NETSCAPE2.0", "ascii"), Buffer.from([0x03, 0x01, 0x00, 0x00, 0x00])]);
29
+ }
30
+ if (specification.extension === "plain-text") extension = Buffer.concat([Buffer.from([0x21, 0x01, 0x0c]), Buffer.alloc(12), Buffer.from([0x01, 0x41, 0x00])]);
31
+ if (specification.extension === "comment") extension = Buffer.from([0x21, 0xfe, 0x01, 0x41, 0x00]);
32
+ if (specification.extensionFault === "gce-size") extension = Buffer.from([0x21, 0xf9, 0x03, 0x00, 0x00, 0x00, 0x00]);
33
+ if (specification.extensionFault === "gce-terminator") extension = Buffer.from([0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01]);
34
+ if (specification.extensionFault === "gce-reserved") extension = Buffer.from([0x21, 0xf9, 0x04, 0x20, 0x00, 0x00, 0x00, 0x00]);
35
+ if (specification.extensionFault === "unknown") extension = Buffer.from([0x21, 0x02, 0x00]);
36
+ if (specification.imageReserved) frame[9] |= 0x08;
37
+ return Buffer.concat([
38
+ header,
39
+ ...(palette === "global" ? [Buffer.from([0x00, 0x00, 0x00, 0xff, 0xff, 0xff])] : []),
40
+ extension,
41
+ ...Array.from({ length: specification.frames }, () => frame),
42
+ Buffer.from([0x3b]),
43
+ ]);
44
+ }
45
+
46
+ const PNG_CRC_TABLE = Uint32Array.from({ length: 256 }, (_, value) => {
47
+ let crc = value;
48
+ for (let bit = 0; bit < 8; bit += 1) crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));
49
+ return crc >>> 0;
50
+ });
51
+
52
+ function pngChunk(type, data) {
53
+ const typeBytes = Buffer.from(type, "ascii");
54
+ let checksum = 0xffffffff;
55
+ for (const byte of Buffer.concat([typeBytes, data])) checksum = PNG_CRC_TABLE[(checksum ^ byte) & 0xff] ^ (checksum >>> 8);
56
+ const chunk = Buffer.alloc(12 + data.length);
57
+ chunk.writeUInt32BE(data.length, 0);
58
+ typeBytes.copy(chunk, 4);
59
+ data.copy(chunk, 8);
60
+ chunk.writeUInt32BE((checksum ^ 0xffffffff) >>> 0, 8 + data.length);
61
+ return chunk;
62
+ }
63
+
64
+ function generatedPng(specification) {
65
+ const width = specification.width ?? 1;
66
+ const height = specification.height ?? 1;
67
+ const bitDepth = specification.bitDepth ?? 8;
68
+ const colorType = specification.colorType ?? 4;
69
+ const ihdr = Buffer.alloc(13);
70
+ ihdr.writeUInt32BE(width, 0);
71
+ ihdr.writeUInt32BE(height, 4);
72
+ ihdr.set([bitDepth, colorType, 0, 0, 0], 8);
73
+ const chunks = [pngChunk("IHDR", ihdr)];
74
+ if (specification.reservedChunk) chunks.push(pngChunk("texT", Buffer.alloc(0)));
75
+ if (specification.paletteEntries !== undefined && !specification.paletteAfterData) {
76
+ chunks.push(pngChunk("PLTE", Buffer.alloc(specification.paletteEntries * 3)));
77
+ }
78
+ const channels = new Map([[0, 1], [2, 3], [3, 1], [4, 2], [6, 4]]).get(colorType) ?? 1;
79
+ const raw = Buffer.alloc((width * channels) + 1);
80
+ const imageData = deflateSync(raw);
81
+ const frames = specification.frames ?? 1;
82
+ if (frames > 1 || specification.animated) {
83
+ const animationControl = Buffer.alloc(8);
84
+ animationControl.writeUInt32BE(specification.declaredFrames ?? frames, 0);
85
+ chunks.push(pngChunk("acTL", animationControl));
86
+ let sequence = specification.firstSequence ?? 0;
87
+ const frameControl = (currentSequence, options = {}) => {
88
+ const control = Buffer.alloc(26);
89
+ control.writeUInt32BE(currentSequence, 0);
90
+ control.writeUInt32BE(width, 4);
91
+ control.writeUInt32BE(height, 8);
92
+ control.writeUInt16BE(1, 20);
93
+ control.writeUInt16BE(100, 22);
94
+ control[24] = options.dispose ?? 0;
95
+ control[25] = options.blend ?? 0;
96
+ return pngChunk("fcTL", control);
97
+ };
98
+ const frameData = (currentSequence) => {
99
+ const data = Buffer.alloc(4 + imageData.length);
100
+ data.writeUInt32BE(currentSequence, 0);
101
+ imageData.copy(data, 4);
102
+ return pngChunk("fdAT", data);
103
+ };
104
+ chunks.push(frameControl(sequence, { dispose: specification.dispose, blend: specification.blend }));
105
+ sequence += 1;
106
+ if (!specification.omitFirstData) chunks.push(pngChunk("IDAT", imageData));
107
+ if (frames > 1) {
108
+ if (specification.fdatBeforeControl) chunks.push(frameData(sequence));
109
+ if (specification.sequenceGap) sequence += 1;
110
+ chunks.push(frameControl(sequence));
111
+ sequence += 1;
112
+ if (!specification.omitSecondData && !specification.fdatBeforeControl) chunks.push(frameData(sequence));
113
+ }
114
+ } else {
115
+ if (specification.leadingZeroIdat) chunks.push(pngChunk("IDAT", Buffer.alloc(0)));
116
+ if (specification.interruptZeroIdat) chunks.push(pngChunk("tEXt", Buffer.alloc(0)));
117
+ chunks.push(pngChunk("IDAT", imageData));
118
+ }
119
+ if (specification.paletteEntries !== undefined && specification.paletteAfterData) {
120
+ chunks.push(pngChunk("PLTE", Buffer.alloc(specification.paletteEntries * 3)));
121
+ }
122
+ chunks.push(pngChunk("IEND", Buffer.alloc(0)));
123
+ return Buffer.concat([Buffer.from("iVBORw0KGgo=", "base64"), ...chunks]);
124
+ }
125
+
126
+ const REAL_JPEG = Buffer.from([
127
+ 0xff, 0xd8,
128
+ 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
129
+ 0xff, 0xdb, 0x00, 0x43, 0x00, ...Array(64).fill(0x01),
130
+ 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x01, 0x00, 0x01, 0x03,
131
+ 0x01, 0x11, 0x00, 0x02, 0x11, 0x00, 0x03, 0x11, 0x00,
132
+ 0xff, 0xc4, 0x00, 0x14, 0x00, 0x01, ...Array(15).fill(0x00), 0x00,
133
+ 0xff, 0xc4, 0x00, 0x14, 0x10, 0x01, ...Array(15).fill(0x00), 0x00,
134
+ 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x3f, 0x00,
135
+ 0x03, 0xff, 0xd9,
136
+ ]);
137
+
138
+ function jpegHuffmanSegment(tableClass, counts, symbols) {
139
+ const payload = Buffer.from([(tableClass << 4), ...counts, ...symbols]);
140
+ const header = Buffer.from([0xff, 0xc4, 0x00, 0x00]);
141
+ header.writeUInt16BE(payload.length + 2, 2);
142
+ return Buffer.concat([header, payload]);
143
+ }
144
+
145
+ function replaceJpegSegment(bytes, offset, replacement) {
146
+ const segmentEnd = offset + 2 + bytes.readUInt16BE(offset + 2);
147
+ return Buffer.concat([bytes.subarray(0, offset), replacement, bytes.subarray(segmentEnd)]);
148
+ }
149
+
150
+ function jpegWithEntropy(bytes, entropy, restartInterval) {
151
+ const scanOffset = bytes.indexOf(Buffer.from([0xff, 0xda]));
152
+ const entropyOffset = scanOffset + 2 + bytes.readUInt16BE(scanOffset + 2);
153
+ const endOffset = bytes.lastIndexOf(Buffer.from([0xff, 0xd9]));
154
+ const restartDefinition = restartInterval === undefined
155
+ ? Buffer.alloc(0)
156
+ : Buffer.from([0xff, 0xdd, 0x00, 0x04, restartInterval >>> 8, restartInterval & 0xff]);
157
+ return Buffer.concat([
158
+ bytes.subarray(0, scanOffset),
159
+ restartDefinition,
160
+ bytes.subarray(scanOffset, entropyOffset),
161
+ Buffer.from(entropy),
162
+ bytes.subarray(endOffset),
163
+ ]);
164
+ }
165
+
166
+ function jpegSegment(bytes, offset) {
167
+ const end = offset + 2 + bytes.readUInt16BE(offset + 2);
168
+ return Buffer.from(bytes.subarray(offset, end));
169
+ }
170
+
171
+ function jpegScan({ components, selectors = [], spectralEnd = 63, restartMarkers = 0 }) {
172
+ const length = 6 + (2 * components.length);
173
+ const scan = Buffer.alloc(2 + length);
174
+ scan.set([0xff, 0xda], 0);
175
+ scan.writeUInt16BE(length, 2);
176
+ scan[4] = components.length;
177
+ for (let index = 0; index < components.length; index += 1) {
178
+ scan[5 + (2 * index)] = components[index];
179
+ scan[6 + (2 * index)] = selectors[index] ?? 0;
180
+ }
181
+ const spectralOffset = 5 + (2 * components.length);
182
+ scan[spectralOffset] = 0;
183
+ scan[spectralOffset + 1] = spectralEnd;
184
+ scan[spectralOffset + 2] = 0;
185
+ const entropyByte = (1 << (8 - (2 * components.length))) - 1;
186
+ const entropy = [];
187
+ for (let index = 0; index <= restartMarkers; index += 1) {
188
+ entropy.push(entropyByte);
189
+ if (index < restartMarkers) entropy.push(0xff, 0xd0 + (index & 0x07));
190
+ }
191
+ return Buffer.concat([scan, Buffer.from(entropy)]);
192
+ }
193
+
194
+ function generatedSequentialJpeg(fault) {
195
+ const bytes = Buffer.from(REAL_JPEG);
196
+ const quantizationOffset = bytes.indexOf(Buffer.from([0xff, 0xdb]));
197
+ const frameOffset = bytes.indexOf(Buffer.from([0xff, 0xc0]));
198
+ const dcHuffmanOffset = bytes.indexOf(Buffer.from([0xff, 0xc4]));
199
+ const acHuffmanOffset = bytes.indexOf(Buffer.from([0xff, 0xc4]), dcHuffmanOffset + 2);
200
+ const scanOffset = bytes.indexOf(Buffer.from([0xff, 0xda]));
201
+ const endOffset = bytes.lastIndexOf(Buffer.from([0xff, 0xd9]));
202
+ bytes[frameOffset + 15] = 1;
203
+ const restartGeometry = ["multi-scan-restarts", "multi-scan-restart-cardinality"].includes(fault);
204
+ if (restartGeometry) {
205
+ bytes.writeUInt16BE(17, frameOffset + 7);
206
+ bytes[frameOffset + 11] = 0x21;
207
+ }
208
+ let firstScanRestartMarkers = 0;
209
+ if (fault === "multi-scan-restarts") firstScanRestartMarkers = 2;
210
+ else if (fault === "multi-scan-restart-cardinality") firstScanRestartMarkers = 1;
211
+ const lateQuantization = jpegSegment(bytes, quantizationOffset);
212
+ lateQuantization[4] = 1;
213
+ const metadata = Buffer.concat([
214
+ ...(fault === "multi-scan-missing-quantization" ? [] : [lateQuantization]),
215
+ jpegSegment(bytes, dcHuffmanOffset),
216
+ jpegSegment(bytes, acHuffmanOffset),
217
+ Buffer.from([0xff, 0xdd, 0x00, 0x04, 0x00, 0x02]),
218
+ ]);
219
+ const scans = [
220
+ jpegScan({
221
+ components: [1],
222
+ restartMarkers: firstScanRestartMarkers,
223
+ }),
224
+ metadata,
225
+ jpegScan({
226
+ components: [2],
227
+ selectors: fault === "multi-scan-missing-huffman" ? [0x11] : [],
228
+ spectralEnd: fault === "multi-scan-spectral" ? 62 : 63,
229
+ }),
230
+ ];
231
+ if (fault === "multi-scan-duplicate") scans.push(jpegScan({ components: [2] }));
232
+ if (fault !== "multi-scan-missing") scans.push(jpegScan({ components: [3] }));
233
+ const initialRestart = restartGeometry ? Buffer.from([0xff, 0xdd, 0x00, 0x04, 0x00, 0x01]) : Buffer.alloc(0);
234
+ return Buffer.concat([bytes.subarray(0, scanOffset), initialRestart, ...scans, bytes.subarray(endOffset)]);
235
+ }
236
+
237
+ function generatedJpeg(specification) {
238
+ if (specification.fault === "header-only") {
239
+ return Buffer.from([0xff, 0xd8, 0xff, 0xc0, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x11, 0x00, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0x00, 0xff, 0xd9]);
240
+ }
241
+ if (specification.fault?.startsWith("multi-scan")) return generatedSequentialJpeg(specification.fault);
242
+ let bytes = Buffer.from(REAL_JPEG);
243
+ const quantizationOffset = bytes.indexOf(Buffer.from([0xff, 0xdb]));
244
+ const frameOffset = bytes.indexOf(Buffer.from([0xff, 0xc0]));
245
+ const dcHuffmanOffset = bytes.indexOf(Buffer.from([0xff, 0xc4]));
246
+ const acHuffmanOffset = bytes.indexOf(Buffer.from([0xff, 0xc4]), dcHuffmanOffset + 2);
247
+ const scanOffset = bytes.indexOf(Buffer.from([0xff, 0xda]));
248
+ if (specification.fault === "dqt-after-sof") {
249
+ const quantizationEnd = quantizationOffset + 2 + bytes.readUInt16BE(quantizationOffset + 2);
250
+ const quantization = bytes.subarray(quantizationOffset, quantizationEnd);
251
+ bytes = Buffer.concat([bytes.subarray(0, quantizationOffset), bytes.subarray(quantizationEnd)]);
252
+ const relocatedFrameOffset = bytes.indexOf(Buffer.from([0xff, 0xc0]));
253
+ const frameEnd = relocatedFrameOffset + 2 + bytes.readUInt16BE(relocatedFrameOffset + 2);
254
+ bytes = Buffer.concat([bytes.subarray(0, frameEnd), quantization, bytes.subarray(frameEnd)]);
255
+ }
256
+ if (specification.fault === "arithmetic") bytes[frameOffset + 1] = 0xc9;
257
+ if (specification.fault === "missing-quantization") bytes[frameOffset + 12] = 3;
258
+ if (specification.fault === "duplicate-component") bytes[frameOffset + 13] = bytes[frameOffset + 10];
259
+ if (specification.fault === "missing-huffman") bytes[scanOffset + 6] = 0x33;
260
+ if (specification.fault === "dqt-16-bit") {
261
+ const payload = Buffer.from([0x10, ...Array(128).fill(0x01)]);
262
+ const header = Buffer.from([0xff, 0xdb, 0x00, 0x00]);
263
+ header.writeUInt16BE(payload.length + 2, 2);
264
+ bytes = replaceJpegSegment(bytes, quantizationOffset, Buffer.concat([header, payload]));
265
+ }
266
+ if (specification.fault === "dqt-zero") bytes[quantizationOffset + 5] = 0;
267
+ const emptyCounts = () => Array(16).fill(0);
268
+ if (specification.fault === "dht-oversubscribed") {
269
+ const counts = emptyCounts();
270
+ counts[0] = 3;
271
+ bytes = replaceJpegSegment(bytes, dcHuffmanOffset, jpegHuffmanSegment(0, counts, [0, 1, 2]));
272
+ }
273
+ if (specification.fault === "dht-exhausted") {
274
+ const counts = emptyCounts();
275
+ counts[0] = 2;
276
+ bytes = replaceJpegSegment(bytes, dcHuffmanOffset, jpegHuffmanSegment(0, counts, [0, 1]));
277
+ }
278
+ if (specification.fault === "dht-all-ones") {
279
+ const counts = emptyCounts();
280
+ counts[0] = 1;
281
+ counts[1] = 2;
282
+ bytes = replaceJpegSegment(bytes, dcHuffmanOffset, jpegHuffmanSegment(0, counts, [0, 1, 2]));
283
+ }
284
+ if (specification.fault === "dht-too-many") {
285
+ const counts = emptyCounts();
286
+ counts[8] = 255;
287
+ counts[9] = 2;
288
+ bytes = replaceJpegSegment(bytes, dcHuffmanOffset, jpegHuffmanSegment(0, counts, Buffer.alloc(257)));
289
+ }
290
+ if (specification.fault === "dht-invalid-dc") {
291
+ const counts = emptyCounts();
292
+ counts[0] = 1;
293
+ bytes = replaceJpegSegment(bytes, dcHuffmanOffset, jpegHuffmanSegment(0, counts, [12]));
294
+ }
295
+ if (specification.fault === "dht-invalid-ac" || specification.fault === "dht-zero-size-ac") {
296
+ const counts = emptyCounts();
297
+ counts[0] = 1;
298
+ const symbol = specification.fault === "dht-invalid-ac" ? 0x0b : 0x10;
299
+ bytes = replaceJpegSegment(bytes, acHuffmanOffset, jpegHuffmanSegment(1, counts, [symbol]));
300
+ }
301
+ if (specification.fault === "rst-only") {
302
+ bytes.writeUInt16BE(9, frameOffset + 7);
303
+ bytes = jpegWithEntropy(bytes, [0xff, 0xd0], 1);
304
+ }
305
+ if (specification.fault === "rst-consecutive") {
306
+ bytes.writeUInt16BE(17, frameOffset + 7);
307
+ bytes = jpegWithEntropy(bytes, [0x03, 0xff, 0xd0, 0xff, 0xd1, 0x03], 1);
308
+ }
309
+ if (specification.fault === "rst-no-final-entropy") {
310
+ bytes.writeUInt16BE(9, frameOffset + 7);
311
+ bytes = jpegWithEntropy(bytes, [0x03, 0xff, 0xd0], 1);
312
+ }
313
+ if (specification.fault === "stuffing-multiple-ff") bytes = jpegWithEntropy(bytes, [0x03, 0xff, 0xff, 0x00, 0x03]);
314
+ if (specification.fault === "fill-before-eoi") bytes = jpegWithEntropy(bytes, [0x03, 0xff]);
315
+ if (specification.fault === "fill-before-rst") {
316
+ bytes.writeUInt16BE(9, frameOffset + 7);
317
+ bytes = jpegWithEntropy(bytes, [0x03, 0xff, 0xff, 0xd0, 0x03], 1);
318
+ }
319
+ if (specification.fault === "rst-missing") {
320
+ bytes.writeUInt16BE(17, frameOffset + 7);
321
+ bytes = jpegWithEntropy(bytes, [0x03, 0xff, 0xd0, 0x03], 1);
322
+ }
323
+ if (specification.fault === "rst-excess") {
324
+ bytes.writeUInt16BE(9, frameOffset + 7);
325
+ bytes = jpegWithEntropy(bytes, [0x03, 0xff, 0xd0, 0x03, 0xff, 0xd1, 0x03], 1);
326
+ }
327
+ if (specification.fault === "rst-exact") {
328
+ bytes.writeUInt16BE(17, frameOffset + 7);
329
+ bytes = jpegWithEntropy(bytes, [0x03, 0xff, 0xd0, 0x03, 0xff, 0xd1, 0x03], 1);
330
+ }
331
+ if (specification.fault === "rst-interval-over-mcus") bytes = jpegWithEntropy(bytes, [0x03], 2);
332
+ if (specification.fault === "sampling-units") {
333
+ bytes[frameOffset + 11] = 0x44;
334
+ bytes[frameOffset + 14] = 0x44;
335
+ bytes[frameOffset + 17] = 0x44;
336
+ }
337
+ return bytes;
338
+ }
339
+
340
+ function webpChunk(type, data, padByte = 0) {
341
+ const header = Buffer.alloc(8);
342
+ header.write(type, 0, 4, "ascii");
343
+ header.writeUInt32LE(data.length, 4);
344
+ return Buffer.concat([header, data, ...(data.length % 2 === 1 ? [Buffer.from([padByte])] : [])]);
345
+ }
346
+
347
+ const REAL_VP8_PAYLOAD = Buffer.from("UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEALmk0mk0iIiIiIgBoSygAAA==", "base64").subarray(20);
348
+ const REAL_VP8L_PAYLOAD = Buffer.from("UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==", "base64").subarray(20, 33);
349
+
350
+ function vp8lPayload(width, height, alphaUsed = false) {
351
+ if (width === 1 && height === 1) {
352
+ const payload = Buffer.from(REAL_VP8L_PAYLOAD);
353
+ payload[4] = (payload[4] & 0x0f) | (alphaUsed ? 0x10 : 0);
354
+ return payload;
355
+ }
356
+ const encodedWidth = width - 1;
357
+ const encodedHeight = height - 1;
358
+ return Buffer.from([
359
+ 0x2f,
360
+ encodedWidth & 0xff,
361
+ ((encodedWidth >> 8) & 0x3f) | ((encodedHeight & 0x03) << 6),
362
+ (encodedHeight >> 2) & 0xff,
363
+ ((encodedHeight >> 10) & 0x0f) | (alphaUsed ? 0x10 : 0),
364
+ 0x00,
365
+ ]);
366
+ }
367
+
368
+ function vp8Payload(width, height) {
369
+ if (width === 1 && height === 1) return Buffer.from(REAL_VP8_PAYLOAD);
370
+ const payload = Buffer.alloc(12);
371
+ payload.set([0x30, 0x00, 0x00, 0x9d, 0x01, 0x2a]);
372
+ payload.writeUInt16LE(width, 6);
373
+ payload.writeUInt16LE(height, 8);
374
+ return payload;
375
+ }
376
+
377
+ function generatedVp8Payload(width, height, specification) {
378
+ const payload = vp8Payload(width, height);
379
+ if (specification.vp8Fault === "interframe") payload[0] |= 0x01;
380
+ if (specification.vp8Fault === "hidden") payload[0] &= ~0x10;
381
+ if (specification.vp8Fault === "experimental") payload[0] = (payload[0] & ~0x0e) | 0x08;
382
+ if (specification.vp8Fault === "empty-partition") payload.set([0x10, 0x00, 0x00], 0);
383
+ if (specification.vp8Fault === "partition-at-end") {
384
+ const tag = ((payload.length - 10) << 5) | 0x10;
385
+ payload.set([tag & 0xff, (tag >>> 8) & 0xff, (tag >>> 16) & 0xff], 0);
386
+ }
387
+ return payload;
388
+ }
389
+
390
+ function alphaPayload(width, height, specification) {
391
+ const dataLength = specification.alphaDataLength ?? width * height;
392
+ return Buffer.concat([Buffer.from([specification.alphaHeader ?? 0]), Buffer.alloc(dataLength, 0xff)]);
393
+ }
394
+
395
+ function generatedAnimatedWebp(specification) {
396
+ const canvasWidth = specification.canvasWidth ?? 1;
397
+ const canvasHeight = specification.canvasHeight ?? 1;
398
+ const frameWidth = specification.frameWidth ?? 1;
399
+ const frameHeight = specification.frameHeight ?? 1;
400
+ const extendedHeader = Buffer.alloc(10);
401
+ extendedHeader[0] = specification.extendedFlags ?? 0x02;
402
+ extendedHeader.writeUIntLE(canvasWidth - 1, 4, 3);
403
+ extendedHeader.writeUIntLE(canvasHeight - 1, 7, 3);
404
+ const frameHeader = Buffer.alloc(16);
405
+ frameHeader.writeUIntLE((specification.frameX ?? 0) / 2, 0, 3);
406
+ frameHeader.writeUIntLE((specification.frameY ?? 0) / 2, 3, 3);
407
+ frameHeader.writeUIntLE(frameWidth - 1, 6, 3);
408
+ frameHeader.writeUIntLE(frameHeight - 1, 9, 3);
409
+ frameHeader[15] = specification.frameFlags ?? 0;
410
+ const nestedChunks = (specification.nestedOrder ?? ["VP8 "]).map((type) => {
411
+ if (type === "ALPH") return webpChunk(type, alphaPayload(frameWidth, frameHeight, specification), specification.nestedPadByte);
412
+ if (type === "VP8 ") {
413
+ return webpChunk(type, generatedVp8Payload(
414
+ specification.embeddedWidth ?? frameWidth,
415
+ specification.embeddedHeight ?? frameHeight,
416
+ specification,
417
+ ), specification.nestedPadByte);
418
+ }
419
+ if (type === "VP8L") {
420
+ const payload = vp8lPayload(
421
+ specification.embeddedWidth ?? frameWidth,
422
+ specification.embeddedHeight ?? frameHeight,
423
+ specification.embeddedAlphaUsed,
424
+ );
425
+ return webpChunk(type, specification.vp8lHeaderOnly ? payload.subarray(0, 5) : payload, specification.nestedPadByte);
426
+ }
427
+ return webpChunk(type, Buffer.from([0]), specification.nestedPadByte);
428
+ });
429
+ const frame = Buffer.concat([
430
+ frameHeader,
431
+ ...nestedChunks,
432
+ ]);
433
+ const chunks = Buffer.concat((specification.topOrder ?? ["VP8X", "ANIM", "ANMF"]).map((type) => {
434
+ if (type === "VP8X") return webpChunk(type, extendedHeader);
435
+ if (type === "ANIM") return webpChunk(type, Buffer.alloc(6));
436
+ if (type === "ANMF") return webpChunk(type, frame);
437
+ if (type === "VP8 ") return webpChunk(type, generatedVp8Payload(canvasWidth, canvasHeight, specification));
438
+ if (type === "VP8L") {
439
+ const payload = vp8lPayload(canvasWidth, canvasHeight, specification.embeddedAlphaUsed);
440
+ return webpChunk(type, specification.vp8lHeaderOnly ? payload.subarray(0, 5) : payload);
441
+ }
442
+ if (type === "ALPH") return webpChunk(type, alphaPayload(canvasWidth, canvasHeight, specification), specification.topPadByte);
443
+ return webpChunk(type, Buffer.from([0]), specification.topPadByte);
444
+ }));
445
+ const riffHeader = Buffer.alloc(12);
446
+ riffHeader.write("RIFF", 0, 4, "ascii");
447
+ riffHeader.writeUInt32LE(4 + chunks.length, 4);
448
+ riffHeader.write("WEBP", 8, 4, "ascii");
449
+ return Buffer.concat([riffHeader, chunks]);
450
+ }
451
+
452
+ function generatedContent(specification) {
453
+ if (specification.kind === "repeat") {
454
+ return Buffer.from(specification.text.repeat(specification.count));
455
+ }
456
+ if (specification.kind === "nested") {
457
+ return Buffer.from(
458
+ `<${specification.tag}>`.repeat(specification.depth)
459
+ + `</${specification.tag}>`.repeat(specification.depth),
460
+ );
461
+ }
462
+ if (specification.kind === "gif") return generatedGif(specification);
463
+ if (specification.kind === "png") return generatedPng(specification);
464
+ if (specification.kind === "jpeg") return generatedJpeg(specification);
465
+ if (specification.kind === "animated-webp") return generatedAnimatedWebp(specification);
466
+ throw new Error(`Unsupported generated conformance content '${specification.kind}'.`);
467
+ }
468
+
469
+ function synchronizeDescriptor(manifest, filePath, bytes) {
470
+ const descriptors = [
471
+ ...manifest.resources.flatMap((resource) => resource.variants.flatMap((variant) => variant.fragments)),
472
+ ...manifest.assets,
473
+ ];
474
+ const descriptor = descriptors.find((candidate) => candidate.path === filePath);
475
+ if (!descriptor) throw new Error(`Generated conformance file '${filePath}' has no manifest descriptor.`);
476
+ descriptor.bytes = bytes.byteLength;
477
+ descriptor.sha256 = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
478
+ }
479
+
480
+ export async function loadConformanceCases() {
481
+ const descriptor = JSON.parse(await readFile(new URL("conformance.json", FIXTURES_ROOT), "utf8"));
482
+ const cases = [];
483
+ for (const fixture of descriptor.cases) {
484
+ let manifest;
485
+ let manifestValue;
486
+ if (fixture.baseManifest) {
487
+ manifestValue = JSON.parse(await readFile(new URL(fixture.baseManifest, FIXTURES_ROOT), "utf8"));
488
+ for (const mutation of fixture.mutations ?? []) {
489
+ let parent = manifestValue;
490
+ for (const segment of mutation.path.slice(0, -1)) parent = parent[segment];
491
+ const key = mutation.path.at(-1);
492
+ if (mutation.operation === "replace") parent[key] = mutation.value;
493
+ else if (mutation.operation === "append") parent[key].push(mutation.value);
494
+ else throw new Error(`Unsupported conformance mutation '${mutation.operation}'.`);
495
+ }
496
+ if (fixture.generatedNavigationDepth !== undefined) {
497
+ let node = { label: `Level ${fixture.generatedNavigationDepth}`, resourceKey: "guide:welcome" };
498
+ for (let level = fixture.generatedNavigationDepth - 1; level >= 1; level -= 1) {
499
+ node = { label: `Level ${level}`, children: [node] };
500
+ }
501
+ manifestValue.navigation[0].items = [node];
502
+ }
503
+ } else {
504
+ manifest = await readFile(new URL(fixture.manifest, FIXTURES_ROOT));
505
+ }
506
+ const fileSpecifications = [...(fixture.files ?? [])];
507
+ if (fixture.generatedAnimatedWebp) {
508
+ if (!manifestValue) throw new Error("Generated animated WebP conformance cases require a base manifest.");
509
+ manifestValue.assets.push({
510
+ key: "image:animated",
511
+ path: "taproot-docs/assets/animated.webp",
512
+ mediaType: "image/webp",
513
+ bytes: 1,
514
+ sha256: `sha256:${"0".repeat(64)}`,
515
+ width: fixture.generatedAnimatedWebp.canvasWidth ?? 1,
516
+ height: fixture.generatedAnimatedWebp.canvasHeight ?? 1,
517
+ });
518
+ fileSpecifications.push(
519
+ { path: "taproot-docs/fragments/welcome.html", source: "valid/minimal/taproot-docs/fragments/welcome.html" },
520
+ {
521
+ path: "taproot-docs/assets/animated.webp",
522
+ generated: { kind: "animated-webp", ...fixture.generatedAnimatedWebp },
523
+ synchronizeDescriptor: true,
524
+ },
525
+ );
526
+ }
527
+ if (fixture.generatedImage) {
528
+ if (!manifestValue) throw new Error("Generated image conformance cases require a base manifest.");
529
+ const extension = fixture.generatedImage.kind === "gif" ? "gif" : fixture.generatedImage.kind === "jpeg" ? "jpg" : "png";
530
+ const mediaType = extension === "jpg" ? "image/jpeg" : `image/${extension}`;
531
+ const path = `taproot-docs/assets/generated.${extension}`;
532
+ manifestValue.assets.push({
533
+ key: "image:generated",
534
+ path,
535
+ mediaType,
536
+ bytes: 1,
537
+ sha256: `sha256:${"0".repeat(64)}`,
538
+ width: fixture.generatedImage.width ?? 1,
539
+ height: fixture.generatedImage.height ?? 1,
540
+ });
541
+ fileSpecifications.push(
542
+ { path: "taproot-docs/fragments/welcome.html", source: "valid/minimal/taproot-docs/fragments/welcome.html" },
543
+ { path, generated: fixture.generatedImage, synchronizeDescriptor: true },
544
+ );
545
+ }
546
+ const files = [];
547
+ for (const file of fileSpecifications) {
548
+ const encoded = file.base64Source
549
+ ? (await readFile(new URL(file.base64Source, FIXTURES_ROOT), "utf8")).trim()
550
+ : file.base64;
551
+ const content = file.generated
552
+ ? generatedContent(file.generated)
553
+ : file.source
554
+ ? await readFile(new URL(file.source, FIXTURES_ROOT))
555
+ : Buffer.from(encoded, "base64");
556
+ if (file.synchronizeDescriptor) {
557
+ if (!manifestValue) manifestValue = JSON.parse(manifest.toString("utf8"));
558
+ synchronizeDescriptor(manifestValue, file.path, content);
559
+ }
560
+ files.push({
561
+ path: file.path,
562
+ content,
563
+ });
564
+ }
565
+ if (manifestValue) manifest = Buffer.from(JSON.stringify(manifestValue));
566
+ if (fixture.manifestPaddingBytes) {
567
+ manifest = Buffer.concat([manifest, Buffer.alloc(Math.min(fixture.manifestPaddingBytes, LIMITS.manifestBytes + 1), 0x20)]);
568
+ }
569
+ cases.push({
570
+ name: fixture.name,
571
+ valid: fixture.valid,
572
+ expectedCodes: fixture.expectedCodes,
573
+ manifest,
574
+ files,
575
+ });
576
+ }
577
+ return cases;
578
+ }