mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-27 18:57:09 +00:00
feat(mesh): bidirectional routing via tcp_open_reverse and central relay (#1003)
* feat(mesh): bidirectional routing via tcp_open_reverse and central relay
Phase B of the mesh redesign. Adds the reverse-direction protocol so a
pilot's MeshForwarder can route cross-node traffic to central or to
another pilot. Central relays pilot-to-pilot streams transparently;
pilots keep their existing single outbound WS to central.
Protocol additions (backend/src/pilot/protocol.ts):
- TcpOpenReverseFrame { s, targetNodeId, stack, service, port } sent
agent to primary. Reuses existing tcp_open_ack, tcp_close, and
TcpData binary frames for the response and byte plane.
- AGENT_REVERSE_ID_BASE = 0x40000001 splits the 32-bit id space so
agent-allocated reverse stream ids never collide with primary-
allocated forward ids on the same tunnel.
- StreamIdAllocator now wraps to its configured start (not always 1)
so an allocator parameterized with the agent base stays in the
agent half across the wrap.
Agent (backend/src/pilot/agent.ts):
- New ReverseTcpStreamHandle exported class, EventEmitter facade
matching PilotTunnelBridge.TcpStream's surface (write, end,
destroy plus open, data, error, close events).
- Public openMeshTcpStream(target) allocates a reverse id, sends
tcp_open_reverse, returns the handle.
- onTcpOpenAckReverse handles inbound ack, dispatches open or
error+close to the matching handle.
- TcpData and tcp_close binary/JSON paths route ids in the reverse
range to reverseTcpStreams; existing primary-allocated paths
unchanged.
- streamCount, cleanupAfterDisconnect, onStreamIdle include reverse
streams. The allocator is reset to a fresh base on disconnect so
long-lived agents that reconnect many times do not drift up the id
space.
- startPilotAgent registers the agent as MeshService's reverse
dialer via lazy import.
Bridge (backend/src/services/PilotTunnelBridge.ts):
- Two new StreamState kinds: reverse_local (target = central, dial
Dockerode container IP) and reverse_relay (target = another pilot,
open a forward TcpStream on the target pilot's bridge).
- handleTcpOpenReverse validates the agent-id range and stream cap,
then dispatches to acceptReverseLocal or acceptReverseRelay via
lazy imports of MeshService, NodeRegistry, and PilotTunnelManager
(avoids module cycles).
- acceptReverseLocal calls MeshService.resolveContainerIp (now
public), opens net.createConnection, splices bytes through the
tunnel. Pre-connect error handler is removed inside connect to
avoid double-firing with the mid-stream error path.
- acceptReverseRelay opens a forward TcpStream on the target
bridge, splices bytes between the two tunnels.
- Existing tcp_close and TcpData handlers extended for the new
state kinds. teardownStream extended.
MeshService (backend/src/services/MeshService.ts):
- resolveContainerIp made public so the bridge's local-target path
can dial the same shape.
- New reverseDialer field plus setReverseDialer setter. Pilot mode
registers the agent at boot; central mode leaves it null.
- New private dialMeshTcpStream dispatcher: pilot side uses the
reverse dialer, central side uses PilotTunnelManager.getBridge.
- openCrossNode refactored to call the dispatcher and use the
active-stream record's id for activity logging.
- New exported MeshTcpStreamLike interface that both
PilotTunnelBridge.TcpStream and ReverseTcpStreamHandle satisfy
structurally; ReverseMeshDialer is the contract for the setter.
Tests (3 files, 16 new cases, 172 total pass):
- pilot-protocol-tcp.test.ts: tcp_open_reverse round-trip; agent
base invariant.
- pilot-agent-reverse-stream.test.ts: openMeshTcpStream allocation,
ws-not-open, frame shape, write encoding, end emits tcp_close,
ack-success, ack-failure, low-id ack ignored, inbound TcpData
routing.
- pilot-bridge-reverse.test.ts: id-range validation, no-target
failure, successful local dial against a real upstream server.
Together with PR #1000 (Phase A) and PR #1001 (deps), Phase B
completes the central-pilot-pilot mesh routing matrix. Phase C
(mesh over proxy-mode remotes) is the planned follow-up.
* test(pilot): drop unused encodeJsonFrame import
Lint failed on pilot-agent-reverse-stream.test.ts after the test
changed from constructing tcp_open_reverse frames inline to driving
the agent's frame-dispatch path with synthetic objects. The import
is no longer referenced; ESLint's no-unused-vars rule rejected it.
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* Phase B: PilotAgent's outbound reverse mesh stream. The agent's
|
||||
* MeshForwarder hands off cross-node connections to PilotAgent via
|
||||
* `openMeshTcpStream`, which sends a `tcp_open_reverse` frame and returns
|
||||
* a stream handle. The test exercises the wire shape and the lifecycle
|
||||
* dispatchers (open / data / close) without needing a real WebSocket or
|
||||
* the full Sencho boot.
|
||||
*/
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
import { setupTestDb, cleanupTestDb } from './helpers/setupTestDb';
|
||||
import {
|
||||
AGENT_REVERSE_ID_BASE,
|
||||
BinaryFrameType,
|
||||
decodeBinaryFrame,
|
||||
decodeJsonFrame,
|
||||
encodeBinaryFrame,
|
||||
} from '../pilot/protocol';
|
||||
|
||||
let tmpDir: string;
|
||||
let PilotAgent: typeof import('../pilot/agent').PilotAgent;
|
||||
let ReverseTcpStreamHandle: typeof import('../pilot/agent').ReverseTcpStreamHandle;
|
||||
|
||||
interface CapturedSend {
|
||||
raw: string | Buffer;
|
||||
binary: boolean;
|
||||
}
|
||||
|
||||
function makeMockAgent(): { agent: import('../pilot/agent').PilotAgent; sent: CapturedSend[]; mockWs: { readyState: number; send: (data: unknown, opts?: { binary?: boolean }) => void } } {
|
||||
const sent: CapturedSend[] = [];
|
||||
const mockWs = {
|
||||
readyState: 1, // WebSocket.OPEN
|
||||
send(data: unknown, opts?: { binary?: boolean }) {
|
||||
const isBinary = opts?.binary === true;
|
||||
sent.push({
|
||||
raw: isBinary ? (Buffer.isBuffer(data) ? data : Buffer.from(data as Uint8Array)) : String(data),
|
||||
binary: isBinary,
|
||||
});
|
||||
},
|
||||
};
|
||||
const agent = new PilotAgent({
|
||||
primaryUrl: 'http://primary.invalid',
|
||||
loopbackPort: 1,
|
||||
initialToken: 'irrelevant',
|
||||
enrolling: false,
|
||||
});
|
||||
// Inject the mock ws into the private slot. The agent's
|
||||
// openMeshTcpStream checks readyState and calls send(); the mock
|
||||
// captures both for assertions without needing real connect/handshake.
|
||||
(agent as unknown as { ws: typeof mockWs }).ws = mockWs;
|
||||
return { agent, sent, mockWs };
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = await setupTestDb();
|
||||
({ PilotAgent, ReverseTcpStreamHandle } = await import('../pilot/agent'));
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
cleanupTestDb(tmpDir);
|
||||
});
|
||||
|
||||
describe('PilotAgent.openMeshTcpStream (Phase B)', () => {
|
||||
it('allocates an id in the agent-reverse range and emits a tcp_open_reverse frame with the target', () => {
|
||||
const { agent, sent } = makeMockAgent();
|
||||
const handle = agent.openMeshTcpStream({
|
||||
nodeId: 12,
|
||||
stack: 'api',
|
||||
service: 'db',
|
||||
port: 5432,
|
||||
});
|
||||
expect(handle).toBeInstanceOf(ReverseTcpStreamHandle);
|
||||
expect(handle!.streamId).toBeGreaterThanOrEqual(AGENT_REVERSE_ID_BASE);
|
||||
|
||||
const textFrames = sent.filter((s) => !s.binary);
|
||||
expect(textFrames.length).toBe(1);
|
||||
const decoded = decodeJsonFrame(textFrames[0].raw as string);
|
||||
expect(decoded.t).toBe('tcp_open_reverse');
|
||||
if (decoded.t !== 'tcp_open_reverse') throw new Error('narrowing');
|
||||
expect(decoded.s).toBe(handle!.streamId);
|
||||
expect(decoded.targetNodeId).toBe(12);
|
||||
expect(decoded.stack).toBe('api');
|
||||
expect(decoded.service).toBe('db');
|
||||
expect(decoded.port).toBe(5432);
|
||||
});
|
||||
|
||||
it('returns null when the tunnel is not OPEN', () => {
|
||||
const { agent, mockWs } = makeMockAgent();
|
||||
mockWs.readyState = 0; // CONNECTING
|
||||
const handle = agent.openMeshTcpStream({ nodeId: 1, stack: 'a', service: 'b', port: 1 });
|
||||
expect(handle).toBeNull();
|
||||
});
|
||||
|
||||
it('handle.write encodes a TcpData binary frame with the agent-allocated streamId', () => {
|
||||
const { agent, sent } = makeMockAgent();
|
||||
const handle = agent.openMeshTcpStream({ nodeId: 2, stack: 's', service: 'svc', port: 80 });
|
||||
if (!handle) throw new Error('handle should exist');
|
||||
sent.length = 0; // clear the open frame
|
||||
|
||||
const ok = handle.write(Buffer.from('hello'));
|
||||
expect(ok).toBe(true);
|
||||
expect(sent.length).toBe(1);
|
||||
expect(sent[0].binary).toBe(true);
|
||||
const decoded = decodeBinaryFrame(sent[0].raw as Buffer);
|
||||
expect(decoded.type).toBe(BinaryFrameType.TcpData);
|
||||
expect(decoded.streamId).toBe(handle.streamId);
|
||||
expect(decoded.payload.toString()).toBe('hello');
|
||||
});
|
||||
|
||||
it('handle.end sends a tcp_close JSON frame and removes the stream from the agent map', () => {
|
||||
const { agent, sent } = makeMockAgent();
|
||||
const handle = agent.openMeshTcpStream({ nodeId: 3, stack: 's', service: 'svc', port: 80 });
|
||||
if (!handle) throw new Error('handle should exist');
|
||||
sent.length = 0;
|
||||
|
||||
handle.end();
|
||||
|
||||
const text = sent.find((s) => !s.binary);
|
||||
expect(text).toBeDefined();
|
||||
const decoded = decodeJsonFrame(text!.raw as string);
|
||||
expect(decoded.t).toBe('tcp_close');
|
||||
if (decoded.t !== 'tcp_close') throw new Error('narrowing');
|
||||
expect(decoded.s).toBe(handle.streamId);
|
||||
|
||||
const internalMap = (agent as unknown as { reverseTcpStreams: Map<number, unknown> }).reverseTcpStreams;
|
||||
expect(internalMap.has(handle.streamId)).toBe(false);
|
||||
});
|
||||
|
||||
it('inbound tcp_open_ack {ok: true} fires the open event on the matching handle', () => {
|
||||
const { agent, mockWs } = makeMockAgent();
|
||||
const handle = agent.openMeshTcpStream({ nodeId: 4, stack: 's', service: 'svc', port: 80 });
|
||||
if (!handle) throw new Error('handle should exist');
|
||||
|
||||
let opened = false;
|
||||
handle.on('open', () => { opened = true; });
|
||||
|
||||
// Simulate an inbound ack frame: invoke the agent's private json-
|
||||
// dispatch path with a synthetic frame.
|
||||
const onTcpOpenAckReverse = (agent as unknown as { onTcpOpenAckReverse: (frame: unknown) => void }).onTcpOpenAckReverse.bind(agent);
|
||||
onTcpOpenAckReverse({ t: 'tcp_open_ack', s: handle.streamId, ok: true });
|
||||
|
||||
expect(opened).toBe(true);
|
||||
// Sanity: mockWs's readyState wasn't touched.
|
||||
expect(mockWs.readyState).toBe(1);
|
||||
});
|
||||
|
||||
it('inbound tcp_open_ack {ok: false} emits error and close, then drops the handle', () => {
|
||||
const { agent } = makeMockAgent();
|
||||
const handle = agent.openMeshTcpStream({ nodeId: 5, stack: 's', service: 'svc', port: 80 });
|
||||
if (!handle) throw new Error('handle should exist');
|
||||
|
||||
let errMessage: string | undefined;
|
||||
let closed = false;
|
||||
handle.on('error', (err: Error) => { errMessage = err.message; });
|
||||
handle.on('close', () => { closed = true; });
|
||||
|
||||
const onTcpOpenAckReverse = (agent as unknown as { onTcpOpenAckReverse: (frame: unknown) => void }).onTcpOpenAckReverse.bind(agent);
|
||||
onTcpOpenAckReverse({ t: 'tcp_open_ack', s: handle.streamId, ok: false, err: 'unreachable' });
|
||||
|
||||
expect(errMessage).toBe('unreachable');
|
||||
expect(closed).toBe(true);
|
||||
const internalMap = (agent as unknown as { reverseTcpStreams: Map<number, unknown> }).reverseTcpStreams;
|
||||
expect(internalMap.has(handle.streamId)).toBe(false);
|
||||
});
|
||||
|
||||
it('forward-direction tcp_open_ack ids (low half) do not match reverse handles', () => {
|
||||
const { agent } = makeMockAgent();
|
||||
const handle = agent.openMeshTcpStream({ nodeId: 6, stack: 's', service: 'svc', port: 80 });
|
||||
if (!handle) throw new Error('handle should exist');
|
||||
|
||||
let opened = false;
|
||||
handle.on('open', () => { opened = true; });
|
||||
|
||||
// Primary-direction id (< AGENT_REVERSE_ID_BASE) must not bleed into
|
||||
// the reverse map.
|
||||
const onTcpOpenAckReverse = (agent as unknown as { onTcpOpenAckReverse: (frame: unknown) => void }).onTcpOpenAckReverse.bind(agent);
|
||||
onTcpOpenAckReverse({ t: 'tcp_open_ack', s: 5, ok: true });
|
||||
|
||||
expect(opened).toBe(false);
|
||||
});
|
||||
|
||||
it('inbound TcpData binary frame (encoded against the wire) emits data on the handle', () => {
|
||||
const { agent } = makeMockAgent();
|
||||
const handle = agent.openMeshTcpStream({ nodeId: 7, stack: 's', service: 'svc', port: 80 });
|
||||
if (!handle) throw new Error('handle should exist');
|
||||
|
||||
const received: Buffer[] = [];
|
||||
handle.on('data', (chunk: Buffer) => received.push(chunk));
|
||||
|
||||
// Drive the agent's binary-frame handler with a wire-encoded TcpData
|
||||
// frame to also exercise the routing branch added in Phase B.
|
||||
const buf = encodeBinaryFrame(BinaryFrameType.TcpData, handle.streamId, Buffer.from('echo!'));
|
||||
const decoded = decodeBinaryFrame(buf);
|
||||
const handleBinaryFrame = (agent as unknown as { handleBinaryFrame: (frame: unknown) => void }).handleBinaryFrame.bind(agent);
|
||||
handleBinaryFrame(decoded);
|
||||
|
||||
expect(Buffer.concat(received).toString()).toBe('echo!');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* Phase B: PilotTunnelBridge handles `tcp_open_reverse` from the agent.
|
||||
* Two paths:
|
||||
* - target is the central's local node: dial via Dockerode container IP
|
||||
* and splice bytes between the resulting `net.Socket` and the tunnel.
|
||||
* - target is another pilot: open a forward `TcpStream` on the target
|
||||
* pilot's bridge and relay.
|
||||
*
|
||||
* The local-dial path is the common case for small fleets. This file
|
||||
* exercises the validation gate (agent-id-range, stream cap) and the
|
||||
* local-dial outcome by intercepting `MeshService.resolveContainerIp`.
|
||||
*/
|
||||
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
|
||||
import net from 'net';
|
||||
import { EventEmitter } from 'events';
|
||||
import { WebSocket } from 'ws';
|
||||
import { setupTestDb, cleanupTestDb } from './helpers/setupTestDb';
|
||||
import {
|
||||
AGENT_REVERSE_ID_BASE,
|
||||
decodeJsonFrame,
|
||||
encodeJsonFrame,
|
||||
} from '../pilot/protocol';
|
||||
|
||||
let tmpDir: string;
|
||||
let PilotTunnelBridge: typeof import('../services/PilotTunnelBridge').PilotTunnelBridge;
|
||||
let MeshService: typeof import('../services/MeshService').MeshService;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = await setupTestDb();
|
||||
({ PilotTunnelBridge } = await import('../services/PilotTunnelBridge'));
|
||||
({ MeshService } = await import('../services/MeshService'));
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vi.restoreAllMocks();
|
||||
cleanupTestDb(tmpDir);
|
||||
});
|
||||
|
||||
function makeMockTunnelWs(): EventEmitter & {
|
||||
sent: unknown[];
|
||||
readyState: number;
|
||||
bufferedAmount: number;
|
||||
send: (data: unknown) => void;
|
||||
ping: () => void;
|
||||
close: () => void;
|
||||
} {
|
||||
const ws = new EventEmitter() as EventEmitter & {
|
||||
sent: unknown[]; readyState: number; bufferedAmount: number;
|
||||
send: (data: unknown) => void; ping: () => void; close: () => void;
|
||||
};
|
||||
ws.sent = [];
|
||||
ws.readyState = WebSocket.OPEN;
|
||||
ws.bufferedAmount = 0;
|
||||
ws.send = (data: unknown) => { ws.sent.push(data); };
|
||||
ws.ping = () => { /* no-op */ };
|
||||
ws.close = () => { ws.readyState = WebSocket.CLOSED; ws.emit('close'); };
|
||||
return ws;
|
||||
}
|
||||
|
||||
function findAck(ws: { sent: unknown[] }, s: number): { ok: boolean; err?: string } | undefined {
|
||||
for (const item of ws.sent) {
|
||||
if (typeof item !== 'string') continue;
|
||||
try {
|
||||
const f = decodeJsonFrame(item);
|
||||
if (f.t === 'tcp_open_ack' && f.s === s) return { ok: f.ok, err: f.err };
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function waitFor<T>(check: () => T | undefined): Promise<T> {
|
||||
const deadline = Date.now() + 1500;
|
||||
while (Date.now() < deadline) {
|
||||
const v = check();
|
||||
if (v !== undefined) return v;
|
||||
await new Promise((r) => setTimeout(r, 10));
|
||||
}
|
||||
throw new Error('timeout');
|
||||
}
|
||||
|
||||
describe('PilotTunnelBridge handles tcp_open_reverse (Phase B)', () => {
|
||||
it('rejects frames whose s is below the agent-reverse range', async () => {
|
||||
const mockWs = makeMockTunnelWs();
|
||||
const bridge = new PilotTunnelBridge(1, mockWs as unknown as WebSocket);
|
||||
await bridge.start();
|
||||
|
||||
// Drive the tunnel-message handler with a low-range id; the
|
||||
// validation gate must reject and emit a tcp_open_ack {ok: false}.
|
||||
mockWs.emit('message', encodeJsonFrame({
|
||||
t: 'tcp_open_reverse', s: 5,
|
||||
targetNodeId: 1, stack: 's', service: 'svc', port: 80,
|
||||
}), false);
|
||||
|
||||
const ack = await waitFor(() => findAck(mockWs, 5));
|
||||
expect(ack.ok).toBe(false);
|
||||
expect(ack.err).toBe('agent_error');
|
||||
|
||||
bridge.close();
|
||||
});
|
||||
|
||||
it('local target with no Dockerode resolution returns ok:false err:no_target', async () => {
|
||||
const mockWs = makeMockTunnelWs();
|
||||
const bridge = new PilotTunnelBridge(1, mockWs as unknown as WebSocket);
|
||||
await bridge.start();
|
||||
|
||||
// The default node id from the test DB. Force resolveContainerIp to
|
||||
// return null to simulate a stack that does not exist locally.
|
||||
const localNodeId = (await import('../services/NodeRegistry')).NodeRegistry.getInstance().getDefaultNodeId();
|
||||
const spy = vi.spyOn(MeshService.getInstance(), 'resolveContainerIp').mockResolvedValue(null);
|
||||
|
||||
const s = AGENT_REVERSE_ID_BASE + 1;
|
||||
mockWs.emit('message', encodeJsonFrame({
|
||||
t: 'tcp_open_reverse', s,
|
||||
targetNodeId: localNodeId, stack: 'missing', service: 'svc', port: 80,
|
||||
}), false);
|
||||
|
||||
const ack = await waitFor(() => findAck(mockWs, s));
|
||||
expect(ack.ok).toBe(false);
|
||||
expect(ack.err).toBe('no_target');
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
|
||||
bridge.close();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('local target with a working dial sends ok:true and registers a reverse stream', async () => {
|
||||
const mockWs = makeMockTunnelWs();
|
||||
const bridge = new PilotTunnelBridge(1, mockWs as unknown as WebSocket);
|
||||
await bridge.start();
|
||||
|
||||
// Spin up a real local server the bridge will dial as the target.
|
||||
const upstream = net.createServer((socket) => {
|
||||
socket.write('hello-upstream');
|
||||
});
|
||||
await new Promise<void>((resolve) => upstream.listen(0, '127.0.0.1', () => resolve()));
|
||||
const addr = upstream.address();
|
||||
if (!addr || typeof addr === 'string') throw new Error('no address');
|
||||
const upstreamPort = addr.port;
|
||||
|
||||
const localNodeId = (await import('../services/NodeRegistry')).NodeRegistry.getInstance().getDefaultNodeId();
|
||||
vi.spyOn(MeshService.getInstance(), 'resolveContainerIp').mockResolvedValue('127.0.0.1');
|
||||
|
||||
const s = AGENT_REVERSE_ID_BASE + 2;
|
||||
mockWs.emit('message', encodeJsonFrame({
|
||||
t: 'tcp_open_reverse', s,
|
||||
targetNodeId: localNodeId, stack: 'real', service: 'svc', port: upstreamPort,
|
||||
}), false);
|
||||
|
||||
const ack = await waitFor(() => findAck(mockWs, s));
|
||||
expect(ack.ok).toBe(true);
|
||||
|
||||
// Stream is registered on the bridge.
|
||||
const streams = (bridge as unknown as { streams: Map<number, { kind: string }> }).streams;
|
||||
const state = streams.get(s);
|
||||
expect(state?.kind).toBe('reverse_local');
|
||||
|
||||
upstream.close();
|
||||
bridge.close();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
AGENT_REVERSE_ID_BASE,
|
||||
BinaryFrameType,
|
||||
decodeBinaryFrame,
|
||||
decodeJsonFrame,
|
||||
@@ -104,3 +105,33 @@ describe('Mesh TcpData binary frames', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Phase B: tcp_open_reverse', () => {
|
||||
it('roundtrips a tcp_open_reverse frame including targetNodeId', () => {
|
||||
const raw = encodeJsonFrame({
|
||||
t: 'tcp_open_reverse',
|
||||
s: AGENT_REVERSE_ID_BASE + 7,
|
||||
targetNodeId: 12,
|
||||
stack: 'api',
|
||||
service: 'db',
|
||||
port: 5432,
|
||||
});
|
||||
const decoded = decodeJsonFrame(raw);
|
||||
expect(decoded.t).toBe('tcp_open_reverse');
|
||||
if (decoded.t !== 'tcp_open_reverse') throw new Error('narrowing');
|
||||
expect(decoded.s).toBe(AGENT_REVERSE_ID_BASE + 7);
|
||||
expect(decoded.targetNodeId).toBe(12);
|
||||
expect(decoded.stack).toBe('api');
|
||||
expect(decoded.service).toBe('db');
|
||||
expect(decoded.port).toBe(5432);
|
||||
});
|
||||
|
||||
it('AGENT_REVERSE_ID_BASE is in the upper half of the 32-bit space and clears primary allocator collision risk', () => {
|
||||
// Primary allocator wraps at 0x7fffffff. Agent base must be above
|
||||
// that range so primary's id sequence (1..0x7fffffff) and the
|
||||
// agent's reverse sequence never collide on the same tunnel for
|
||||
// the lifetime of the tunnel (well beyond MAX_STREAMS_PER_TUNNEL).
|
||||
expect(AGENT_REVERSE_ID_BASE).toBeGreaterThan(0x40000000);
|
||||
expect(AGENT_REVERSE_ID_BASE).toBeLessThan(0x80000000);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user