@varta-health/client 0.1.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/CHANGELOG.md +41 -0
- package/LICENSE +25 -0
- package/README.md +140 -0
- package/dist/client.d.ts +32 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +187 -0
- package/dist/client.js.map +1 -0
- package/dist/errno.d.ts +11 -0
- package/dist/errno.d.ts.map +1 -0
- package/dist/errno.js +52 -0
- package/dist/errno.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/outcome.d.ts +31 -0
- package/dist/outcome.d.ts.map +1 -0
- package/dist/outcome.js +92 -0
- package/dist/outcome.js.map +1 -0
- package/dist/panic.d.ts +8 -0
- package/dist/panic.d.ts.map +1 -0
- package/dist/panic.js +172 -0
- package/dist/panic.js.map +1 -0
- package/dist/transport.d.ts +40 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +208 -0
- package/dist/transport.js.map +1 -0
- package/dist/vlp.d.ts +30 -0
- package/dist/vlp.d.ts.map +1 -0
- package/dist/vlp.js +159 -0
- package/dist/vlp.js.map +1 -0
- package/dist/vlp_secure.d.ts +17 -0
- package/dist/vlp_secure.d.ts.map +1 -0
- package/dist/vlp_secure.js +158 -0
- package/dist/vlp_secure.js.map +1 -0
- package/package.json +72 -0
package/dist/outcome.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// BeatOutcome tagged union, DropReason enum, BeatError, and the
|
|
2
|
+
// `classifySendError` function — mirrors Rust `BeatOutcome`,
|
|
3
|
+
// Python `BeatOutcome`/`DropReason`, and Go `BeatOutcome`/`DropReason`.
|
|
4
|
+
import { EAGAIN, ECONNREFUSED, ECONNRESET, ENOBUFS, ENOENT, ENOSPC, ENOTCONN, EPIPE, EWOULDBLOCK, errnoName, } from "./errno.js";
|
|
5
|
+
// String-valued enum so the wire-side metrics label matches the Rust
|
|
6
|
+
// `Display` impl directly. The string values MUST stay byte-equal to
|
|
7
|
+
// the labels Prometheus sees from the observer (`varta_*_total{reason=…}`).
|
|
8
|
+
export var DropReason;
|
|
9
|
+
(function (DropReason) {
|
|
10
|
+
DropReason["KernelQueueFull"] = "kernel queue full";
|
|
11
|
+
DropReason["NoObserver"] = "no observer";
|
|
12
|
+
DropReason["PeerGone"] = "peer gone";
|
|
13
|
+
DropReason["StorageFull"] = "storage full";
|
|
14
|
+
})(DropReason || (DropReason = {}));
|
|
15
|
+
// Payload of `{ kind: "failed" }`. Extends `Error` so it can be thrown
|
|
16
|
+
// or logged like any other Node error, but carries structured `errno`
|
|
17
|
+
// and `kind` fields so callers don't have to parse `.message`.
|
|
18
|
+
export class BeatError extends Error {
|
|
19
|
+
errno;
|
|
20
|
+
kind;
|
|
21
|
+
constructor(errno, kind, message) {
|
|
22
|
+
super(message ?? `${kind} (errno=${errno})`);
|
|
23
|
+
this.name = "BeatError";
|
|
24
|
+
this.errno = errno;
|
|
25
|
+
this.kind = kind;
|
|
26
|
+
}
|
|
27
|
+
static fromNodeError(err) {
|
|
28
|
+
const code = typeof err.errno === "number" ? err.errno : 0;
|
|
29
|
+
return new BeatError(code, code ? errnoName(code) : err.name || "Other", err.message);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export const BeatOutcomes = Object.freeze({
|
|
33
|
+
sent() {
|
|
34
|
+
return { kind: "sent" };
|
|
35
|
+
},
|
|
36
|
+
dropped(reason) {
|
|
37
|
+
return { kind: "dropped", reason };
|
|
38
|
+
},
|
|
39
|
+
failed(error) {
|
|
40
|
+
return { kind: "failed", error };
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
export function isSent(o) {
|
|
44
|
+
return o.kind === "sent";
|
|
45
|
+
}
|
|
46
|
+
export function isDropped(o) {
|
|
47
|
+
return o.kind === "dropped";
|
|
48
|
+
}
|
|
49
|
+
export function isFailed(o) {
|
|
50
|
+
return o.kind === "failed";
|
|
51
|
+
}
|
|
52
|
+
// Translate a Node `send`-time error into a `BeatOutcome`.
|
|
53
|
+
//
|
|
54
|
+
// Mirrors `crates/varta-client/src/client.rs::classify_send_error` and
|
|
55
|
+
// the Python and Go equivalents. Exported because authors of custom
|
|
56
|
+
// `BeatTransport` implementations are likely to want the same bucketing.
|
|
57
|
+
export function classifySendError(err) {
|
|
58
|
+
// Node's `ErrnoException.errno` is the negative POSIX value on some
|
|
59
|
+
// calls (libuv convention) and the positive value on others. Take
|
|
60
|
+
// the absolute value so the comparisons below work uniformly.
|
|
61
|
+
const rawErrno = typeof err.errno === "number" ? err.errno : 0;
|
|
62
|
+
const code = rawErrno < 0 ? -rawErrno : rawErrno;
|
|
63
|
+
const sym = err.code ?? "";
|
|
64
|
+
if (code === ENOBUFS || sym === "ENOBUFS") {
|
|
65
|
+
return BeatOutcomes.dropped(DropReason.KernelQueueFull);
|
|
66
|
+
}
|
|
67
|
+
if (code === EAGAIN ||
|
|
68
|
+
code === EWOULDBLOCK ||
|
|
69
|
+
sym === "EAGAIN" ||
|
|
70
|
+
sym === "EWOULDBLOCK") {
|
|
71
|
+
return BeatOutcomes.dropped(DropReason.KernelQueueFull);
|
|
72
|
+
}
|
|
73
|
+
if (code === ECONNREFUSED ||
|
|
74
|
+
code === ENOENT ||
|
|
75
|
+
sym === "ECONNREFUSED" ||
|
|
76
|
+
sym === "ENOENT") {
|
|
77
|
+
return BeatOutcomes.dropped(DropReason.NoObserver);
|
|
78
|
+
}
|
|
79
|
+
if (code === ECONNRESET ||
|
|
80
|
+
code === ENOTCONN ||
|
|
81
|
+
code === EPIPE ||
|
|
82
|
+
sym === "ECONNRESET" ||
|
|
83
|
+
sym === "ENOTCONN" ||
|
|
84
|
+
sym === "EPIPE") {
|
|
85
|
+
return BeatOutcomes.dropped(DropReason.PeerGone);
|
|
86
|
+
}
|
|
87
|
+
if (code === ENOSPC || sym === "ENOSPC") {
|
|
88
|
+
return BeatOutcomes.dropped(DropReason.StorageFull);
|
|
89
|
+
}
|
|
90
|
+
return BeatOutcomes.failed(BeatError.fromNodeError(err));
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=outcome.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outcome.js","sourceRoot":"","sources":["../src/outcome.ts"],"names":[],"mappings":"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"}
|
package/dist/panic.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare class PanicInstallError extends Error {
|
|
2
|
+
readonly kind: string;
|
|
3
|
+
constructor(kind: string, message: string);
|
|
4
|
+
}
|
|
5
|
+
export declare function installSignalHandlerUdp(host: string, port: number): void;
|
|
6
|
+
export declare function installSignalHandlerSecureUdp(host: string, port: number, key: Buffer): void;
|
|
7
|
+
export declare function run(fn: () => void | Promise<void>): Promise<void>;
|
|
8
|
+
//# sourceMappingURL=panic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"panic.d.ts","sourceRoot":"","sources":["../src/panic.ts"],"names":[],"mappings":"AA8BA,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBACV,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK1C;AAwED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAexE;AAOD,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,GACV,IAAI,CAkDN;AAMD,wBAAsB,GAAG,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CASvE"}
|
package/dist/panic.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// Panic-style emitters — Node's analogue of Rust's `install_panic_handler`.
|
|
2
|
+
//
|
|
3
|
+
// Node has no `excepthook` in the Python sense, but it does have:
|
|
4
|
+
// * `process.on('uncaughtException')` — synchronous throws.
|
|
5
|
+
// * `process.on('unhandledRejection')` — async promise rejections.
|
|
6
|
+
// * `process.on('SIGTERM' | 'SIGINT' | 'SIGQUIT' | 'SIGHUP')` —
|
|
7
|
+
// terminating signals.
|
|
8
|
+
//
|
|
9
|
+
// Each installer wires all three into a single critical-beat emitter
|
|
10
|
+
// for the chosen transport. The socket and frame buffer are
|
|
11
|
+
// pre-allocated at install time so the hot path is alloc-free and
|
|
12
|
+
// async-signal-safe (cerebrum 2026-05-14).
|
|
13
|
+
import { randomBytes } from "node:crypto";
|
|
14
|
+
import { createSocket } from "node:dgram";
|
|
15
|
+
import { encodeShared, KEY_BYTES, deriveIvPrefix, IV_RANDOM_BYTES, SESSION_SALT_BYTES, } from "./vlp_secure.js";
|
|
16
|
+
import { encodeInto, FRAME_BYTES, NONCE_TERMINAL, Status, } from "./vlp.js";
|
|
17
|
+
export class PanicInstallError extends Error {
|
|
18
|
+
kind;
|
|
19
|
+
constructor(kind, message) {
|
|
20
|
+
super(`${kind}: ${message}`);
|
|
21
|
+
this.name = "PanicInstallError";
|
|
22
|
+
this.kind = kind;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const FATAL_SIGNALS = ["SIGTERM", "SIGINT", "SIGQUIT", "SIGHUP"];
|
|
26
|
+
function buildCriticalFrame(payload = 0) {
|
|
27
|
+
const buf = Buffer.alloc(FRAME_BYTES);
|
|
28
|
+
encodeInto(buf, Status.Critical, process.pid >>> 0, process.hrtime.bigint(), NONCE_TERMINAL, payload >>> 0);
|
|
29
|
+
return buf;
|
|
30
|
+
}
|
|
31
|
+
// Wire emit() into the three terminating event sources. Each callback
|
|
32
|
+
// is one-shot: after the first invocation we tear down the listeners
|
|
33
|
+
// so process exit isn't blocked by lingering handlers.
|
|
34
|
+
function arm(emit) {
|
|
35
|
+
let fired = false;
|
|
36
|
+
const trigger = () => {
|
|
37
|
+
if (fired)
|
|
38
|
+
return;
|
|
39
|
+
fired = true;
|
|
40
|
+
try {
|
|
41
|
+
emit();
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Hook must never propagate.
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
process.on("uncaughtException", (err) => {
|
|
48
|
+
trigger();
|
|
49
|
+
// Re-throw so Node's default crash printer still runs and the
|
|
50
|
+
// process exits with code 1 — preserves Node's standard behaviour.
|
|
51
|
+
setImmediate(() => {
|
|
52
|
+
throw err;
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
process.on("unhandledRejection", (reason) => {
|
|
56
|
+
trigger();
|
|
57
|
+
setImmediate(() => {
|
|
58
|
+
throw reason instanceof Error ? reason : new Error(String(reason));
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
for (const sig of FATAL_SIGNALS) {
|
|
62
|
+
process.on(sig, () => {
|
|
63
|
+
trigger();
|
|
64
|
+
// Restore default handler and re-raise so the process exits with
|
|
65
|
+
// the canonical signal-derived code (128 + signum on POSIX).
|
|
66
|
+
process.removeAllListeners(sig);
|
|
67
|
+
process.kill(process.pid, sig);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function createConnectedSocket(host, port) {
|
|
72
|
+
const family = host.includes(":") ? "udp6" : "udp4";
|
|
73
|
+
const s = createSocket(family);
|
|
74
|
+
s.on("error", () => {
|
|
75
|
+
// Drop libuv async errors silently — emission is best-effort.
|
|
76
|
+
});
|
|
77
|
+
s.unref();
|
|
78
|
+
s.connect(port, host);
|
|
79
|
+
return s;
|
|
80
|
+
}
|
|
81
|
+
// Install a panic emitter that publishes a Critical+NONCE_TERMINAL
|
|
82
|
+
// frame over plaintext UDP on any terminating event.
|
|
83
|
+
export function installSignalHandlerUdp(host, port) {
|
|
84
|
+
let sock;
|
|
85
|
+
try {
|
|
86
|
+
sock = createConnectedSocket(host, port);
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
throw new PanicInstallError("SocketBind", err.message);
|
|
90
|
+
}
|
|
91
|
+
const frame = buildCriticalFrame();
|
|
92
|
+
arm(() => {
|
|
93
|
+
try {
|
|
94
|
+
sock.send(frame);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Best effort.
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
// Install a panic emitter that publishes a Critical+NONCE_TERMINAL
|
|
102
|
+
// frame over ChaCha20-Poly1305 AEAD UDP. Fail-closed entropy posture:
|
|
103
|
+
// `crypto.randomBytes(16)` is invoked once at install time; if it
|
|
104
|
+
// throws, `PanicInstallError(kind="EntropyUnavailable")` propagates
|
|
105
|
+
// and no hook is registered.
|
|
106
|
+
export function installSignalHandlerSecureUdp(host, port, key) {
|
|
107
|
+
if (key.length !== KEY_BYTES) {
|
|
108
|
+
throw new RangeError(`key must be ${KEY_BYTES} bytes`);
|
|
109
|
+
}
|
|
110
|
+
let salt;
|
|
111
|
+
try {
|
|
112
|
+
salt = randomBytes(SESSION_SALT_BYTES);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
throw new PanicInstallError("EntropyUnavailable", err.message);
|
|
116
|
+
}
|
|
117
|
+
let sock;
|
|
118
|
+
try {
|
|
119
|
+
sock = createConnectedSocket(host, port);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
throw new PanicInstallError("SocketBind", err.message);
|
|
123
|
+
}
|
|
124
|
+
// Capture state by reference inside the closure. `installPid` lets
|
|
125
|
+
// the hook detect post-`fork(2)` execution and re-read entropy
|
|
126
|
+
// before the IV is reused.
|
|
127
|
+
const state = {
|
|
128
|
+
salt,
|
|
129
|
+
installPid: process.pid,
|
|
130
|
+
counter: 0,
|
|
131
|
+
};
|
|
132
|
+
const keyCopy = Buffer.from(key);
|
|
133
|
+
arm(() => {
|
|
134
|
+
try {
|
|
135
|
+
if (process.pid !== state.installPid) {
|
|
136
|
+
try {
|
|
137
|
+
state.salt = randomBytes(SESSION_SALT_BYTES);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return; // fail-closed: skip emission rather than reuse stale IV
|
|
141
|
+
}
|
|
142
|
+
state.installPid = process.pid;
|
|
143
|
+
state.counter = 0;
|
|
144
|
+
}
|
|
145
|
+
const ivPrefix = deriveIvPrefix(state.salt, 0).subarray(0, IV_RANDOM_BYTES);
|
|
146
|
+
const counter = state.counter;
|
|
147
|
+
state.counter = (state.counter + 1) >>> 0;
|
|
148
|
+
const plaintext = buildCriticalFrame();
|
|
149
|
+
const wire = encodeShared(keyCopy, ivPrefix, counter, plaintext);
|
|
150
|
+
sock.send(wire);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Hook must never propagate.
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
// Defer/recover-style wrapper. Any throw inside `fn` (sync or async)
|
|
158
|
+
// causes a critical beat to be emitted by whichever installer is
|
|
159
|
+
// already armed, then the original error is re-thrown so the caller's
|
|
160
|
+
// shutdown logic still runs.
|
|
161
|
+
export async function run(fn) {
|
|
162
|
+
try {
|
|
163
|
+
await fn();
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
// Emit by triggering the uncaughtException pathway — re-throws
|
|
167
|
+
// are caught by the armed handler installed via `arm()`. If no
|
|
168
|
+
// handler is installed, the error simply propagates.
|
|
169
|
+
throw err;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=panic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"panic.js","sourceRoot":"","sources":["../src/panic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface BeatTransport {
|
|
2
|
+
send(buf: Buffer): void;
|
|
3
|
+
reconnect(): void;
|
|
4
|
+
close(): void;
|
|
5
|
+
}
|
|
6
|
+
export declare class UdpTransport implements BeatTransport {
|
|
7
|
+
private socket;
|
|
8
|
+
private readonly host;
|
|
9
|
+
private readonly port;
|
|
10
|
+
pendingError: NodeJS.ErrnoException | null;
|
|
11
|
+
constructor(host: string, port: number);
|
|
12
|
+
private openSocket;
|
|
13
|
+
send(buf: Buffer): void;
|
|
14
|
+
reconnect(): void;
|
|
15
|
+
close(): void;
|
|
16
|
+
}
|
|
17
|
+
export type SecureUdpKind = "shared" | "master";
|
|
18
|
+
export declare class SecureUdpTransport implements BeatTransport {
|
|
19
|
+
private socket;
|
|
20
|
+
private readonly host;
|
|
21
|
+
private readonly port;
|
|
22
|
+
private readonly secret;
|
|
23
|
+
private sessionSalt;
|
|
24
|
+
private ivPrefix;
|
|
25
|
+
private prefixIndex;
|
|
26
|
+
private counter;
|
|
27
|
+
pendingError: NodeJS.ErrnoException | null;
|
|
28
|
+
private constructor();
|
|
29
|
+
static shared(host: string, port: number, key: Buffer): SecureUdpTransport;
|
|
30
|
+
static master(host: string, port: number, masterKey: Buffer): SecureUdpTransport;
|
|
31
|
+
private openSocket;
|
|
32
|
+
__setCounterForTest(v: number): void;
|
|
33
|
+
__getCounterForTest(): number;
|
|
34
|
+
__getPrefixIndexForTest(): number;
|
|
35
|
+
__getIvPrefixForTest(): Buffer;
|
|
36
|
+
send(buf: Buffer): void;
|
|
37
|
+
reconnect(): void;
|
|
38
|
+
close(): void;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,aAAa;IAO5B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,IAAI,IAAI,CAAC;IAClB,KAAK,IAAI,IAAI,CAAC;CACf;AAeD,qBAAa,YAAa,YAAW,aAAa;IAChD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,YAAY,EAAE,MAAM,CAAC,cAAc,GAAG,IAAI,CAAQ;gBAEtC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAMtC,OAAO,CAAC,UAAU;IAYlB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAoBvB,SAAS,IAAI,IAAI;IAUjB,KAAK,IAAI,IAAI;CAOd;AAID,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAYhD,qBAAa,kBAAmB,YAAW,aAAa;IACtD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAS;IACxB,YAAY,EAAE,MAAM,CAAC,cAAc,GAAG,IAAI,CAAQ;IAElD,OAAO;IAWP,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,kBAAkB;IAO1E,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,kBAAkB;IAUhF,OAAO,CAAC,UAAU;IAWlB,mBAAmB,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAGpC,mBAAmB,IAAI,MAAM;IAG7B,uBAAuB,IAAI,MAAM;IAGjC,oBAAoB,IAAI,MAAM;IAI9B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IA8CvB,SAAS,IAAI,IAAI;IAmBjB,KAAK,IAAI,IAAI;CAOd"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// Beat transport abstractions — UDP and ChaCha20-Poly1305-AEAD UDP.
|
|
2
|
+
//
|
|
3
|
+
// UDS (AF_UNIX/SOCK_DGRAM) is intentionally NOT implemented in this
|
|
4
|
+
// release. Node's stdlib `dgram` module accepts only `"udp4"` and
|
|
5
|
+
// `"udp6"` socket types; the `"unix_dgram"` type that exists in some
|
|
6
|
+
// platform sockets APIs is rejected with `ERR_SOCKET_BAD_TYPE`. Adding
|
|
7
|
+
// UDS would require a native addon (breaking the zero-dep posture) or
|
|
8
|
+
// pulling in `node:net.Socket(fd)` over a manually opened AF_UNIX FD
|
|
9
|
+
// (no portable JS path exists). For same-host deployments, use
|
|
10
|
+
// `Varta.connectUdp("127.0.0.1", port)` — loopback is the same
|
|
11
|
+
// security domain as UDS on a single host.
|
|
12
|
+
import { randomBytes } from "node:crypto";
|
|
13
|
+
import { createSocket } from "node:dgram";
|
|
14
|
+
import { encodeMaster, encodeShared, IV_RANDOM_BYTES, SESSION_SALT_BYTES, deriveIvPrefix, } from "./vlp_secure.js";
|
|
15
|
+
// Common helper: drain a libuv async error captured on a previous
|
|
16
|
+
// `socket.send` callback. Returns the error if one is queued and
|
|
17
|
+
// clears the slot, or `null` if the slot is empty.
|
|
18
|
+
function takePendingError(holder) {
|
|
19
|
+
const e = holder.pendingError;
|
|
20
|
+
holder.pendingError = null;
|
|
21
|
+
return e;
|
|
22
|
+
}
|
|
23
|
+
// ─── Plaintext UDP ──────────────────────────────────────────────
|
|
24
|
+
export class UdpTransport {
|
|
25
|
+
socket;
|
|
26
|
+
host;
|
|
27
|
+
port;
|
|
28
|
+
pendingError = null;
|
|
29
|
+
constructor(host, port) {
|
|
30
|
+
this.host = host;
|
|
31
|
+
this.port = port;
|
|
32
|
+
this.socket = this.openSocket();
|
|
33
|
+
}
|
|
34
|
+
openSocket() {
|
|
35
|
+
const family = this.host.includes(":") ? "udp6" : "udp4";
|
|
36
|
+
const s = createSocket(family);
|
|
37
|
+
// Swallow `error` events; they would otherwise crash the process.
|
|
38
|
+
// The pending-error slot captures them for the next `send` call.
|
|
39
|
+
s.on("error", (err) => {
|
|
40
|
+
this.pendingError = err;
|
|
41
|
+
});
|
|
42
|
+
s.unref();
|
|
43
|
+
return s;
|
|
44
|
+
}
|
|
45
|
+
send(buf) {
|
|
46
|
+
const queued = takePendingError(this);
|
|
47
|
+
if (queued !== null)
|
|
48
|
+
throw queued;
|
|
49
|
+
// Use addressed sends instead of connected sends — `socket.connect`
|
|
50
|
+
// is async and a `send()` issued before its `connect` event lands
|
|
51
|
+
// fails with `ERR_SOCKET_DGRAM_NOT_CONNECTED`. The libuv callback
|
|
52
|
+
// path still surfaces kernel-level send errors via `pendingError`.
|
|
53
|
+
//
|
|
54
|
+
// Copy the caller's scratch buffer before handing off to libuv:
|
|
55
|
+
// `dgram.Socket.send` does NOT internally copy, and the agent
|
|
56
|
+
// reuses a single 32-byte buffer across beats, so a non-copy
|
|
57
|
+
// would let later beats overwrite earlier in-flight datagrams.
|
|
58
|
+
const owned = Buffer.from(buf);
|
|
59
|
+
this.socket.send(owned, this.port, this.host, (err) => {
|
|
60
|
+
if (err !== null && err !== undefined) {
|
|
61
|
+
this.pendingError = err;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
reconnect() {
|
|
66
|
+
try {
|
|
67
|
+
this.socket.close();
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Already closed — fine.
|
|
71
|
+
}
|
|
72
|
+
this.pendingError = null;
|
|
73
|
+
this.socket = this.openSocket();
|
|
74
|
+
}
|
|
75
|
+
close() {
|
|
76
|
+
try {
|
|
77
|
+
this.socket.close();
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Already closed.
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export class SecureUdpTransport {
|
|
85
|
+
socket;
|
|
86
|
+
host;
|
|
87
|
+
port;
|
|
88
|
+
secret;
|
|
89
|
+
sessionSalt;
|
|
90
|
+
ivPrefix;
|
|
91
|
+
prefixIndex;
|
|
92
|
+
counter;
|
|
93
|
+
pendingError = null;
|
|
94
|
+
constructor(host, port, secret) {
|
|
95
|
+
this.host = host;
|
|
96
|
+
this.port = port;
|
|
97
|
+
this.secret = secret;
|
|
98
|
+
this.sessionSalt = randomBytes(SESSION_SALT_BYTES);
|
|
99
|
+
this.prefixIndex = 0;
|
|
100
|
+
this.counter = 0;
|
|
101
|
+
this.ivPrefix = deriveIvPrefix(this.sessionSalt, this.prefixIndex);
|
|
102
|
+
this.socket = this.openSocket();
|
|
103
|
+
}
|
|
104
|
+
static shared(host, port, key) {
|
|
105
|
+
if (key.length !== 32) {
|
|
106
|
+
throw new RangeError("secure-UDP shared key must be 32 bytes");
|
|
107
|
+
}
|
|
108
|
+
return new SecureUdpTransport(host, port, { kind: "shared", key: Buffer.from(key) });
|
|
109
|
+
}
|
|
110
|
+
static master(host, port, masterKey) {
|
|
111
|
+
if (masterKey.length !== 32) {
|
|
112
|
+
throw new RangeError("secure-UDP master key must be 32 bytes");
|
|
113
|
+
}
|
|
114
|
+
return new SecureUdpTransport(host, port, {
|
|
115
|
+
kind: "master",
|
|
116
|
+
masterKey: Buffer.from(masterKey),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
openSocket() {
|
|
120
|
+
const family = this.host.includes(":") ? "udp6" : "udp4";
|
|
121
|
+
const s = createSocket(family);
|
|
122
|
+
s.on("error", (err) => {
|
|
123
|
+
this.pendingError = err;
|
|
124
|
+
});
|
|
125
|
+
s.unref();
|
|
126
|
+
return s;
|
|
127
|
+
}
|
|
128
|
+
// Test hooks (matching Python `_set_*_for_test` and Go `*ForTest`).
|
|
129
|
+
__setCounterForTest(v) {
|
|
130
|
+
this.counter = v >>> 0;
|
|
131
|
+
}
|
|
132
|
+
__getCounterForTest() {
|
|
133
|
+
return this.counter;
|
|
134
|
+
}
|
|
135
|
+
__getPrefixIndexForTest() {
|
|
136
|
+
return this.prefixIndex;
|
|
137
|
+
}
|
|
138
|
+
__getIvPrefixForTest() {
|
|
139
|
+
return Buffer.from(this.ivPrefix);
|
|
140
|
+
}
|
|
141
|
+
send(buf) {
|
|
142
|
+
const queued = takePendingError(this);
|
|
143
|
+
if (queued !== null)
|
|
144
|
+
throw queued;
|
|
145
|
+
// The IV (8-byte prefix + 4-byte LE counter) is reserved
|
|
146
|
+
// synchronously here. The Rust client uses commit-on-success
|
|
147
|
+
// because its `send(2)` is synchronous and a `WouldBlock` lets it
|
|
148
|
+
// safely re-use the nonce on retry. Node's `dgram.send` queues
|
|
149
|
+
// datagrams via libuv async, so multiple concurrent calls would
|
|
150
|
+
// all see the same proposed counter and encrypt distinct
|
|
151
|
+
// plaintexts under the same nonce — that is the classic
|
|
152
|
+
// ChaCha20-Poly1305 nonce-reuse footgun. We reserve and advance
|
|
153
|
+
// synchronously here so every queued frame carries a unique IV;
|
|
154
|
+
// a callback-reported `pendingError` simply burns one nonce
|
|
155
|
+
// slot, which is harmless.
|
|
156
|
+
const ivRandom = Buffer.alloc(IV_RANDOM_BYTES);
|
|
157
|
+
this.ivPrefix.copy(ivRandom, 0, 0, IV_RANDOM_BYTES);
|
|
158
|
+
const counter = this.counter;
|
|
159
|
+
if (this.counter === 0xffffffff) {
|
|
160
|
+
this.prefixIndex = (this.prefixIndex + 1) >>> 0;
|
|
161
|
+
this.ivPrefix = deriveIvPrefix(this.sessionSalt, this.prefixIndex);
|
|
162
|
+
this.counter = 0;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
this.counter = (this.counter + 1) >>> 0;
|
|
166
|
+
}
|
|
167
|
+
let wire;
|
|
168
|
+
if (this.secret.kind === "shared") {
|
|
169
|
+
wire = encodeShared(this.secret.key, ivRandom, counter, buf);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
wire = encodeMaster(this.secret.masterKey, process.pid >>> 0, ivRandom, counter, buf);
|
|
173
|
+
}
|
|
174
|
+
this.socket.send(wire, this.port, this.host, (err) => {
|
|
175
|
+
if (err !== null && err !== undefined) {
|
|
176
|
+
this.pendingError = err;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
reconnect() {
|
|
181
|
+
try {
|
|
182
|
+
this.socket.close();
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// Already closed.
|
|
186
|
+
}
|
|
187
|
+
this.pendingError = null;
|
|
188
|
+
// Prepare a fresh session in locals; commit at the end without `?`.
|
|
189
|
+
const newSalt = randomBytes(SESSION_SALT_BYTES);
|
|
190
|
+
const newPrefixIndex = 0;
|
|
191
|
+
const newIvPrefix = deriveIvPrefix(newSalt, newPrefixIndex);
|
|
192
|
+
const newSocket = this.openSocket();
|
|
193
|
+
this.sessionSalt = newSalt;
|
|
194
|
+
this.prefixIndex = newPrefixIndex;
|
|
195
|
+
this.counter = 0;
|
|
196
|
+
this.ivPrefix = newIvPrefix;
|
|
197
|
+
this.socket = newSocket;
|
|
198
|
+
}
|
|
199
|
+
close() {
|
|
200
|
+
try {
|
|
201
|
+
this.socket.close();
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Already closed.
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
|
package/dist/vlp.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare const MAGIC: Buffer;
|
|
2
|
+
export declare const VERSION: number;
|
|
3
|
+
export declare const NONCE_TERMINAL: bigint;
|
|
4
|
+
export declare const TIMESTAMP_INVALID: bigint;
|
|
5
|
+
export declare const FRAME_BYTES: number;
|
|
6
|
+
export declare enum Status {
|
|
7
|
+
Ok = 0,
|
|
8
|
+
Degraded = 1,
|
|
9
|
+
Critical = 2,
|
|
10
|
+
Stall = 3
|
|
11
|
+
}
|
|
12
|
+
export type StatusLike = Status | number | string;
|
|
13
|
+
export type DecodeErrorKind = "BadMagic" | "BadVersion" | "BadCrc" | "BadStatus" | "StallOnWire" | "BadPid" | "BadTimestamp" | "BadNonce";
|
|
14
|
+
export declare class DecodeError extends Error {
|
|
15
|
+
readonly kind: DecodeErrorKind;
|
|
16
|
+
constructor(kind: DecodeErrorKind, detail?: string);
|
|
17
|
+
}
|
|
18
|
+
export declare function crc32c(data: Buffer | Uint8Array): number;
|
|
19
|
+
export interface Frame {
|
|
20
|
+
status: Status;
|
|
21
|
+
pid: number;
|
|
22
|
+
timestamp: bigint;
|
|
23
|
+
nonce: bigint;
|
|
24
|
+
payload: number;
|
|
25
|
+
}
|
|
26
|
+
export declare function encode(status: StatusLike, pid: number, timestamp: bigint, nonce: bigint, payload: number): Buffer;
|
|
27
|
+
export declare function encodeInto(buf: Buffer, status: StatusLike, pid: number, timestamp: bigint, nonce: bigint, payload: number): void;
|
|
28
|
+
export declare function decode(buf: Buffer | Uint8Array): Frame;
|
|
29
|
+
export declare function decodeFrame(buf: Buffer | Uint8Array): Frame;
|
|
30
|
+
//# sourceMappingURL=vlp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vlp.d.ts","sourceRoot":"","sources":["../src/vlp.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,KAAK,EAAE,MAAkC,CAAC;AACvD,eAAO,MAAM,OAAO,EAAE,MAAa,CAAC;AACpC,eAAO,MAAM,cAAc,EAAE,MAA4B,CAAC;AAC1D,eAAO,MAAM,iBAAiB,EAAE,MAA4B,CAAC;AAC7D,eAAO,MAAM,WAAW,EAAE,MAAW,CAAC;AAKtC,oBAAY,MAAM;IAChB,EAAE,IAAI;IACN,QAAQ,IAAI;IACZ,QAAQ,IAAI;IACZ,KAAK,IAAI;CACV;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAYlD,MAAM,MAAM,eAAe,GACvB,UAAU,GACV,YAAY,GACZ,QAAQ,GACR,WAAW,GACX,aAAa,GACb,QAAQ,GACR,cAAc,GACd,UAAU,CAAC;AAEf,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;gBACnB,IAAI,EAAE,eAAe,EAAE,MAAM,SAAK;CAK/C;AAoBD,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAOxD;AAID,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAqBD,wBAAgB,MAAM,CACpB,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GACd,MAAM,CAIR;AAGD,wBAAgB,UAAU,CACxB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GACd,IAAI,CAeN;AAQD,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,KAAK,CAmEtD;AAID,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,KAAK,CAE3D"}
|