@tracehound/core 1.2.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.
- package/README.md +125 -0
- package/dist/core/agent.d.ts +89 -0
- package/dist/core/agent.d.ts.map +1 -0
- package/dist/core/agent.js +141 -0
- package/dist/core/agent.js.map +1 -0
- package/dist/core/audit-chain.d.ts +39 -0
- package/dist/core/audit-chain.d.ts.map +1 -0
- package/dist/core/audit-chain.js +87 -0
- package/dist/core/audit-chain.js.map +1 -0
- package/dist/core/cold-storage.d.ts +87 -0
- package/dist/core/cold-storage.d.ts.map +1 -0
- package/dist/core/cold-storage.js +53 -0
- package/dist/core/cold-storage.js.map +1 -0
- package/dist/core/evidence-factory.d.ts +85 -0
- package/dist/core/evidence-factory.d.ts.map +1 -0
- package/dist/core/evidence-factory.js +96 -0
- package/dist/core/evidence-factory.js.map +1 -0
- package/dist/core/evidence.d.ts +48 -0
- package/dist/core/evidence.d.ts.map +1 -0
- package/dist/core/evidence.js +135 -0
- package/dist/core/evidence.js.map +1 -0
- package/dist/core/fail-safe.d.ts +149 -0
- package/dist/core/fail-safe.d.ts.map +1 -0
- package/dist/core/fail-safe.js +217 -0
- package/dist/core/fail-safe.js.map +1 -0
- package/dist/core/hound-ipc.d.ts +91 -0
- package/dist/core/hound-ipc.d.ts.map +1 -0
- package/dist/core/hound-ipc.js +196 -0
- package/dist/core/hound-ipc.js.map +1 -0
- package/dist/core/hound-pool.d.ts +157 -0
- package/dist/core/hound-pool.d.ts.map +1 -0
- package/dist/core/hound-pool.js +337 -0
- package/dist/core/hound-pool.js.map +1 -0
- package/dist/core/hound-process.d.ts +14 -0
- package/dist/core/hound-process.d.ts.map +1 -0
- package/dist/core/hound-process.js +112 -0
- package/dist/core/hound-process.js.map +1 -0
- package/dist/core/hound-worker.d.ts +14 -0
- package/dist/core/hound-worker.d.ts.map +1 -0
- package/dist/core/hound-worker.js +112 -0
- package/dist/core/hound-worker.js.map +1 -0
- package/dist/core/lane-queue.d.ts +121 -0
- package/dist/core/lane-queue.d.ts.map +1 -0
- package/dist/core/lane-queue.js +181 -0
- package/dist/core/lane-queue.js.map +1 -0
- package/dist/core/license-manager.d.ts +128 -0
- package/dist/core/license-manager.d.ts.map +1 -0
- package/dist/core/license-manager.js +219 -0
- package/dist/core/license-manager.js.map +1 -0
- package/dist/core/notification-emitter.d.ts +140 -0
- package/dist/core/notification-emitter.d.ts.map +1 -0
- package/dist/core/notification-emitter.js +197 -0
- package/dist/core/notification-emitter.js.map +1 -0
- package/dist/core/process-adapter.d.ts +146 -0
- package/dist/core/process-adapter.d.ts.map +1 -0
- package/dist/core/process-adapter.js +174 -0
- package/dist/core/process-adapter.js.map +1 -0
- package/dist/core/quarantine.d.ts +95 -0
- package/dist/core/quarantine.d.ts.map +1 -0
- package/dist/core/quarantine.js +221 -0
- package/dist/core/quarantine.js.map +1 -0
- package/dist/core/rate-limiter.d.ts +94 -0
- package/dist/core/rate-limiter.d.ts.map +1 -0
- package/dist/core/rate-limiter.js +156 -0
- package/dist/core/rate-limiter.js.map +1 -0
- package/dist/core/s3-cold-storage.d.ts +116 -0
- package/dist/core/s3-cold-storage.d.ts.map +1 -0
- package/dist/core/s3-cold-storage.js +198 -0
- package/dist/core/s3-cold-storage.js.map +1 -0
- package/dist/core/scheduler.d.ts +126 -0
- package/dist/core/scheduler.d.ts.map +1 -0
- package/dist/core/scheduler.js +138 -0
- package/dist/core/scheduler.js.map +1 -0
- package/dist/core/security-state.d.ts +170 -0
- package/dist/core/security-state.d.ts.map +1 -0
- package/dist/core/security-state.js +156 -0
- package/dist/core/security-state.js.map +1 -0
- package/dist/core/tier-capacity.d.ts +58 -0
- package/dist/core/tier-capacity.d.ts.map +1 -0
- package/dist/core/tier-capacity.js +89 -0
- package/dist/core/tier-capacity.js.map +1 -0
- package/dist/core/tracehound.d.ts +85 -0
- package/dist/core/tracehound.d.ts.map +1 -0
- package/dist/core/tracehound.js +90 -0
- package/dist/core/tracehound.js.map +1 -0
- package/dist/core/trust-boundary.d.ts +85 -0
- package/dist/core/trust-boundary.d.ts.map +1 -0
- package/dist/core/trust-boundary.js +71 -0
- package/dist/core/trust-boundary.js.map +1 -0
- package/dist/core/watcher.d.ts +153 -0
- package/dist/core/watcher.d.ts.map +1 -0
- package/dist/core/watcher.js +141 -0
- package/dist/core/watcher.js.map +1 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +112 -0
- package/dist/index.js.map +1 -0
- package/dist/types/audit.d.ts +45 -0
- package/dist/types/audit.d.ts.map +1 -0
- package/dist/types/audit.js +5 -0
- package/dist/types/audit.js.map +1 -0
- package/dist/types/common.d.ts +12 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +5 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/config.d.ts +98 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +58 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/errors.d.ts +118 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +266 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/evidence.d.ts +102 -0
- package/dist/types/evidence.d.ts.map +1 -0
- package/dist/types/evidence.js +5 -0
- package/dist/types/evidence.js.map +1 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +9 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/result.d.ts +62 -0
- package/dist/types/result.d.ts.map +1 -0
- package/dist/types/result.js +34 -0
- package/dist/types/result.js.map +1 -0
- package/dist/types/scent.d.ts +55 -0
- package/dist/types/scent.d.ts.map +1 -0
- package/dist/types/scent.js +5 -0
- package/dist/types/scent.js.map +1 -0
- package/dist/types/signature.d.ts +47 -0
- package/dist/types/signature.d.ts.map +1 -0
- package/dist/types/signature.js +68 -0
- package/dist/types/signature.js.map +1 -0
- package/dist/types/threat.d.ts +38 -0
- package/dist/types/threat.d.ts.map +1 -0
- package/dist/types/threat.js +18 -0
- package/dist/types/threat.js.map +1 -0
- package/dist/utils/binary-codec.d.ts +225 -0
- package/dist/utils/binary-codec.d.ts.map +1 -0
- package/dist/utils/binary-codec.js +266 -0
- package/dist/utils/binary-codec.js.map +1 -0
- package/dist/utils/compare.d.ts +26 -0
- package/dist/utils/compare.d.ts.map +1 -0
- package/dist/utils/compare.js +44 -0
- package/dist/utils/compare.js.map +1 -0
- package/dist/utils/encode.d.ts +39 -0
- package/dist/utils/encode.d.ts.map +1 -0
- package/dist/utils/encode.js +124 -0
- package/dist/utils/encode.js.map +1 -0
- package/dist/utils/hash.d.ts +19 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +25 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/id.d.ts +20 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +47 -0
- package/dist/utils/id.js.map +1 -0
- package/dist/utils/runtime.d.ts +24 -0
- package/dist/utils/runtime.d.ts.map +1 -0
- package/dist/utils/runtime.js +68 -0
- package/dist/utils/runtime.js.map +1 -0
- package/dist/utils/serialize.d.ts +14 -0
- package/dist/utils/serialize.d.ts.map +1 -0
- package/dist/utils/serialize.js +27 -0
- package/dist/utils/serialize.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signature generation and validation.
|
|
3
|
+
*/
|
|
4
|
+
import type { ThreatInput } from './threat.js';
|
|
5
|
+
/**
|
|
6
|
+
* Options for signature generation.
|
|
7
|
+
*/
|
|
8
|
+
export interface GenerateSignatureOptions {
|
|
9
|
+
/** Maximum payload size in bytes. Default: 1MB */
|
|
10
|
+
maxPayloadSize?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Generate a content-based, collision-resistant threat signature.
|
|
14
|
+
*
|
|
15
|
+
* Format: {category}:{sha256(payload)}
|
|
16
|
+
*
|
|
17
|
+
* Uses deterministic serialization to ensure identical payloads
|
|
18
|
+
* produce identical signatures regardless of key order.
|
|
19
|
+
*
|
|
20
|
+
* SECURITY INVARIANTS:
|
|
21
|
+
* - Validates payload structure (rejects undefined, NaN, Infinity, etc.)
|
|
22
|
+
* - Checks size before hashing (memory exhaustion prevention)
|
|
23
|
+
* - Hashes UTF-8 bytes directly (split-brain prevention)
|
|
24
|
+
*
|
|
25
|
+
* @param threat - Threat input (without signature)
|
|
26
|
+
* @param options - Optional configuration
|
|
27
|
+
* @returns Signature string
|
|
28
|
+
* @throws TracehoundError if payload validation fails
|
|
29
|
+
*/
|
|
30
|
+
export declare function generateSignature(threat: ThreatInput, options?: GenerateSignatureOptions): string;
|
|
31
|
+
/**
|
|
32
|
+
* Compare two signatures in constant time.
|
|
33
|
+
* MUST be used for all signature comparisons to prevent timing attacks.
|
|
34
|
+
*
|
|
35
|
+
* @param a - First signature
|
|
36
|
+
* @param b - Second signature
|
|
37
|
+
* @returns True if signatures are equal
|
|
38
|
+
*/
|
|
39
|
+
export declare function compareSignatures(a: string, b: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Validate a signature format.
|
|
42
|
+
*
|
|
43
|
+
* @param sig - Signature to validate
|
|
44
|
+
* @returns True if valid format
|
|
45
|
+
*/
|
|
46
|
+
export declare function validateSignature(sig: string): boolean;
|
|
47
|
+
//# sourceMappingURL=signature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../../src/types/signature.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAK9C;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,kDAAkD;IAClD,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,OAAO,GAAE,wBAA6B,GACrC,MAAM,CASR;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAetD"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signature generation and validation.
|
|
3
|
+
*/
|
|
4
|
+
import { constantTimeEqual } from '../utils/compare.js';
|
|
5
|
+
import { encodePayload } from '../utils/encode.js';
|
|
6
|
+
import { hashBuffer } from '../utils/hash.js';
|
|
7
|
+
/** Default max payload size for signature generation (1MB) */
|
|
8
|
+
const DEFAULT_MAX_PAYLOAD_SIZE = 1_000_000;
|
|
9
|
+
/**
|
|
10
|
+
* Generate a content-based, collision-resistant threat signature.
|
|
11
|
+
*
|
|
12
|
+
* Format: {category}:{sha256(payload)}
|
|
13
|
+
*
|
|
14
|
+
* Uses deterministic serialization to ensure identical payloads
|
|
15
|
+
* produce identical signatures regardless of key order.
|
|
16
|
+
*
|
|
17
|
+
* SECURITY INVARIANTS:
|
|
18
|
+
* - Validates payload structure (rejects undefined, NaN, Infinity, etc.)
|
|
19
|
+
* - Checks size before hashing (memory exhaustion prevention)
|
|
20
|
+
* - Hashes UTF-8 bytes directly (split-brain prevention)
|
|
21
|
+
*
|
|
22
|
+
* @param threat - Threat input (without signature)
|
|
23
|
+
* @param options - Optional configuration
|
|
24
|
+
* @returns Signature string
|
|
25
|
+
* @throws TracehoundError if payload validation fails
|
|
26
|
+
*/
|
|
27
|
+
export function generateSignature(threat, options = {}) {
|
|
28
|
+
const maxSize = options.maxPayloadSize ?? DEFAULT_MAX_PAYLOAD_SIZE;
|
|
29
|
+
// CRITICAL: Use encodePayload for full validation
|
|
30
|
+
// This enforces: structure validation, size check, canonical encoding
|
|
31
|
+
const { bytes } = encodePayload(threat.scent.payload, maxSize);
|
|
32
|
+
const contentHash = hashBuffer(bytes);
|
|
33
|
+
return `${threat.category}:${contentHash}`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Compare two signatures in constant time.
|
|
37
|
+
* MUST be used for all signature comparisons to prevent timing attacks.
|
|
38
|
+
*
|
|
39
|
+
* @param a - First signature
|
|
40
|
+
* @param b - Second signature
|
|
41
|
+
* @returns True if signatures are equal
|
|
42
|
+
*/
|
|
43
|
+
export function compareSignatures(a, b) {
|
|
44
|
+
return constantTimeEqual(a, b);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Validate a signature format.
|
|
48
|
+
*
|
|
49
|
+
* @param sig - Signature to validate
|
|
50
|
+
* @returns True if valid format
|
|
51
|
+
*/
|
|
52
|
+
export function validateSignature(sig) {
|
|
53
|
+
const colonIndex = sig.indexOf(':');
|
|
54
|
+
if (colonIndex === -1)
|
|
55
|
+
return false;
|
|
56
|
+
const category = sig.slice(0, colonIndex);
|
|
57
|
+
const contentHash = sig.slice(colonIndex + 1);
|
|
58
|
+
// Category must not be empty
|
|
59
|
+
if (category.length === 0)
|
|
60
|
+
return false;
|
|
61
|
+
// Hash must be 64 hex characters (SHA-256)
|
|
62
|
+
if (contentHash.length !== 64)
|
|
63
|
+
return false;
|
|
64
|
+
if (!/^[a-f0-9]+$/.test(contentHash))
|
|
65
|
+
return false;
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=signature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.js","sourceRoot":"","sources":["../../src/types/signature.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAG7C,8DAA8D;AAC9D,MAAM,wBAAwB,GAAG,SAAS,CAAA;AAU1C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAmB,EACnB,UAAoC,EAAE;IAEtC,MAAM,OAAO,GAAG,OAAO,CAAC,cAAc,IAAI,wBAAwB,CAAA;IAElE,kDAAkD;IAClD,sEAAsE;IACtE,MAAM,EAAE,KAAK,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IAE9D,MAAM,WAAW,GAAG,UAAU,CAAC,KAAK,CAAC,CAAA;IACrC,OAAO,GAAG,MAAM,CAAC,QAAQ,IAAI,WAAW,EAAE,CAAA;AAC5C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,CAAS,EAAE,CAAS;IACpD,OAAO,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AAChC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACnC,IAAI,UAAU,KAAK,CAAC,CAAC;QAAE,OAAO,KAAK,CAAA;IAEnC,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAA;IACzC,MAAM,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAA;IAE7C,6BAA6B;IAC7B,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAEvC,2CAA2C;IAC3C,IAAI,WAAW,CAAC,MAAM,KAAK,EAAE;QAAE,OAAO,KAAK,CAAA;IAC3C,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC;QAAE,OAAO,KAAK,CAAA;IAElD,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Threat - a classified scent with signature.
|
|
3
|
+
*/
|
|
4
|
+
import type { Severity } from './common.js';
|
|
5
|
+
import type { Scent, ThreatCategory } from './scent.js';
|
|
6
|
+
export type { ThreatCategory, ThreatSignal } from './scent.js';
|
|
7
|
+
/**
|
|
8
|
+
* A threat is a scent that has been classified as malicious
|
|
9
|
+
* and assigned a signature for deduplication.
|
|
10
|
+
*/
|
|
11
|
+
export interface Threat {
|
|
12
|
+
/** Content-based, collision-resistant signature */
|
|
13
|
+
readonly signature: string;
|
|
14
|
+
/** Classification category */
|
|
15
|
+
readonly category: ThreatCategory;
|
|
16
|
+
/** Severity level */
|
|
17
|
+
readonly severity: Severity;
|
|
18
|
+
/** Original scent */
|
|
19
|
+
readonly scent: Scent;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Input for threat classification (before signature generation).
|
|
23
|
+
* Used internally by EvidenceFactory.
|
|
24
|
+
*/
|
|
25
|
+
export interface ThreatInput {
|
|
26
|
+
/** Classification category */
|
|
27
|
+
readonly category: ThreatCategory;
|
|
28
|
+
/** Severity level */
|
|
29
|
+
readonly severity: Severity;
|
|
30
|
+
/** Original scent */
|
|
31
|
+
readonly scent: Scent;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create ThreatInput from Scent with threat signal.
|
|
35
|
+
* Helper function for type conversion.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createThreatInput(scent: Scent): ThreatInput | null;
|
|
38
|
+
//# sourceMappingURL=threat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"threat.d.ts","sourceRoot":"","sources":["../../src/types/threat.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAGvD,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9D;;;GAGG;AACH,MAAM,WAAW,MAAM;IACrB,mDAAmD;IACnD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,8BAA8B;IAC9B,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAA;IACjC,qBAAqB;IACrB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;IAC3B,qBAAqB;IACrB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,8BAA8B;IAC9B,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAA;IACjC,qBAAqB;IACrB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;IAC3B,qBAAqB;IACrB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;CACtB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,GAAG,WAAW,GAAG,IAAI,CASlE"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Threat - a classified scent with signature.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Create ThreatInput from Scent with threat signal.
|
|
6
|
+
* Helper function for type conversion.
|
|
7
|
+
*/
|
|
8
|
+
export function createThreatInput(scent) {
|
|
9
|
+
if (!scent.threat) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
category: scent.threat.category,
|
|
14
|
+
severity: scent.threat.severity,
|
|
15
|
+
scent,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=threat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"threat.js","sourceRoot":"","sources":["../../src/types/threat.ts"],"names":[],"mappings":"AAAA;;GAEG;AAoCH;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAY;IAC5C,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,IAAI,CAAA;IACb,CAAC;IACD,OAAO;QACL,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,QAAQ;QAC/B,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,QAAQ;QAC/B,KAAK;KACN,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binary Codec - gzip encoding for evidence storage.
|
|
3
|
+
*
|
|
4
|
+
* SECURITY INVARIANT:
|
|
5
|
+
* - `encode()` used in hot-path (Agent → EvidenceFactory)
|
|
6
|
+
* - `decode()` ONLY for cold storage / forensics
|
|
7
|
+
* - Agent / Hound Pool MUST NOT have access to decode
|
|
8
|
+
*
|
|
9
|
+
* INTEGRITY INVARIANT (Cold Storage):
|
|
10
|
+
* - Always call verify() BEFORE decode()
|
|
11
|
+
* - Decoding without verification is a DoS vector
|
|
12
|
+
* - Empty payloads are VALID (originalSize=0, compressedSize>0)
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Hot-path codec interface.
|
|
16
|
+
* Used by Agent, EvidenceFactory, Hound Pool.
|
|
17
|
+
* NO decode access - security by design.
|
|
18
|
+
*/
|
|
19
|
+
export interface HotPathCodec {
|
|
20
|
+
/**
|
|
21
|
+
* Encode bytes to compressed format.
|
|
22
|
+
* Used in hot-path for evidence creation.
|
|
23
|
+
*/
|
|
24
|
+
encode(bytes: Uint8Array): Uint8Array;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Cold-path codec interface.
|
|
28
|
+
* Used ONLY for: evacuate, cold storage retrieval, forensics.
|
|
29
|
+
* Extends HotPathCodec with decode capability.
|
|
30
|
+
*/
|
|
31
|
+
export interface ColdPathCodec extends HotPathCodec {
|
|
32
|
+
/**
|
|
33
|
+
* Decode compressed bytes back to original.
|
|
34
|
+
* NOT used in Agent/Hound hot-path.
|
|
35
|
+
*/
|
|
36
|
+
decode(bytes: Uint8Array): Uint8Array;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Codec statistics.
|
|
40
|
+
*/
|
|
41
|
+
export interface CodecStats {
|
|
42
|
+
/** Total encode operations */
|
|
43
|
+
encodeCount: number;
|
|
44
|
+
/** Total decode operations */
|
|
45
|
+
decodeCount: number;
|
|
46
|
+
/** Total bytes before encoding */
|
|
47
|
+
totalInputBytes: number;
|
|
48
|
+
/** Total bytes after encoding */
|
|
49
|
+
totalOutputBytes: number;
|
|
50
|
+
/** Average compression ratio */
|
|
51
|
+
compressionRatio: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Gzip codec implementation.
|
|
55
|
+
* Provides both hot-path and cold-path capabilities.
|
|
56
|
+
*
|
|
57
|
+
* USAGE:
|
|
58
|
+
* - Pass as HotPathCodec to Agent/EvidenceFactory
|
|
59
|
+
* - Pass as ColdPathCodec only to cold storage / forensics tools
|
|
60
|
+
*/
|
|
61
|
+
export declare class GzipCodec implements ColdPathCodec {
|
|
62
|
+
private _encodeCount;
|
|
63
|
+
private _decodeCount;
|
|
64
|
+
private _totalInputBytes;
|
|
65
|
+
private _totalOutputBytes;
|
|
66
|
+
/**
|
|
67
|
+
* Encode bytes using gzip compression.
|
|
68
|
+
* Sync operation for hot-path performance.
|
|
69
|
+
*/
|
|
70
|
+
encode(bytes: Uint8Array): Uint8Array;
|
|
71
|
+
/**
|
|
72
|
+
* Decode gzip compressed bytes.
|
|
73
|
+
* ONLY for cold storage retrieval / forensics.
|
|
74
|
+
*/
|
|
75
|
+
decode(bytes: Uint8Array): Uint8Array;
|
|
76
|
+
/**
|
|
77
|
+
* Get codec statistics (immutable snapshot).
|
|
78
|
+
*/
|
|
79
|
+
get stats(): Readonly<CodecStats>;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Create a hot-path codec (encode only).
|
|
83
|
+
* Use this for Agent / EvidenceFactory.
|
|
84
|
+
*
|
|
85
|
+
* @returns HotPathCodec with no decode access
|
|
86
|
+
*/
|
|
87
|
+
export declare function createHotPathCodec(): HotPathCodec;
|
|
88
|
+
/**
|
|
89
|
+
* Create a cold-path codec (encode + decode).
|
|
90
|
+
* Use ONLY for cold storage / forensics.
|
|
91
|
+
*
|
|
92
|
+
* @returns ColdPathCodec with full access
|
|
93
|
+
*/
|
|
94
|
+
export declare function createColdPathCodec(): ColdPathCodec;
|
|
95
|
+
/**
|
|
96
|
+
* Async hot-path codec interface.
|
|
97
|
+
* Non-blocking encode for background operations.
|
|
98
|
+
*/
|
|
99
|
+
export interface AsyncHotPathCodec {
|
|
100
|
+
/** Async encode bytes to compressed format. */
|
|
101
|
+
encode(bytes: Uint8Array): Promise<Uint8Array>;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Async cold-path codec interface.
|
|
105
|
+
* Non-blocking encode + decode for cold storage operations.
|
|
106
|
+
* Extends AsyncHotPathCodec with decode capability.
|
|
107
|
+
*/
|
|
108
|
+
export interface AsyncColdPathCodec extends AsyncHotPathCodec {
|
|
109
|
+
/** Async decode compressed bytes back to original. */
|
|
110
|
+
decode(bytes: Uint8Array): Promise<Uint8Array>;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Async gzip codec implementation.
|
|
114
|
+
* Uses Node.js non-blocking zlib for cold storage and background operations.
|
|
115
|
+
*
|
|
116
|
+
* USAGE:
|
|
117
|
+
* - Cold storage write/read (never blocks the event loop)
|
|
118
|
+
* - Batch evidence evacuation
|
|
119
|
+
* - Background forensic analysis
|
|
120
|
+
*
|
|
121
|
+
* NOT for hot-path — use GzipCodec (sync) for Agent/EvidenceFactory.
|
|
122
|
+
*/
|
|
123
|
+
export declare class AsyncGzipCodec implements AsyncColdPathCodec {
|
|
124
|
+
private _encodeCount;
|
|
125
|
+
private _decodeCount;
|
|
126
|
+
private _totalInputBytes;
|
|
127
|
+
private _totalOutputBytes;
|
|
128
|
+
/**
|
|
129
|
+
* Async encode bytes using gzip compression.
|
|
130
|
+
* Non-blocking — yields to event loop during compression.
|
|
131
|
+
*/
|
|
132
|
+
encode(bytes: Uint8Array): Promise<Uint8Array>;
|
|
133
|
+
/**
|
|
134
|
+
* Async decode gzip compressed bytes.
|
|
135
|
+
* Non-blocking — yields to event loop during decompression.
|
|
136
|
+
*/
|
|
137
|
+
decode(bytes: Uint8Array): Promise<Uint8Array>;
|
|
138
|
+
/**
|
|
139
|
+
* Get codec statistics (immutable snapshot).
|
|
140
|
+
*/
|
|
141
|
+
get stats(): Readonly<CodecStats>;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Create an async cold-path codec (encode + decode).
|
|
145
|
+
* Use for cold storage operations where blocking is unacceptable.
|
|
146
|
+
*
|
|
147
|
+
* @returns AsyncColdPathCodec with non-blocking encode/decode
|
|
148
|
+
*/
|
|
149
|
+
export declare function createAsyncColdPathCodec(): AsyncColdPathCodec;
|
|
150
|
+
/**
|
|
151
|
+
* Encoded payload with compression and integrity hash.
|
|
152
|
+
* Used for cold storage serialization.
|
|
153
|
+
*/
|
|
154
|
+
export interface EncodedPayload {
|
|
155
|
+
/** gzip compressed bytes */
|
|
156
|
+
readonly compressed: Uint8Array;
|
|
157
|
+
/** SHA-256 hash of compressed bytes (hex) */
|
|
158
|
+
readonly hash: string;
|
|
159
|
+
/** Original uncompressed size in bytes */
|
|
160
|
+
readonly originalSize: number;
|
|
161
|
+
/** Compressed size in bytes */
|
|
162
|
+
readonly compressedSize: number;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Codec error for integrity violations.
|
|
166
|
+
*/
|
|
167
|
+
export declare class CodecError extends Error {
|
|
168
|
+
readonly code: 'INTEGRITY_VIOLATION' | 'DECODE_FAILED' | 'ENCODE_FAILED';
|
|
169
|
+
readonly cause?: unknown | undefined;
|
|
170
|
+
constructor(message: string, code: 'INTEGRITY_VIOLATION' | 'DECODE_FAILED' | 'ENCODE_FAILED', cause?: unknown | undefined);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Encode payload for cold storage with integrity hash.
|
|
174
|
+
*
|
|
175
|
+
* - Compresses with gzip
|
|
176
|
+
* - Computes SHA-256 of compressed bytes
|
|
177
|
+
* - Empty payloads are VALID (originalSize=0, compressedSize>0)
|
|
178
|
+
*
|
|
179
|
+
* @param payload - Raw bytes to encode
|
|
180
|
+
* @returns Encoded payload with hash
|
|
181
|
+
*/
|
|
182
|
+
export declare function encodeWithIntegrity(payload: Uint8Array): EncodedPayload;
|
|
183
|
+
/**
|
|
184
|
+
* Verify integrity of encoded payload.
|
|
185
|
+
*
|
|
186
|
+
* MUST be called BEFORE decodeWithIntegrity().
|
|
187
|
+
* This is a cheap operation (hash only, no decompression).
|
|
188
|
+
*
|
|
189
|
+
* @param encoded - Payload to verify
|
|
190
|
+
* @returns true if hash matches, false if tampered
|
|
191
|
+
*/
|
|
192
|
+
export declare function verify(encoded: EncodedPayload): boolean;
|
|
193
|
+
/**
|
|
194
|
+
* Decode payload from cold storage.
|
|
195
|
+
*
|
|
196
|
+
* CRITICAL: Call verify() first. Decoding untrusted data wastes CPU.
|
|
197
|
+
*
|
|
198
|
+
* @param encoded - Verified encoded payload
|
|
199
|
+
* @returns Original uncompressed bytes
|
|
200
|
+
* @throws CodecError if decompression fails (storage corruption)
|
|
201
|
+
*/
|
|
202
|
+
export declare function decodeWithIntegrity(encoded: EncodedPayload): Uint8Array;
|
|
203
|
+
/**
|
|
204
|
+
* Async encode payload for cold storage with integrity hash.
|
|
205
|
+
* Non-blocking version of encodeWithIntegrity().
|
|
206
|
+
*
|
|
207
|
+
* Use for cold storage write operations where event loop must not stall.
|
|
208
|
+
*
|
|
209
|
+
* @param payload - Raw bytes to encode
|
|
210
|
+
* @returns Encoded payload with hash
|
|
211
|
+
* @throws CodecError if compression fails
|
|
212
|
+
*/
|
|
213
|
+
export declare function encodeWithIntegrityAsync(payload: Uint8Array): Promise<EncodedPayload>;
|
|
214
|
+
/**
|
|
215
|
+
* Async decode payload from cold storage.
|
|
216
|
+
* Non-blocking version of decodeWithIntegrity().
|
|
217
|
+
*
|
|
218
|
+
* CRITICAL: Call verify() first. Decoding untrusted data wastes CPU.
|
|
219
|
+
*
|
|
220
|
+
* @param encoded - Verified encoded payload
|
|
221
|
+
* @returns Original uncompressed bytes
|
|
222
|
+
* @throws CodecError if decompression fails (storage corruption)
|
|
223
|
+
*/
|
|
224
|
+
export declare function decodeWithIntegrityAsync(encoded: EncodedPayload): Promise<Uint8Array>;
|
|
225
|
+
//# sourceMappingURL=binary-codec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binary-codec.d.ts","sourceRoot":"","sources":["../../src/utils/binary-codec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binary Codec - gzip encoding for evidence storage.
|
|
3
|
+
*
|
|
4
|
+
* SECURITY INVARIANT:
|
|
5
|
+
* - `encode()` used in hot-path (Agent → EvidenceFactory)
|
|
6
|
+
* - `decode()` ONLY for cold storage / forensics
|
|
7
|
+
* - Agent / Hound Pool MUST NOT have access to decode
|
|
8
|
+
*
|
|
9
|
+
* INTEGRITY INVARIANT (Cold Storage):
|
|
10
|
+
* - Always call verify() BEFORE decode()
|
|
11
|
+
* - Decoding without verification is a DoS vector
|
|
12
|
+
* - Empty payloads are VALID (originalSize=0, compressedSize>0)
|
|
13
|
+
*/
|
|
14
|
+
import { promisify } from 'node:util';
|
|
15
|
+
import { gunzip, gunzipSync, gzip, gzipSync } from 'node:zlib';
|
|
16
|
+
import { hashBuffer } from './hash.js';
|
|
17
|
+
const gzipAsync = promisify(gzip);
|
|
18
|
+
const gunzipAsync = promisify(gunzip);
|
|
19
|
+
/**
|
|
20
|
+
* Gzip codec implementation.
|
|
21
|
+
* Provides both hot-path and cold-path capabilities.
|
|
22
|
+
*
|
|
23
|
+
* USAGE:
|
|
24
|
+
* - Pass as HotPathCodec to Agent/EvidenceFactory
|
|
25
|
+
* - Pass as ColdPathCodec only to cold storage / forensics tools
|
|
26
|
+
*/
|
|
27
|
+
export class GzipCodec {
|
|
28
|
+
_encodeCount = 0;
|
|
29
|
+
_decodeCount = 0;
|
|
30
|
+
_totalInputBytes = 0;
|
|
31
|
+
_totalOutputBytes = 0;
|
|
32
|
+
/**
|
|
33
|
+
* Encode bytes using gzip compression.
|
|
34
|
+
* Sync operation for hot-path performance.
|
|
35
|
+
*/
|
|
36
|
+
encode(bytes) {
|
|
37
|
+
this._encodeCount++;
|
|
38
|
+
this._totalInputBytes += bytes.length;
|
|
39
|
+
const compressed = gzipSync(bytes, {
|
|
40
|
+
level: 6, // Balanced speed/compression
|
|
41
|
+
});
|
|
42
|
+
const result = new Uint8Array(compressed);
|
|
43
|
+
this._totalOutputBytes += result.length;
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Decode gzip compressed bytes.
|
|
48
|
+
* ONLY for cold storage retrieval / forensics.
|
|
49
|
+
*/
|
|
50
|
+
decode(bytes) {
|
|
51
|
+
this._decodeCount++;
|
|
52
|
+
const decompressed = gunzipSync(bytes);
|
|
53
|
+
return new Uint8Array(decompressed);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get codec statistics (immutable snapshot).
|
|
57
|
+
*/
|
|
58
|
+
get stats() {
|
|
59
|
+
const ratio = this._totalInputBytes > 0 ? this._totalOutputBytes / this._totalInputBytes : 0;
|
|
60
|
+
return Object.freeze({
|
|
61
|
+
encodeCount: this._encodeCount,
|
|
62
|
+
decodeCount: this._decodeCount,
|
|
63
|
+
totalInputBytes: this._totalInputBytes,
|
|
64
|
+
totalOutputBytes: this._totalOutputBytes,
|
|
65
|
+
compressionRatio: ratio,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Create a hot-path codec (encode only).
|
|
71
|
+
* Use this for Agent / EvidenceFactory.
|
|
72
|
+
*
|
|
73
|
+
* @returns HotPathCodec with no decode access
|
|
74
|
+
*/
|
|
75
|
+
export function createHotPathCodec() {
|
|
76
|
+
const codec = new GzipCodec();
|
|
77
|
+
// Return only HotPathCodec interface - no decode access
|
|
78
|
+
return {
|
|
79
|
+
encode: (bytes) => codec.encode(bytes),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Create a cold-path codec (encode + decode).
|
|
84
|
+
* Use ONLY for cold storage / forensics.
|
|
85
|
+
*
|
|
86
|
+
* @returns ColdPathCodec with full access
|
|
87
|
+
*/
|
|
88
|
+
export function createColdPathCodec() {
|
|
89
|
+
return new GzipCodec();
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Async gzip codec implementation.
|
|
93
|
+
* Uses Node.js non-blocking zlib for cold storage and background operations.
|
|
94
|
+
*
|
|
95
|
+
* USAGE:
|
|
96
|
+
* - Cold storage write/read (never blocks the event loop)
|
|
97
|
+
* - Batch evidence evacuation
|
|
98
|
+
* - Background forensic analysis
|
|
99
|
+
*
|
|
100
|
+
* NOT for hot-path — use GzipCodec (sync) for Agent/EvidenceFactory.
|
|
101
|
+
*/
|
|
102
|
+
export class AsyncGzipCodec {
|
|
103
|
+
_encodeCount = 0;
|
|
104
|
+
_decodeCount = 0;
|
|
105
|
+
_totalInputBytes = 0;
|
|
106
|
+
_totalOutputBytes = 0;
|
|
107
|
+
/**
|
|
108
|
+
* Async encode bytes using gzip compression.
|
|
109
|
+
* Non-blocking — yields to event loop during compression.
|
|
110
|
+
*/
|
|
111
|
+
async encode(bytes) {
|
|
112
|
+
this._encodeCount++;
|
|
113
|
+
this._totalInputBytes += bytes.length;
|
|
114
|
+
const compressed = await gzipAsync(bytes, { level: 6 });
|
|
115
|
+
const result = new Uint8Array(compressed);
|
|
116
|
+
this._totalOutputBytes += result.length;
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Async decode gzip compressed bytes.
|
|
121
|
+
* Non-blocking — yields to event loop during decompression.
|
|
122
|
+
*/
|
|
123
|
+
async decode(bytes) {
|
|
124
|
+
this._decodeCount++;
|
|
125
|
+
const decompressed = await gunzipAsync(bytes);
|
|
126
|
+
return new Uint8Array(decompressed);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get codec statistics (immutable snapshot).
|
|
130
|
+
*/
|
|
131
|
+
get stats() {
|
|
132
|
+
const ratio = this._totalInputBytes > 0 ? this._totalOutputBytes / this._totalInputBytes : 0;
|
|
133
|
+
return Object.freeze({
|
|
134
|
+
encodeCount: this._encodeCount,
|
|
135
|
+
decodeCount: this._decodeCount,
|
|
136
|
+
totalInputBytes: this._totalInputBytes,
|
|
137
|
+
totalOutputBytes: this._totalOutputBytes,
|
|
138
|
+
compressionRatio: ratio,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Create an async cold-path codec (encode + decode).
|
|
144
|
+
* Use for cold storage operations where blocking is unacceptable.
|
|
145
|
+
*
|
|
146
|
+
* @returns AsyncColdPathCodec with non-blocking encode/decode
|
|
147
|
+
*/
|
|
148
|
+
export function createAsyncColdPathCodec() {
|
|
149
|
+
return new AsyncGzipCodec();
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Codec error for integrity violations.
|
|
153
|
+
*/
|
|
154
|
+
export class CodecError extends Error {
|
|
155
|
+
code;
|
|
156
|
+
cause;
|
|
157
|
+
constructor(message, code, cause) {
|
|
158
|
+
super(message);
|
|
159
|
+
this.code = code;
|
|
160
|
+
this.cause = cause;
|
|
161
|
+
this.name = 'CodecError';
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Encode payload for cold storage with integrity hash.
|
|
166
|
+
*
|
|
167
|
+
* - Compresses with gzip
|
|
168
|
+
* - Computes SHA-256 of compressed bytes
|
|
169
|
+
* - Empty payloads are VALID (originalSize=0, compressedSize>0)
|
|
170
|
+
*
|
|
171
|
+
* @param payload - Raw bytes to encode
|
|
172
|
+
* @returns Encoded payload with hash
|
|
173
|
+
*/
|
|
174
|
+
export function encodeWithIntegrity(payload) {
|
|
175
|
+
const compressed = gzipSync(payload, { level: 6 });
|
|
176
|
+
const compressedBytes = new Uint8Array(compressed);
|
|
177
|
+
const hash = hashBuffer(compressedBytes);
|
|
178
|
+
return {
|
|
179
|
+
compressed: compressedBytes,
|
|
180
|
+
hash,
|
|
181
|
+
originalSize: payload.length,
|
|
182
|
+
compressedSize: compressedBytes.length,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Verify integrity of encoded payload.
|
|
187
|
+
*
|
|
188
|
+
* MUST be called BEFORE decodeWithIntegrity().
|
|
189
|
+
* This is a cheap operation (hash only, no decompression).
|
|
190
|
+
*
|
|
191
|
+
* @param encoded - Payload to verify
|
|
192
|
+
* @returns true if hash matches, false if tampered
|
|
193
|
+
*/
|
|
194
|
+
export function verify(encoded) {
|
|
195
|
+
const computed = hashBuffer(encoded.compressed);
|
|
196
|
+
return computed === encoded.hash;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Decode payload from cold storage.
|
|
200
|
+
*
|
|
201
|
+
* CRITICAL: Call verify() first. Decoding untrusted data wastes CPU.
|
|
202
|
+
*
|
|
203
|
+
* @param encoded - Verified encoded payload
|
|
204
|
+
* @returns Original uncompressed bytes
|
|
205
|
+
* @throws CodecError if decompression fails (storage corruption)
|
|
206
|
+
*/
|
|
207
|
+
export function decodeWithIntegrity(encoded) {
|
|
208
|
+
try {
|
|
209
|
+
const decompressed = gunzipSync(encoded.compressed);
|
|
210
|
+
return new Uint8Array(decompressed);
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
// Decompression failure = integrity violation or storage corruption
|
|
214
|
+
// NEVER swallow this error - propagate for Fail-Safe handling
|
|
215
|
+
throw new CodecError('Decompression failed - possible storage corruption', 'DECODE_FAILED', err);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// ============================================================================
|
|
219
|
+
// ASYNC COLD STORAGE INTEGRITY (Non-blocking encode/decode for cold storage)
|
|
220
|
+
// ============================================================================
|
|
221
|
+
/**
|
|
222
|
+
* Async encode payload for cold storage with integrity hash.
|
|
223
|
+
* Non-blocking version of encodeWithIntegrity().
|
|
224
|
+
*
|
|
225
|
+
* Use for cold storage write operations where event loop must not stall.
|
|
226
|
+
*
|
|
227
|
+
* @param payload - Raw bytes to encode
|
|
228
|
+
* @returns Encoded payload with hash
|
|
229
|
+
* @throws CodecError if compression fails
|
|
230
|
+
*/
|
|
231
|
+
export async function encodeWithIntegrityAsync(payload) {
|
|
232
|
+
try {
|
|
233
|
+
const compressed = await gzipAsync(payload, { level: 6 });
|
|
234
|
+
const compressedBytes = new Uint8Array(compressed);
|
|
235
|
+
const hash = hashBuffer(compressedBytes);
|
|
236
|
+
return {
|
|
237
|
+
compressed: compressedBytes,
|
|
238
|
+
hash,
|
|
239
|
+
originalSize: payload.length,
|
|
240
|
+
compressedSize: compressedBytes.length,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
throw new CodecError('Async compression failed', 'ENCODE_FAILED', err);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Async decode payload from cold storage.
|
|
249
|
+
* Non-blocking version of decodeWithIntegrity().
|
|
250
|
+
*
|
|
251
|
+
* CRITICAL: Call verify() first. Decoding untrusted data wastes CPU.
|
|
252
|
+
*
|
|
253
|
+
* @param encoded - Verified encoded payload
|
|
254
|
+
* @returns Original uncompressed bytes
|
|
255
|
+
* @throws CodecError if decompression fails (storage corruption)
|
|
256
|
+
*/
|
|
257
|
+
export async function decodeWithIntegrityAsync(encoded) {
|
|
258
|
+
try {
|
|
259
|
+
const decompressed = await gunzipAsync(encoded.compressed);
|
|
260
|
+
return new Uint8Array(decompressed);
|
|
261
|
+
}
|
|
262
|
+
catch (err) {
|
|
263
|
+
throw new CodecError('Async decompression failed - possible storage corruption', 'DECODE_FAILED', err);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
//# sourceMappingURL=binary-codec.js.map
|