feat(device-plane): add typed B2 service ping transport

This commit is contained in:
Codex
2026-08-12 22:57:09 +03:00
parent bdb85cb4f7
commit 4b73a15765
24 changed files with 1322 additions and 88 deletions
@@ -52,6 +52,7 @@ export function createDeviceGatewayRuntime(options = {}) {
processing: false,
rejected: false,
closed: false,
pendingCommand: null,
};
sessions.set(socket, session);
incrementAddressSessions(remoteAddress);
@@ -103,7 +104,7 @@ export function createDeviceGatewayRuntime(options = {}) {
publicIngress: config.publicIngressEnabled
? "telemetry-ingest"
: "disabled",
commandTransport: "disabled",
commandTransport: config.commandTransport,
sessions: {
active: sessions.size,
accepted: totalAccepted,
@@ -149,7 +150,7 @@ export function createDeviceGatewayRuntime(options = {}) {
totalMessagesAccepted,
totalPackagesAcknowledged,
totalBufferedBytes,
commandTransport: "disabled",
commandTransport: config.commandTransport,
publicIngress: config.publicIngressEnabled
? "telemetry-ingest"
: "disabled",
@@ -186,9 +187,39 @@ export function createDeviceGatewayRuntime(options = {}) {
await writeWithBackpressure(socket, session.adapterSession.buildHeaderAcknowledgement(
Math.floor(new Date(observedAt).getTime() / 1000),
));
if (acceptedDiscovery.commandOffer) {
await dispatchCommandOffer(
socket,
session,
acceptedDiscovery.commandOffer,
);
}
continue;
}
if (session.pendingCommand) {
const result = session.adapterSession.parseTypedCommandResponse(
session.buffer,
session.pendingCommand,
);
if (result.status === "incomplete") return;
if (result.status === "acknowledged") {
const command = session.pendingCommand;
consumeBuffer(session, result.bytesConsumed);
session.pendingCommand = null;
await config.onCommandStatus({
commandRef: command.commandRef,
transportMessageRef: command.transportMessageRef,
lifecycleState: "acknowledged",
resultCode: result.resultCode,
observedAt: config.now().toISOString(),
sessionRef: session.sessionRef,
adapterProfileRef: config.profile.profileRef,
});
continue;
}
}
const parsed = session.adapterSession.parseMessage(session.buffer);
if (parsed.status === "incomplete") return;
const observedAt = config.now().toISOString();
@@ -215,9 +246,12 @@ export function createDeviceGatewayRuntime(options = {}) {
payloadSchemaRef: parsed.payloadSchemaRef,
payload: parsed.payload,
});
const acceptance = normalizeAdapterAcceptance(
await config.onMessage(message),
);
const acceptedMessage = await config.onMessage(message);
const {
commandOffer,
...acceptanceValue
} = acceptedMessage;
const acceptance = normalizeAdapterAcceptance(acceptanceValue);
if (acceptance.idempotencyKey !== message.idempotencyKey) {
throw new TypeError("device_gateway_core_acceptance_mismatch");
}
@@ -228,9 +262,26 @@ export function createDeviceGatewayRuntime(options = {}) {
socket,
session.adapterSession.buildMessageAcknowledgement(parsed),
);
if (commandOffer) {
await dispatchCommandOffer(
socket,
session,
commandOffer,
);
}
}
}
async function dispatchCommandOffer(socket, session, value) {
if (session.pendingCommand) {
throw new Error("device_gateway_command_already_pending");
}
const command = normalizeCommandOffer(value);
const bytes = session.adapterSession.buildTypedCommand(command);
session.pendingCommand = command;
await writeWithBackpressure(socket, bytes);
}
function currentAddressSessions(remoteAddress) {
return sessionsByAddress.get(remoteAddress) || 0;
}
@@ -287,8 +338,21 @@ export function createDeviceGatewayRuntime(options = {}) {
function closeSession(socket, session) {
if (session.closed) return;
session.closed = true;
totalBufferedBytes -= session.buffer.length;
totalBufferedBytes = Math.max(0, totalBufferedBytes - session.buffer.length);
session.buffer = Buffer.alloc(0);
if (session.pendingCommand && config.onCommandStatus) {
const command = session.pendingCommand;
session.pendingCommand = null;
void config.onCommandStatus({
commandRef: command.commandRef,
transportMessageRef: command.transportMessageRef,
lifecycleState: "unknown",
resultCode: "tracker_session_closed",
observedAt: config.now().toISOString(),
sessionRef: session.sessionRef,
adapterProfileRef: config.profile.profileRef,
}).catch(() => undefined);
}
sessions.delete(socket);
decrementAddressSessions(session.remoteAddress);
}
@@ -302,8 +366,9 @@ export function createDeviceGatewayRuntime(options = {}) {
}
function consumeBuffer(session, bytesConsumed) {
session.buffer = session.buffer.subarray(bytesConsumed);
totalBufferedBytes -= bytesConsumed;
const consumed = Math.min(bytesConsumed, session.buffer.length);
session.buffer = session.buffer.subarray(consumed);
totalBufferedBytes = Math.max(0, totalBufferedBytes - consumed);
}
}
@@ -326,11 +391,25 @@ function normalizeConfig(input) {
const registration = listenEnabled
? resolveAdapterRegistration(input.adapterRegistry, input.protocolProfileRef)
: null;
if (registration?.profile?.commandTransport?.status === "typed-service-ping-v1") {
const probe = assertDeviceAdapterSession(registration.adapter.createSession({
profileRef: registration.profile.profileRef,
}));
if (
typeof probe.buildTypedCommand !== "function"
|| typeof probe.parseTypedCommandResponse !== "function"
|| typeof input.onCommandStatus !== "function"
) {
throw new TypeError("device_gateway_typed_command_runtime_required");
}
}
return {
listenEnabled,
publicIngressEnabled,
adapter: registration?.adapter ?? null,
profile: registration?.profile ?? null,
commandTransport:
registration?.profile?.commandTransport?.status ?? "disabled",
edgeRef: listenEnabled
? normalizeOpaqueRef(input.edgeRef, "device_gateway_edge_ref_invalid")
: "edge:disabled",
@@ -412,10 +491,36 @@ function normalizeConfig(input) {
onMessage: typeof input.onMessage === "function"
? input.onMessage
: undefined,
onCommandStatus: typeof input.onCommandStatus === "function"
? input.onCommandStatus
: undefined,
now: typeof input.now === "function" ? input.now : () => new Date(),
};
}
function normalizeCommandOffer(value) {
if (
!value
|| typeof value !== "object"
|| Array.isArray(value)
|| typeof value.commandRef !== "string"
|| !/^command:[0-9a-f-]{36}$/i.test(value.commandRef)
|| value.commandType !== "service.ping"
|| typeof value.accessCode !== "string"
|| !/^\d{6}$/.test(value.accessCode)
|| typeof value.transportMessageRef !== "string"
|| !/^edge-command:[0-9a-f-]{36}$/i.test(value.transportMessageRef)
) {
throw new TypeError("device_gateway_command_offer_invalid");
}
return Object.freeze({
commandRef: value.commandRef.toLowerCase(),
commandType: value.commandType,
accessCode: value.accessCode,
transportMessageRef: value.transportMessageRef.toLowerCase(),
});
}
function resolveAdapterRegistration(registry, profileRef) {
if (!registry || typeof registry.resolveProfile !== "function") {
throw new TypeError("device_gateway_adapter_registry_required");