first commit
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
# `commands/files/files`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Local-packer file-transport commands: `prepareFileCommand`,
|
||||
`validateFileCommand`, `getFileStatsCommand`, `validatePacketCommand`.
|
||||
These four functions implement the operator-facing surface every file-
|
||||
transport bin in `@suaveplan/server-keys` calls into. Reconstruction
|
||||
lives in the sibling `serve-file.ts`. The implementation is fully local —
|
||||
no HTTP, no remote file server — backed by `@suaveplan/packer`'s
|
||||
whole-file `Packer` / `Unpacker` and plain Node `fs`.
|
||||
|
||||
The choice of a local packer (rather than an HTTP client to a remote
|
||||
file-server, which the upstream package shipped) was made deliberately:
|
||||
the upstream HTTP design forces operators to stand up a separate
|
||||
file-server before they can prepare or serve a file, and the binary
|
||||
contract is dominated by the packet wire format anyway. Owning the
|
||||
format here keeps the suaveplan test surface auditable without a network
|
||||
hop and removes a non-trivial deployment dependency.
|
||||
|
||||
## Features
|
||||
|
||||
- Fixed-size packetisation (`packetSize`, default 1 MiB) with a JSON
|
||||
envelope per packet on disk.
|
||||
- ed25519 signature per packet using the vault's `sign` slot secret;
|
||||
recipient verifies against the manifest's `signerPublicKey`.
|
||||
- Manifest is a single JSON document that lists every packet's
|
||||
`index`, `checksum`, `filename`, and `originalSize`.
|
||||
- Cooperative abort at every packet boundary via `options.signal`.
|
||||
- Typed `ServerKeysError` for every failure (`INVALID_INPUT`,
|
||||
`VAULT_DECRYPT_FAILED`, `PACKET_INTEGRITY_FAILED`, `FILE_NOT_FOUND`,
|
||||
`ABORTED`).
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
prepareFileCommand,
|
||||
validateFileCommand,
|
||||
getFileStatsCommand,
|
||||
} from "@suaveplan/server-keys/commands/files/files";
|
||||
|
||||
const stats = await getFileStatsCommand("/tmp/data.bin", {
|
||||
packetSize: 256 * 1024,
|
||||
});
|
||||
console.log(stats.estimatedPackets);
|
||||
|
||||
await prepareFileCommand(
|
||||
"/tmp/data.bin",
|
||||
"/tmp/keys/server.vault",
|
||||
"serverpw1234",
|
||||
{ output: "/tmp/prepared", packetSize: 256 * 1024 },
|
||||
);
|
||||
|
||||
const result = await validateFileCommand("/tmp/prepared");
|
||||
expect(result.isComplete).toBe(true);
|
||||
expect(result.invalidPackets).toEqual([]);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Single-packet validation against an explicit hex public key (useful for
|
||||
spot-checking a remote-supplied packet without trusting the colocated
|
||||
manifest):
|
||||
|
||||
```ts
|
||||
import { validatePacketCommand } from "@suaveplan/server-keys/commands/files/files";
|
||||
|
||||
const r = await validatePacketCommand(
|
||||
"/tmp/prepared/packet.000000.bin",
|
||||
"abcd...ed25519-public-key-hex",
|
||||
);
|
||||
expect(r.isValid).toBe(true);
|
||||
```
|
||||
|
||||
Detect a tampered packet:
|
||||
|
||||
```ts
|
||||
import { promises as fs } from "node:fs";
|
||||
const target = "/tmp/prepared/packet.000003.bin";
|
||||
const original = await fs.readFile(target, "utf-8");
|
||||
const obj = JSON.parse(original);
|
||||
const bytes = Buffer.from(obj.data, "hex");
|
||||
bytes[10] ^= 0xff;
|
||||
obj.data = bytes.toString("hex");
|
||||
await fs.writeFile(target, JSON.stringify(obj));
|
||||
const r = await validateFileCommand("/tmp/prepared");
|
||||
expect(r.isComplete).toBe(false);
|
||||
expect(r.invalidPackets).toContain(3);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `prepareFileCommand(filePath, vaultPath, password, options?) =>
|
||||
Promise<FileManifest>`
|
||||
- `validateFileCommand(packetDir, options?) =>
|
||||
Promise<ValidateFileResult>`
|
||||
- `getFileStatsCommand(filePath, options?) => Promise<FileStatsResult>`
|
||||
- `validatePacketCommand(packetPath, verifyKeyHex, options?) =>
|
||||
Promise<ValidatePacketResult>`
|
||||
|
||||
All take `FileCommandOptions = { output?, packetSize?, logger?,
|
||||
signal? }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`prepareFileCommand` constructs a new `Packer` per packet so the
|
||||
underlying event bus stays scoped to a single pack call — the abstract
|
||||
`Packer` is an `EventEmitter` and we don't want pack events leaking
|
||||
across packets. Each packet stores its `signature`, `data`, `checksum`,
|
||||
and `originalSize` in a small JSON envelope (`{ data, signature,
|
||||
checksum, originalSize }`), all hex-encoded — operators inspecting
|
||||
packets by hand can paste them into a hex viewer without further
|
||||
tooling. The intentional `compressionLevel: 0` (no gzip) keeps each
|
||||
packet's plaintext slice byte-identical to the input region; without
|
||||
this, signing operates over the gzip stream and reconstruction
|
||||
requires inverting it, which is an avoidable layer for the local
|
||||
transport.
|
||||
Reference in New Issue
Block a user