mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
A control-handshake test that expects a timeout was instead getting
'Unexpected server response: 401' about once in fourteen runs. A slow machine
cannot turn a timeout into a 401 -- that needs a real HTTP response, so the
connection was reaching a different server.
new WebSocketServer({ port: 0 }) binds the wildcard address while the client
dials 127.0.0.1. On macOS those differ, and with SO_REUSEADDR a foreign process
can hold the more specific 127.0.0.1:P and win the connection. Caught live: a
wildcard bind took port 52584, which a running Orca app already held on
loopback, and Orca answered the probe. A listener that checks a token answers
401.
Ten constructions across seven files now pass host: '127.0.0.1', so the
reservation covers the address the client dials and a duplicate bind is refused.
Adds a ratchet, because this is not authors forgetting a convention: all 30+
.listen(0, ...) sites already pass '127.0.0.1', while 7 of 7 ws constructions
did not. ws accepts { port } alone and binds the wildcard silently, so nothing
told them. The guard pins the wildcard count, and pins separately at zero the
option shapes it cannot read -- spreads and variable option objects fail rather
than being exempted, and a recognized-construction floor catches the matcher
going blind, which otherwise reads exactly like a clean tree.
mobile/scripts/mock-server.ts stays on the wildcard deliberately: a phone
reaches it over the LAN.
184 lines
5.5 KiB
TypeScript
184 lines
5.5 KiB
TypeScript
import type { AddressInfo } from 'node:net'
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import { WebSocketServer, type WebSocket } from 'ws'
|
|
import { encodePairingOffer, parsePairingCode, type PairingOffer } from './pairing'
|
|
import {
|
|
decrypt,
|
|
deriveSharedKey,
|
|
encrypt,
|
|
generateKeyPair,
|
|
publicKeyFromBase64,
|
|
publicKeyToBase64
|
|
} from './e2ee-crypto'
|
|
import { RemoteRuntimeRequestConnection } from './remote-runtime-request-connection'
|
|
import { remoteRuntimeClientCapabilities } from './remote-runtime-client-capabilities'
|
|
|
|
type TestServer = {
|
|
wss: WebSocketServer
|
|
pairing: PairingOffer
|
|
requests: unknown[]
|
|
auths: unknown[]
|
|
connectionCount: () => number
|
|
}
|
|
|
|
const servers: WebSocketServer[] = []
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(
|
|
servers.splice(0).map(
|
|
(server) =>
|
|
new Promise<void>((resolve) => {
|
|
for (const client of server.clients) {
|
|
client.close()
|
|
}
|
|
server.close(() => resolve())
|
|
})
|
|
)
|
|
)
|
|
})
|
|
|
|
describe('RemoteRuntimeRequestConnection', () => {
|
|
it('reuses one encrypted WebSocket for multiple one-shot RPCs', async () => {
|
|
const server = await createServer()
|
|
const connection = new RemoteRuntimeRequestConnection(server.pairing)
|
|
|
|
const first = await connection.request('status.get', undefined, 1000)
|
|
const second = await connection.request('terminal.send', { terminal: 't1', text: 'ab' }, 1000)
|
|
|
|
expect(first).toMatchObject({
|
|
ok: true,
|
|
result: { method: 'status.get' },
|
|
_meta: { runtimeId: 'runtime-test' }
|
|
})
|
|
expect(second).toMatchObject({
|
|
ok: true,
|
|
result: { method: 'terminal.send' },
|
|
_meta: { runtimeId: 'runtime-test' }
|
|
})
|
|
expect(server.connectionCount()).toBe(1)
|
|
expect(server.auths).toContainEqual(
|
|
expect.objectContaining({
|
|
clientCapabilities: remoteRuntimeClientCapabilities()
|
|
})
|
|
)
|
|
expect(server.requests).toMatchObject([
|
|
{ method: 'status.get' },
|
|
{ method: 'terminal.send', params: { terminal: 't1', text: 'ab' } }
|
|
])
|
|
|
|
connection.close()
|
|
})
|
|
|
|
it('aborts one request without closing the cached connection', async () => {
|
|
const server = await createServer()
|
|
const connection = new RemoteRuntimeRequestConnection(server.pairing)
|
|
const controller = new AbortController()
|
|
const pending = connection.request('test.hang', undefined, 60_000, controller.signal)
|
|
await vi.waitFor(() =>
|
|
expect(server.requests).toContainEqual(expect.objectContaining({ method: 'test.hang' }))
|
|
)
|
|
|
|
controller.abort()
|
|
await expect(pending).rejects.toMatchObject({ name: 'AbortError' })
|
|
expect(
|
|
(connection as unknown as { pendingRequests: Map<string, unknown> }).pendingRequests.size
|
|
).toBe(0)
|
|
await expect(connection.request('status.get', undefined, 1000)).resolves.toMatchObject({
|
|
ok: true,
|
|
result: { method: 'status.get' }
|
|
})
|
|
expect(server.connectionCount()).toBe(1)
|
|
|
|
connection.close()
|
|
})
|
|
})
|
|
|
|
async function createServer(): Promise<TestServer> {
|
|
const serverKeyPair = generateKeyPair()
|
|
const requests: unknown[] = []
|
|
const auths: unknown[] = []
|
|
let connectionCount = 0
|
|
// host must match the 127.0.0.1 clients dial: a wildcard bind lets a foreign loopback listener claim the port and answer here.
|
|
const wss = new WebSocketServer({ host: '127.0.0.1', port: 0 })
|
|
servers.push(wss)
|
|
|
|
wss.on('connection', (ws) => {
|
|
connectionCount += 1
|
|
let sharedKey: Uint8Array | null = null
|
|
let authenticated = false
|
|
|
|
ws.on('message', (data, isBinary) => {
|
|
if (isBinary) {
|
|
return
|
|
}
|
|
const frame = data.toString()
|
|
if (!sharedKey) {
|
|
const hello = JSON.parse(frame) as { type: string; publicKeyB64: string }
|
|
const clientPublicKey = publicKeyFromBase64(hello.publicKeyB64)
|
|
sharedKey = deriveSharedKey(serverKeyPair.secretKey, clientPublicKey)
|
|
ws.send(JSON.stringify({ type: 'e2ee_ready' }))
|
|
return
|
|
}
|
|
|
|
const plaintext = decrypt(frame, sharedKey)
|
|
if (plaintext === null) {
|
|
return
|
|
}
|
|
if (!authenticated) {
|
|
const auth = JSON.parse(plaintext) as { type: string; deviceToken: string }
|
|
auths.push(auth)
|
|
expect(auth).toEqual({
|
|
type: 'e2ee_auth',
|
|
deviceToken: 'device-token',
|
|
clientCapabilities: remoteRuntimeClientCapabilities()
|
|
})
|
|
authenticated = true
|
|
sendEncrypted(ws, sharedKey, { type: 'e2ee_authenticated' })
|
|
return
|
|
}
|
|
|
|
const request = JSON.parse(plaintext) as {
|
|
id: string
|
|
method: string
|
|
params?: unknown
|
|
}
|
|
requests.push(request)
|
|
if (request.method === 'test.hang') {
|
|
return
|
|
}
|
|
sendEncrypted(ws, sharedKey, {
|
|
id: request.id,
|
|
ok: true,
|
|
result: { method: request.method },
|
|
_meta: { runtimeId: 'runtime-test' }
|
|
})
|
|
})
|
|
})
|
|
|
|
await new Promise<void>((resolve) => wss.once('listening', resolve))
|
|
const address = wss.address() as AddressInfo
|
|
const pairing = parsePairingCode(
|
|
encodePairingOffer({
|
|
v: 2,
|
|
endpoint: `ws://127.0.0.1:${address.port}`,
|
|
deviceToken: 'device-token',
|
|
publicKeyB64: publicKeyToBase64(serverKeyPair.publicKey)
|
|
})
|
|
)
|
|
if (!pairing) {
|
|
throw new Error('Failed to create test pairing')
|
|
}
|
|
|
|
return {
|
|
wss,
|
|
pairing,
|
|
requests,
|
|
auths,
|
|
connectionCount: () => connectionCount
|
|
}
|
|
}
|
|
|
|
function sendEncrypted(ws: WebSocket, sharedKey: Uint8Array, message: unknown): void {
|
|
ws.send(encrypt(JSON.stringify(message), sharedKey))
|
|
}
|