Skip to content

Commit fe44548

Browse files
committed
chore(gates): export host-kit's archive fixtures for cross-package tests
Move the archive fixture module from src/internal/ to src/archive.fixtures.ts and export it as ./archive-fixtures (audio-probe-fixtures precedent) so platform-apple's IPA fixture can write real stored-zip archives through host-kit's writeZipFixture instead of the host zip binary. Replace the host-zip encrypted-entry helper with a fixture-written encrypted flag, and record the subpath in the R11 export-surface list.
1 parent 838865c commit fe44548

6 files changed

Lines changed: 38 additions & 22 deletions

File tree

‎packages/host-kit/package.json‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
"types": "./src/archive.ts",
2121
"default": "./src/archive.ts"
2222
},
23+
"./archive-fixtures": {
24+
"types": "./src/archive.fixtures.ts",
25+
"default": "./src/archive.fixtures.ts"
26+
},
2327
"./audio-probe-fixtures": {
2428
"types": "./src/audio-probe.fixtures.ts",
2529
"default": "./src/audio-probe.fixtures.ts"

packages/host-kit/src/internal/archive-extraction.fixtures.ts renamed to packages/host-kit/src/archive.fixtures.ts

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import { promises as fs } from 'node:fs';
33
import path from 'node:path';
44
import { crc32, gzipSync } from 'node:zlib';
55
import * as tar from 'tar-stream';
6-
import { runCmdSync } from './exec.ts';
7-
import { mkdtempForTest } from './tmp-dir.fixtures.ts';
6+
import { mkdtempForTest } from './internal/tmp-dir.fixtures.ts';
87

98
export async function createArchiveWorkspace(): Promise<{
109
archivePath: string;
@@ -27,21 +26,30 @@ export async function createTruncatedTgz(archivePath: string): Promise<void> {
2726
await fs.writeFile(archivePath, gzipSync(headerOnly));
2827
}
2928

29+
/** Two stored entries whose second carries the encrypted general-purpose flag bit. */
3030
export async function createZipWithEncryptedSecondEntry(archivePath: string): Promise<void> {
31-
const staging = path.join(path.dirname(archivePath), 'zip-input');
32-
await fs.mkdir(staging);
33-
await fs.writeFile(path.join(staging, 'first.txt'), 'first');
34-
await fs.writeFile(path.join(staging, 'second.txt'), 'second');
35-
runCmdSync('zip', ['-q', archivePath, 'first.txt'], { cwd: staging });
36-
runCmdSync('zip', ['-q', '-P', 'secret', archivePath, 'second.txt'], { cwd: staging });
31+
await writeZipFixture(archivePath, [
32+
{ name: 'first.txt', mode: ZIP_FILE_MODE, data: 'first' },
33+
{ name: 'second.txt', mode: ZIP_FILE_MODE, data: 'second', encrypted: true },
34+
]);
3735
}
3836

37+
const ZIP_FILE_MODE = 0o100644;
38+
3939
/** One stored zip entry; `mode` is the full Unix `st_mode`, file-type bits included. */
40-
export type ZipFixtureEntry = { name: string; mode: number; data?: string };
40+
export type ZipFixtureEntry = {
41+
name: string;
42+
mode: number;
43+
data?: string;
44+
/** Declares the entry encrypted, which no archive reader may then extract. */
45+
encrypted?: boolean;
46+
};
4147

4248
/**
4349
* Writes a stored (uncompressed) zip whose names and Unix attributes are exactly as given, which
44-
* `zip` itself refuses to produce for links, special files, and escaping names.
50+
* `zip` itself refuses to produce for links, special files, and escaping names. An entry declared
51+
* `encrypted` carries the traditional encryption flag with a padded 12-byte encryption header and
52+
* a zero CRC, the shape readers must refuse before extracting anything.
4553
*/
4654
export async function writeZipFixture(
4755
archivePath: string,
@@ -52,24 +60,29 @@ export async function writeZipFixture(
5260
let offset = 0;
5361
for (const entry of entries) {
5462
const name = Buffer.from(entry.name, 'utf8');
55-
const data = Buffer.from(entry.data ?? '', 'utf8');
56-
const checksum = crc32(data);
63+
const payload = Buffer.from(entry.data ?? '', 'utf8');
64+
// A traditionally encrypted stored entry carries a 12-byte encryption header before the
65+
// ciphertext, and readers validate that against the declared sizes, so the record declares
66+
// it and the data section pads it; the payload itself never decrypts to anything.
67+
const data = entry.encrypted ? Buffer.concat([Buffer.alloc(12), payload]) : payload;
68+
const checksum = entry.encrypted ? 0 : crc32(payload);
69+
const flags = 0x0800 | (entry.encrypted ? 0x0001 : 0);
5770
const local = Buffer.alloc(30);
5871
local.writeUInt32LE(0x04034b50, 0);
5972
local.writeUInt16LE(20, 4);
60-
local.writeUInt16LE(0x0800, 6);
73+
local.writeUInt16LE(flags, 6);
6174
local.writeUInt32LE(checksum, 14);
6275
local.writeUInt32LE(data.length, 18);
63-
local.writeUInt32LE(data.length, 22);
76+
local.writeUInt32LE(payload.length, 22);
6477
local.writeUInt16LE(name.length, 26);
6578
const central = Buffer.alloc(46);
6679
central.writeUInt32LE(0x02014b50, 0);
6780
central.writeUInt16LE((3 << 8) | 20, 4);
6881
central.writeUInt16LE(20, 6);
69-
central.writeUInt16LE(0x0800, 8);
82+
central.writeUInt16LE(flags, 8);
7083
central.writeUInt32LE(checksum, 16);
7184
central.writeUInt32LE(data.length, 20);
72-
central.writeUInt32LE(data.length, 24);
85+
central.writeUInt32LE(payload.length, 24);
7386
central.writeUInt16LE(name.length, 28);
7487
central.writeUInt32LE((entry.mode << 16) >>> 0, 38);
7588
central.writeUInt32LE(offset, 42);

‎packages/host-kit/src/internal/archive-extraction-tar.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { test } from 'vitest';
44
import { AppError } from '@agent-device/kernel/errors';
55
import { extractTarArchive } from './archive-extraction-tar.ts';
66
import { ArchiveBudget } from './archive-safety.ts';
7-
import { createArchiveWorkspace, createTruncatedTgz } from './archive-extraction.fixtures.ts';
7+
import { createArchiveWorkspace, createTruncatedTgz } from '../archive.fixtures.ts';
88

99
test('tar inspection rejects declared bytes before draining an over-budget entry', async () => {
1010
const workspace = await createArchiveWorkspace();

‎packages/host-kit/src/internal/archive-extraction-zip.test.ts‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@ import { test } from 'vitest';
44
import { AppError } from '@agent-device/kernel/errors';
55
import { extractZipArchive } from './archive-extraction-zip.ts';
66
import { ArchiveBudget } from './archive-safety.ts';
7-
import {
8-
createArchiveWorkspace,
9-
createZipWithEncryptedSecondEntry,
10-
} from './archive-extraction.fixtures.ts';
7+
import { createArchiveWorkspace, createZipWithEncryptedSecondEntry } from '../archive.fixtures.ts';
118

129
test('zip inspection stops at the entry budget before inspecting later entries', async () => {
1310
const workspace = await createArchiveWorkspace();

‎packages/host-kit/src/internal/archive-extraction.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
writeZipFixture,
1010
type TarFixtureEntry,
1111
type ZipFixtureEntry,
12-
} from './archive-extraction.fixtures.ts';
12+
} from '../archive.fixtures.ts';
1313
import { mkdtempForTest } from './tmp-dir.fixtures.ts';
1414

1515
const S_IFREG = 0o100000;

‎scripts/layering/package-boundaries.test.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,8 @@ test('the real tree parses, declares, and passes R11', () => {
552552
);
553553
assert.deepEqual([...hostKitPackage.exportTargets.keys()].sort(), [
554554
'@agent-device/host-kit/archive',
555+
// Test-only entry: the zip/tar archive fixture writers shared with platform tests (#3461).
556+
'@agent-device/host-kit/archive-fixtures',
555557
// Test-only entry: the one inert audio-probe double the platform runtime fixtures share.
556558
'@agent-device/host-kit/audio-probe-fixtures',
557559
'@agent-device/host-kit/code-signature',

0 commit comments

Comments
 (0)