@stigmer/runner 3.12.9 → 3.13.0

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 (34) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/activities/call-agent.d.ts +3 -2
  3. package/dist/activities/call-agent.js +13 -4
  4. package/dist/activities/call-agent.js.map +1 -1
  5. package/dist/activities/discover-mcp-server.js +1 -1
  6. package/dist/activities/execute-deep-agent/attachment-injector.d.ts +1 -1
  7. package/dist/activities/execute-deep-agent/attachment-injector.js +2 -2
  8. package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
  9. package/dist/client/stigmer-client.js +7 -2
  10. package/dist/client/stigmer-client.js.map +1 -1
  11. package/dist/runner-manager.js +1 -1
  12. package/dist/runner-manager.js.map +1 -1
  13. package/dist/runner.js +1 -1
  14. package/dist/runner.js.map +1 -1
  15. package/dist/shared/zip-extract.js +1 -1
  16. package/dist/shared/zip-extract.js.map +1 -1
  17. package/package.json +4 -3
  18. package/src/activities/call-agent.ts +14 -4
  19. package/src/activities/discover-mcp-server.ts +1 -1
  20. package/src/activities/execute-cursor/__tests__/skill-resolver.test.ts +1 -1
  21. package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +3 -2
  22. package/src/activities/execute-deep-agent/attachment-injector.ts +2 -2
  23. package/src/client/stigmer-client.ts +7 -2
  24. package/src/runner-manager.ts +1 -1
  25. package/src/runner.ts +1 -1
  26. package/src/shared/__tests__/skill-mount.test.ts +1 -1
  27. package/src/shared/__tests__/skill-writer.test.ts +1 -1
  28. package/src/shared/__tests__/zip-extract.test.ts +1 -1
  29. package/src/shared/zip-extract.ts +1 -1
  30. package/dist/shared/zip-structure.d.ts +0 -61
  31. package/dist/shared/zip-structure.js +0 -128
  32. package/dist/shared/zip-structure.js.map +0 -1
  33. package/src/__test-utils__/zip-fixtures.ts +0 -206
  34. package/src/shared/zip-structure.ts +0 -181
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * Security model: attachments are untrusted user uploads. ZIP archives are
9
9
  * parsed from the central directory — the format's authoritative index —
10
- * via the shared structural layer (shared/zip-structure.ts; issue #567,
10
+ * via the shared structural layer (@stigmer/zip-structure; issue #567,
11
11
  * which killed this module's local-header walk: it silently truncated
12
12
  * stored streaming entries and rejected Go-default archives outright).
13
13
  * Every entry is validated for path traversal, zip bombs, and format
@@ -48,7 +48,7 @@ import {
48
48
  EOCD_MIN_SIZE,
49
49
  parseZipStructure,
50
50
  type ZipStructuralEntry,
51
- } from "../../shared/zip-structure.js";
51
+ } from "@stigmer/zip-structure";
52
52
 
53
53
  // ── Constants ────────────────────────────────────────────────────────
54
54
 
@@ -234,7 +234,10 @@ export class StigmerClient {
234
234
  // scoped-token exchange itself requires the embedded_runner
235
235
  // bootstrap credential (a desktop runner's control-plane token is
236
236
  // the user's own Auth0 token, which the server correctly treats as
237
- // a browsing user).
237
+ // a browsing user); and the workflow child-execution create stamps
238
+ // the platform's workflow lineage labels, which cloud's
239
+ // reserved-label guard and environment composer accept only from
240
+ // runner-class callers — a user-token create is rejected outright.
238
241
  //
239
242
  // 3. Everything else uses the control-plane token. Falls through
240
243
  // unchanged when no runner token exists (OSS/local, where the
@@ -246,7 +249,9 @@ export class StigmerClient {
246
249
  const usesRunnerCredential =
247
250
  req.service.typeName === ExecutionContextQueryController.typeName ||
248
251
  (req.service.typeName === PlatformQueryController.typeName &&
249
- req.method.name === PlatformQueryController.method.getRunnerScopedToken.name);
252
+ req.method.name === PlatformQueryController.method.getRunnerScopedToken.name) ||
253
+ (req.service.typeName === AgentExecutionCommandController.typeName &&
254
+ req.method.name === AgentExecutionCommandController.method.create.name);
250
255
  const token =
251
256
  (usesRunnerCredential ? this.runnerTokenRef?.current : null)
252
257
  ?? this.tokenRef?.current
@@ -789,7 +789,7 @@ async function createAllActivities(config: Config): Promise<WorkerActivities> {
789
789
  ...createCallGrpcActivities(),
790
790
  ...createCallFunctionActivities(),
791
791
  ...createCallLlmActivities(),
792
- ...createCallAgentActivities(),
792
+ ...createCallAgentActivities(config),
793
793
  ...createCallAgentStatusActivities(),
794
794
  ...createRunCommandActivities(),
795
795
  ...createHydrateWorkflowActivities(config),
package/src/runner.ts CHANGED
@@ -524,7 +524,7 @@ async function createAllActivities(config: Config): Promise<WorkerActivities> {
524
524
  ...createCallGrpcActivities(),
525
525
  ...createCallFunctionActivities(),
526
526
  ...createCallLlmActivities(),
527
- ...createCallAgentActivities(),
527
+ ...createCallAgentActivities(config),
528
528
  ...createCallAgentStatusActivities(),
529
529
  ...createRunCommandActivities(),
530
530
  ...createHydrateWorkflowActivities(config),
@@ -9,7 +9,7 @@ import {
9
9
  downloadArtifact,
10
10
  writeSkillMount,
11
11
  } from "../skill-mount.js";
12
- import { buildZip } from "../../__test-utils__/zip-fixtures.js";
12
+ import { buildZip } from "@stigmer/zip-structure/testing";
13
13
 
14
14
  // ─── Helpers ─────────────────────────────────────────────────────────────
15
15
 
@@ -12,7 +12,7 @@ import {
12
12
  } from "../skill-writer.js";
13
13
  import type { Skill } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb";
14
14
  import type { ApiResourceReference } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
15
- import { buildZip } from "../../__test-utils__/zip-fixtures.js";
15
+ import { buildZip } from "@stigmer/zip-structure/testing";
16
16
 
17
17
  // ─── Helpers ─────────────────────────────────────────────────────────────
18
18
 
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from "vitest";
2
2
  import { extractZipFileEntries, type ZipFileEntry } from "../zip-extract.js";
3
- import { buildZip } from "../../__test-utils__/zip-fixtures.js";
3
+ import { buildZip } from "@stigmer/zip-structure/testing";
4
4
 
5
5
  /**
6
6
  * Decode entries for text-content assertions. The production contract is
@@ -33,7 +33,7 @@
33
33
  */
34
34
 
35
35
  import { createInflateRaw } from "node:zlib";
36
- import { EOCD_MIN_SIZE, parseZipStructure, type ZipStructuralEntry } from "./zip-structure.js";
36
+ import { EOCD_MIN_SIZE, parseZipStructure, type ZipStructuralEntry } from "@stigmer/zip-structure";
37
37
 
38
38
  // ─── Public API ──────────────────────────────────────────────────────────
39
39
 
@@ -1,61 +0,0 @@
1
- /**
2
- * Policy-free structural ZIP parsing: end-of-central-directory (EOCD)
3
- * location plus central-directory walk, producing one record per entry.
4
- *
5
- * Parsing is *central-directory-based* — never a front-to-back walk of
6
- * local file headers. This is a correctness decision, not a style choice
7
- * (issues #450 and #567): local headers of streaming entries
8
- * (general-purpose flag bit 3) carry zeroed sizes, and the only
9
- * local-only way to recover them is scanning the payload for the
10
- * data-descriptor signature — which silently truncates any stored entry
11
- * whose *content* happens to contain those four bytes, and
12
- * desynchronizes every entry after it. The central directory always
13
- * carries the real sizes, and every consumer here holds the complete
14
- * archive bytes, so nothing a local-header walk could offer is needed.
15
- *
16
- * This layer maps bytes to entry records and nothing else. Policy — what
17
- * a structural failure means, which entries are acceptable, how payloads
18
- * are decoded — belongs to the consumers, and they differ on purpose:
19
- *
20
- * - shared/zip-extract.ts (skill artifacts): structural failure is
21
- * NON-FATAL — both editions' push gates validated every artifact
22
- * with a central-directory-based reader before storage, so a defect
23
- * here can only be a truncated or corrupted download.
24
- * - execute-deep-agent/attachment-injector.ts (user attachments):
25
- * structural failure is FAIL-HARD — the input is an untrusted
26
- * upload and nothing upstream vouched for it.
27
- *
28
- * ZIP64 is deliberately unsupported: both consumers cap input far below
29
- * every ZIP64 threshold (skill push gates: 100MB / 10,000 files;
30
- * attachment uploads: 10MB).
31
- *
32
- * Throws plain `Error`s on structural defects (no valid EOCD, a central
33
- * directory or local header record that does not parse, a payload slice
34
- * that runs past the end of the buffer); consumers translate those into
35
- * their own error models.
36
- */
37
- export interface ZipStructuralEntry {
38
- /** Entry path exactly as recorded in the central directory. */
39
- readonly name: string;
40
- readonly isDirectory: boolean;
41
- readonly compressionMethod: number;
42
- /**
43
- * The central directory's declared uncompressed size — authoritative for
44
- * pre-extraction accounting (e.g. ZIP-bomb budgeting), but still a
45
- * *declaration*: a consumer that distrusts its input must enforce it
46
- * against the actual decompressed output.
47
- */
48
- readonly uncompressedSize: number;
49
- /** The entry's raw payload slice (a view, not a copy) of the archive buffer. */
50
- readonly compressedData: Uint8Array;
51
- }
52
- /** Fixed size of the EOCD record, excluding the variable-length comment. */
53
- export declare const EOCD_MIN_SIZE = 22;
54
- /**
55
- * Enumerate the archive's entries from its central directory.
56
- *
57
- * Throws on any structural defect: no valid EOCD, a central directory or
58
- * local header record that doesn't parse, or a payload slice that runs
59
- * past the end of the buffer.
60
- */
61
- export declare function parseZipStructure(data: Uint8Array): ZipStructuralEntry[];
@@ -1,128 +0,0 @@
1
- /**
2
- * Policy-free structural ZIP parsing: end-of-central-directory (EOCD)
3
- * location plus central-directory walk, producing one record per entry.
4
- *
5
- * Parsing is *central-directory-based* — never a front-to-back walk of
6
- * local file headers. This is a correctness decision, not a style choice
7
- * (issues #450 and #567): local headers of streaming entries
8
- * (general-purpose flag bit 3) carry zeroed sizes, and the only
9
- * local-only way to recover them is scanning the payload for the
10
- * data-descriptor signature — which silently truncates any stored entry
11
- * whose *content* happens to contain those four bytes, and
12
- * desynchronizes every entry after it. The central directory always
13
- * carries the real sizes, and every consumer here holds the complete
14
- * archive bytes, so nothing a local-header walk could offer is needed.
15
- *
16
- * This layer maps bytes to entry records and nothing else. Policy — what
17
- * a structural failure means, which entries are acceptable, how payloads
18
- * are decoded — belongs to the consumers, and they differ on purpose:
19
- *
20
- * - shared/zip-extract.ts (skill artifacts): structural failure is
21
- * NON-FATAL — both editions' push gates validated every artifact
22
- * with a central-directory-based reader before storage, so a defect
23
- * here can only be a truncated or corrupted download.
24
- * - execute-deep-agent/attachment-injector.ts (user attachments):
25
- * structural failure is FAIL-HARD — the input is an untrusted
26
- * upload and nothing upstream vouched for it.
27
- *
28
- * ZIP64 is deliberately unsupported: both consumers cap input far below
29
- * every ZIP64 threshold (skill push gates: 100MB / 10,000 files;
30
- * attachment uploads: 10MB).
31
- *
32
- * Throws plain `Error`s on structural defects (no valid EOCD, a central
33
- * directory or local header record that does not parse, a payload slice
34
- * that runs past the end of the buffer); consumers translate those into
35
- * their own error models.
36
- */
37
- // ─── Format constants ────────────────────────────────────────────────────
38
- const LOCAL_FILE_HEADER_SIG = 0x04034b50;
39
- const CENTRAL_DIRECTORY_SIG = 0x02014b50;
40
- const EOCD_SIG = 0x06054b50;
41
- /** Fixed size of the EOCD record, excluding the variable-length comment. */
42
- export const EOCD_MIN_SIZE = 22;
43
- /** The archive comment length is a u16, so the EOCD sits within the last 64KB + 22 bytes. */
44
- const EOCD_MAX_COMMENT = 0xffff;
45
- const LOCAL_HEADER_SIZE = 30;
46
- const CD_RECORD_SIZE = 46;
47
- // ─── Parsing ─────────────────────────────────────────────────────────────
48
- /**
49
- * Enumerate the archive's entries from its central directory.
50
- *
51
- * Throws on any structural defect: no valid EOCD, a central directory or
52
- * local header record that doesn't parse, or a payload slice that runs
53
- * past the end of the buffer.
54
- */
55
- export function parseZipStructure(data) {
56
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
57
- const eocd = findEndOfCentralDirectory(data, view);
58
- const entries = [];
59
- let offset = eocd.centralDirectoryOffset;
60
- for (let i = 0; i < eocd.entryCount; i++) {
61
- if (offset + CD_RECORD_SIZE > data.length || view.getUint32(offset, true) !== CENTRAL_DIRECTORY_SIG) {
62
- throw new Error(`invalid central directory record at offset ${offset}`);
63
- }
64
- const compressionMethod = view.getUint16(offset + 10, true);
65
- const compressedSize = view.getUint32(offset + 20, true);
66
- const uncompressedSize = view.getUint32(offset + 24, true);
67
- const fileNameLength = view.getUint16(offset + 28, true);
68
- const extraFieldLength = view.getUint16(offset + 30, true);
69
- const commentLength = view.getUint16(offset + 32, true);
70
- const localHeaderOffset = view.getUint32(offset + 42, true);
71
- const fileName = new TextDecoder().decode(data.subarray(offset + CD_RECORD_SIZE, offset + CD_RECORD_SIZE + fileNameLength));
72
- // The local header is consulted for one thing only: the payload start.
73
- // Its name/extra field lengths can legitimately differ from the central
74
- // directory's (streaming writers pad the extra field), so the offset
75
- // cannot be derived from CD fields alone.
76
- const dataStart = payloadStart(data, view, localHeaderOffset, fileName);
77
- if (dataStart + compressedSize > data.length) {
78
- throw new Error(`entry "${fileName}" payload runs past end of archive`);
79
- }
80
- entries.push({
81
- name: fileName,
82
- isDirectory: fileName.endsWith("/"),
83
- compressionMethod,
84
- uncompressedSize,
85
- compressedData: data.subarray(dataStart, dataStart + compressedSize),
86
- });
87
- offset += CD_RECORD_SIZE + fileNameLength + extraFieldLength + commentLength;
88
- }
89
- return entries;
90
- }
91
- /**
92
- * Locate and validate the end-of-central-directory record.
93
- *
94
- * Scans backward from the end of the archive across the maximum comment
95
- * span. A signature match alone is not trusted (the four bytes can occur
96
- * inside a trailing comment): the record must also point at an offset
97
- * that actually holds a central directory record, or be a genuinely
98
- * empty archive.
99
- */
100
- function findEndOfCentralDirectory(data, view) {
101
- const scanFloor = Math.max(0, data.length - EOCD_MIN_SIZE - EOCD_MAX_COMMENT);
102
- for (let pos = data.length - EOCD_MIN_SIZE; pos >= scanFloor; pos--) {
103
- if (view.getUint32(pos, true) !== EOCD_SIG)
104
- continue;
105
- const entryCount = view.getUint16(pos + 10, true);
106
- const centralDirectorySize = view.getUint32(pos + 12, true);
107
- const centralDirectoryOffset = view.getUint32(pos + 16, true);
108
- const directoryEndsAtRecord = centralDirectoryOffset + centralDirectorySize <= pos;
109
- const directoryLooksReal = entryCount === 0 ||
110
- (centralDirectoryOffset + 4 <= data.length &&
111
- view.getUint32(centralDirectoryOffset, true) === CENTRAL_DIRECTORY_SIG);
112
- if (directoryEndsAtRecord && directoryLooksReal) {
113
- return { centralDirectoryOffset, entryCount };
114
- }
115
- }
116
- throw new Error("no end-of-central-directory record found");
117
- }
118
- /** Resolve where an entry's payload begins, from its local file header. */
119
- function payloadStart(data, view, localHeaderOffset, fileName) {
120
- if (localHeaderOffset + LOCAL_HEADER_SIZE > data.length ||
121
- view.getUint32(localHeaderOffset, true) !== LOCAL_FILE_HEADER_SIG) {
122
- throw new Error(`entry "${fileName}" has no local file header at offset ${localHeaderOffset}`);
123
- }
124
- const localNameLength = view.getUint16(localHeaderOffset + 26, true);
125
- const localExtraLength = view.getUint16(localHeaderOffset + 28, true);
126
- return localHeaderOffset + LOCAL_HEADER_SIZE + localNameLength + localExtraLength;
127
- }
128
- //# sourceMappingURL=zip-structure.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"zip-structure.js","sourceRoot":"","sources":["../../src/shared/zip-structure.ts"],"names":[],"mappings":"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"}
@@ -1,206 +0,0 @@
1
- /**
2
- * Shared ZIP fixture builder for runner unit tests.
3
- *
4
- * Emits complete, real-shaped archives — local file headers (optionally
5
- * streaming-style, the Go stdlib writer's default), payloads, central
6
- * directory, and EOCD — because that is the only shape that can reach the
7
- * runner: both editions' skill push gates validate artifacts with
8
- * central-directory-based readers (see zip-extract.ts's module doc and
9
- * design record 017). Earlier fixtures emitted local headers only, a
10
- * shape no real ZIP writer produces, and were coupled to the old parser's
11
- * front-to-back walk.
12
- *
13
- * Mirrors the conformance suite's `zipFilesStreaming()`
14
- * (test/conformance/src/support/skills.ts) so unit fixtures and
15
- * cross-edition fixtures share one shape.
16
- *
17
- * Lives in src/__test-utils__/ so `tsc --noEmit` covers it while
18
- * tsconfig.build.json keeps it out of dist/.
19
- */
20
-
21
- import { deflateRawSync } from "node:zlib";
22
-
23
- export interface ZipFixtureFile {
24
- name: string;
25
- /** Text content is UTF-8 encoded; pass bytes directly for binary payloads. */
26
- content: string | Uint8Array;
27
- /** Compression method for the entry. Defaults to stored. */
28
- method?: "stored" | "deflated";
29
- /**
30
- * Emit the entry the way Go's archive/zip does by default: general-purpose
31
- * flag bit 3 set, zeroed sizes in the local header, and a trailing data
32
- * descriptor (with the conventional signature) carrying the real values.
33
- */
34
- streaming?: boolean;
35
- /**
36
- * Lie about the entry's uncompressed size everywhere the writer would
37
- * declare it (local header, data descriptor, central directory). Models a
38
- * crafted archive whose declarations disagree with its actual payload —
39
- * the input class the attachment injector's declared-size enforcement
40
- * exists to reject (issue #567).
41
- */
42
- declaredUncompressedSize?: number;
43
- }
44
-
45
- export interface ZipFixtureOptions {
46
- /** Trailing archive comment appended after the EOCD record. */
47
- comment?: string;
48
- /**
49
- * Drop the central directory and EOCD, leaving only local headers and
50
- * payloads. No real ZIP writer produces this shape — it models a download
51
- * truncated before the archive's index.
52
- */
53
- omitCentralDirectory?: boolean;
54
- }
55
-
56
- /** Build a ZIP archive from path + content pairs. */
57
- export function buildZip(files: ZipFixtureFile[], options?: ZipFixtureOptions): Uint8Array {
58
- const out = new ByteWriter();
59
-
60
- interface WrittenEntry {
61
- nameBytes: Uint8Array;
62
- payload: Uint8Array;
63
- declaredUncompressed: number;
64
- crc: number;
65
- flags: number;
66
- method: number;
67
- localHeaderOffset: number;
68
- }
69
- const written: WrittenEntry[] = [];
70
-
71
- for (const file of files) {
72
- const contentBytes =
73
- typeof file.content === "string" ? new TextEncoder().encode(file.content) : file.content;
74
- const deflated = file.method === "deflated";
75
- const payload = deflated ? new Uint8Array(deflateRawSync(contentBytes)) : contentBytes;
76
- const entry: WrittenEntry = {
77
- nameBytes: new TextEncoder().encode(file.name),
78
- payload,
79
- declaredUncompressed: file.declaredUncompressedSize ?? contentBytes.length,
80
- crc: crc32(contentBytes),
81
- flags: file.streaming ? 0x0008 : 0,
82
- method: deflated ? 8 : 0,
83
- localHeaderOffset: out.length,
84
- };
85
- written.push(entry);
86
-
87
- out.u32(0x04034b50); // local file header signature
88
- out.u16(20); // version needed to extract
89
- out.u16(entry.flags);
90
- out.u16(entry.method);
91
- out.u16(0); // mod time
92
- out.u16(0); // mod date
93
- out.u32(file.streaming ? 0 : entry.crc);
94
- out.u32(file.streaming ? 0 : payload.length);
95
- out.u32(file.streaming ? 0 : entry.declaredUncompressed);
96
- out.u16(entry.nameBytes.length);
97
- out.u16(0); // extra field length
98
- out.raw(entry.nameBytes);
99
- out.raw(payload);
100
-
101
- if (file.streaming) {
102
- out.u32(0x08074b50); // data descriptor signature (Go writes it)
103
- out.u32(entry.crc);
104
- out.u32(payload.length);
105
- out.u32(entry.declaredUncompressed);
106
- }
107
- }
108
-
109
- if (options?.omitCentralDirectory) {
110
- return out.toUint8Array();
111
- }
112
-
113
- const centralDirectoryOffset = out.length;
114
- for (const entry of written) {
115
- out.u32(0x02014b50); // central directory record signature
116
- out.u16(20); // version made by
117
- out.u16(20); // version needed to extract
118
- out.u16(entry.flags);
119
- out.u16(entry.method);
120
- out.u16(0); // mod time
121
- out.u16(0); // mod date
122
- out.u32(entry.crc);
123
- out.u32(entry.payload.length);
124
- out.u32(entry.declaredUncompressed);
125
- out.u16(entry.nameBytes.length);
126
- out.u16(0); // extra field length
127
- out.u16(0); // comment length
128
- out.u16(0); // disk number start
129
- out.u16(0); // internal attributes
130
- out.u32(0); // external attributes
131
- out.u32(entry.localHeaderOffset);
132
- out.raw(entry.nameBytes);
133
- }
134
- const centralDirectorySize = out.length - centralDirectoryOffset;
135
-
136
- const commentBytes = new TextEncoder().encode(options?.comment ?? "");
137
- out.u32(0x06054b50); // EOCD signature
138
- out.u16(0); // disk number
139
- out.u16(0); // disk with central directory
140
- out.u16(written.length); // entries on this disk
141
- out.u16(written.length); // total entries
142
- out.u32(centralDirectorySize);
143
- out.u32(centralDirectoryOffset);
144
- out.u16(commentBytes.length);
145
- out.raw(commentBytes);
146
-
147
- return out.toUint8Array();
148
- }
149
-
150
- // Chunked assembly instead of a number[]-per-byte accumulator: fixtures at
151
- // the injector's 100 MB zip-bomb limit are built from payload-sized chunks,
152
- // which a per-byte spread-push cannot survive (argument-count overflow).
153
- class ByteWriter {
154
- private readonly chunks: Uint8Array[] = [];
155
- private size = 0;
156
-
157
- get length(): number {
158
- return this.size;
159
- }
160
-
161
- u16(v: number): void {
162
- this.raw(new Uint8Array([v & 0xff, (v >>> 8) & 0xff]));
163
- }
164
-
165
- u32(v: number): void {
166
- this.raw(new Uint8Array([v & 0xff, (v >>> 8) & 0xff, (v >>> 16) & 0xff, (v >>> 24) & 0xff]));
167
- }
168
-
169
- raw(b: Uint8Array): void {
170
- this.chunks.push(b);
171
- this.size += b.length;
172
- }
173
-
174
- toUint8Array(): Uint8Array {
175
- const result = new Uint8Array(this.size);
176
- let offset = 0;
177
- for (const chunk of this.chunks) {
178
- result.set(chunk, offset);
179
- offset += chunk.length;
180
- }
181
- return result;
182
- }
183
- }
184
-
185
- // Standard CRC-32 (IEEE 802.3, the ZIP checksum), table-driven so fixtures
186
- // at the injector's 100 MB limit stay cheap to build. Semantically identical
187
- // to the conformance suite's inline bit-loop helper.
188
- const CRC_TABLE = (() => {
189
- const table = new Uint32Array(256);
190
- for (let n = 0; n < 256; n++) {
191
- let c = n;
192
- for (let bit = 0; bit < 8; bit++) {
193
- c = (c >>> 1) ^ (0xedb88320 & -(c & 1));
194
- }
195
- table[n] = c;
196
- }
197
- return table;
198
- })();
199
-
200
- function crc32(data: Uint8Array): number {
201
- let crc = 0xffffffff;
202
- for (let i = 0; i < data.length; i++) {
203
- crc = (crc >>> 8) ^ CRC_TABLE[(crc ^ data[i]!) & 0xff]!;
204
- }
205
- return (crc ^ 0xffffffff) >>> 0;
206
- }