Files
orca/src/runtime-relay-server/protocol.ts
T
2026-05-28 13:40:12 -07:00

132 lines
4.6 KiB
TypeScript

import { randomBytes, randomUUID } from 'crypto'
import type { IncomingMessage } from 'http'
export { RUNTIME_RELAY_MAX_FRAME_BYTES } from '../shared/runtime-relay-limits'
export const RUNTIME_RELAY_PROTOCOL_VERSION = 1
export const RUNTIME_RELAY_WS_PATH = '/ws'
export const RUNTIME_RELAY_DEFAULT_PORT = 8787
export const RUNTIME_RELAY_ENROLLMENT_TOKEN_BYTES = 32
export const RUNTIME_RELAY_MAX_PENDING_CLIENTS_PER_SERVER = 64
export const RUNTIME_RELAY_MAX_ACTIVE_DATA_SOCKETS_PER_SERVER = 64
export const RUNTIME_RELAY_MAX_GLOBAL_PENDING_CLIENTS = 1024
export const RUNTIME_RELAY_MAX_GLOBAL_ACTIVE_DATA_SOCKETS = 1024
export const RUNTIME_RELAY_MAX_BOUND_SERVERS = 10_000
export const RUNTIME_RELAY_MAX_EARLY_FRAMES_PER_CONNECTION = 4
export const RUNTIME_RELAY_MAX_EARLY_BYTES_PER_CONNECTION = 4 * 1024 * 1024
export const RUNTIME_RELAY_MAX_TOTAL_EARLY_BYTES_PER_SERVER = 16 * 1024 * 1024
export const RUNTIME_RELAY_MAX_GLOBAL_EARLY_BYTES = 64 * 1024 * 1024
export const RUNTIME_RELAY_MAX_FORWARD_BUFFERED_BYTES = 8 * 1024 * 1024
export const RUNTIME_RELAY_MAX_CONTROL_BUFFERED_BYTES = 1024 * 1024
export const RUNTIME_RELAY_ATTACH_DEADLINE_MS = 15_000
export const RUNTIME_RELAY_HEARTBEAT_INTERVAL_MS = 15_000
export const RUNTIME_RELAY_STALE_SOCKET_TIMEOUT_MS = 45_000
const SERVER_ID_RE = /^[A-Za-z0-9_-]{16,128}$/
const CONNECTION_ID_RE = /^[A-Za-z0-9_-]{16,128}$/
const HOST_TOKEN_HASH_RE = /^[a-f0-9]{64}$/
const MIN_HOST_TOKEN_LENGTH = 32
export type RuntimeRelayRole = 'server' | 'client'
export type RuntimeRelayClientRequest = {
role: 'client'
serverId: string
}
export type RuntimeRelayServerRequest =
| {
role: 'server'
serverId: string
hostToken: string
enrollmentToken: string | null
connectionId: null
}
| {
role: 'server'
serverId: string
hostToken: string
enrollmentToken: string | null
connectionId: string
}
export type RuntimeRelayRequest = RuntimeRelayClientRequest | RuntimeRelayServerRequest
export type RelayControlMessage =
| { type: 'sync'; connectionIds: string[] }
| { type: 'connected'; connectionId: string }
| { type: 'disconnected'; connectionId: string }
export function createRelayConnectionId(): string {
return randomUUID().replace(/-/g, '')
}
export function createRelayEnrollmentToken(): string {
return randomBytes(RUNTIME_RELAY_ENROLLMENT_TOKEN_BYTES).toString('base64url')
}
export function isRuntimeRelayServerId(value: unknown): value is string {
return typeof value === 'string' && SERVER_ID_RE.test(value)
}
export function isRuntimeRelayHostTokenHash(value: unknown): value is string {
return typeof value === 'string' && HOST_TOKEN_HASH_RE.test(value)
}
export function parseRuntimeRelayRequest(
request: IncomingMessage
): { ok: true; value: RuntimeRelayRequest } | { ok: false; statusCode: number; reason: string } {
const host = request.headers.host ?? '127.0.0.1'
let url: URL
try {
url = new URL(request.url ?? '/', `http://${host}`)
} catch {
return { ok: false, statusCode: 400, reason: 'invalid_url' }
}
if (url.pathname !== RUNTIME_RELAY_WS_PATH) {
return { ok: false, statusCode: 404, reason: 'not_found' }
}
if (url.searchParams.get('v') !== String(RUNTIME_RELAY_PROTOCOL_VERSION)) {
return { ok: false, statusCode: 400, reason: 'invalid_version' }
}
const role = url.searchParams.get('role')
const serverId = url.searchParams.get('serverId')
if ((role !== 'server' && role !== 'client') || !isRuntimeRelayServerId(serverId)) {
return { ok: false, statusCode: 400, reason: 'invalid_relay_request' }
}
if (role === 'client') {
return { ok: true, value: { role, serverId } }
}
const hostToken = parseHostAuthorization(request)
if (!hostToken || hostToken.length < MIN_HOST_TOKEN_LENGTH) {
return { ok: false, statusCode: 401, reason: 'invalid_host_token' }
}
const connectionId = url.searchParams.get('connectionId')
if (connectionId !== null && !CONNECTION_ID_RE.test(connectionId)) {
return { ok: false, statusCode: 400, reason: 'invalid_connection_id' }
}
return {
ok: true,
value: {
role,
serverId,
hostToken,
enrollmentToken: parseEnrollmentToken(request),
connectionId
}
}
}
function parseHostAuthorization(request: IncomingMessage): string | null {
const authorization = request.headers.authorization
if (!authorization) {
return null
}
const match = /^Bearer\s+(.+)$/i.exec(authorization.trim())
return match?.[1] ?? null
}
function parseEnrollmentToken(request: IncomingMessage): string | null {
const value = request.headers['x-orca-relay-enrollment']
return typeof value === 'string' && value.trim().length > 0 ? value : null
}