Files
suaveplan/packages/security-stack/security/server-keys/src/commands/files/packet-io.md
2026-05-27 23:47:53 +01:00

3.3 KiB

commands/files/packet-io

Purpose

Shared on-disk format helpers for the file-transport commands. Holds the canonical packet envelope { data, signature, checksum, originalSize } (all hex-encoded scalars in JSON), the canonical packet filename convention (packet.<6-digit-index>.bin), the manifest filename (manifest.json), and the read/write helpers used by both prepareFileCommand and serveFileCommand.

Lives in its own module so the format is the contract: any tool that agrees on the envelope shape can interop with a server-keys-prepared directory without depending on the full file-command surface. Also keeps files.ts and serve-file.ts under the 300-line hard limit by lifting out the duplicated serialisation code.

Features

  • packetFilename(index) — zero-padded 6-digit filename for deterministic sort order.
  • bytesToHex / hexToBytes — lowercase hex, strict input validation.
  • serializePacket(packed, originalSize) — JSON-encode a PackedData envelope.
  • deserializePacket(buffer) — parse a JSON envelope back into the shape Unpacker.unpack expects, with a typed PACKET_INTEGRITY_FAILED failure on malformed JSON.
  • readManifest(dir) / writeManifest(dir, manifest) — single-file JSON manifest I/O with directory creation.

Usage: Basic

import {
    packetFilename,
    serializePacket,
    writeManifest,
} from "@suaveplan/server-keys/commands/files/packet-io";

const filename = packetFilename(7); // "packet.000007.bin"
const text = serializePacket(packed, 1024);
await fs.writeFile(`/tmp/prepared/${filename}`, text);
await writeManifest("/tmp/prepared", manifest);

Usage: Advanced

Round-trip a packet envelope without touching disk:

import {
    deserializePacket,
    serializePacket,
} from "@suaveplan/server-keys/commands/files/packet-io";

const text = serializePacket(packed, slice.byteLength);
const buf = new TextEncoder().encode(text);
const { packed: rehydrated, envelope } = deserializePacket(buf);
expect(envelope.checksum).toBe(packed.metadata.checksum);
expect(Array.from(rehydrated.data)).toEqual(Array.from(packed.data));

API Reference

  • MANIFEST_FILENAME = "manifest.json"
  • packetFilename(index: number) => string
  • bytesToHex(bytes: Uint8Array) => string
  • hexToBytes(hex: string) => Uint8Array (throws on malformed input)
  • interface SerializedPacket { data, signature, checksum, originalSize }
  • serializePacket(packed, originalSize) => string
  • deserializePacket(buffer) => { packed, envelope }
  • writeManifest(dir, manifest) => Promise<void>
  • readManifest(dir) => Promise<FileManifest>

Implementation Notes

readManifest translates a missing file into SERVER_KEYS_FILE_NOT_FOUND; this is the closest match in the registry for "the directory exists but the manifest entry is absent" and aligns with how the keys commands report missing key files. deserializePacket raises PACKET_INTEGRITY_FAILED on malformed JSON rather than INVALID_INPUT because the input is a file the caller already trusted enough to read — the failure is on the storage side, not on a CLI option-bag boundary. The packet envelope deliberately hex-encodes both data and signature (rather than base64) to match the rest of the package's binary-on-disk convention and to keep the file readable by hand.