import { describe, expect, it } from 'vitest' import { REMOTE_RUNTIME_MAX_SUBSCRIPTION_PARAM_BYTES } from './remote-runtime-memory-limits' import { BROWSER_CLIENT_HOST_PAGE_INVENTORY_MAX_BYTES, BROWSER_CLIENT_HOST_PAGE_INVENTORY_IDENTITY_MAX_JSON_BYTES, BROWSER_CLIENT_HOST_PAGE_INVENTORY_PROTOCOL_VERSION, BROWSER_CLIENT_HOST_LEASE_RECONNECT_PROTOCOL_VERSION, BrowserClientHostAttachParams, BrowserClientHostCommandEvent, BrowserClientHostCommandResultAck, BrowserClientHostCommandResultParams, BrowserClientHostEvent, BrowserClientHostReady, BrowserNetworkTunnelAttachParams, BrowserNetworkTunnelEvent } from './browser-client-host-protocol' describe('browser client-host control protocol', () => { it('decodes a bounded host attach and server-issued lease fence', () => { expect( BrowserClientHostAttachParams.parse({ authorityRuntimeId: 'runtime-a', browserHostClientId: 'host-a', hostCapabilities: ['webview'] }) ).toEqual({ authorityRuntimeId: 'runtime-a', browserHostClientId: 'host-a', hostCapabilities: ['webview'] }) expect( BrowserClientHostReady.parse({ type: 'ready', authorityEpoch: 'epoch-a', browserHostGeneration: 2 }) ).toEqual({ type: 'ready', authorityEpoch: 'epoch-a', browserHostGeneration: 2 }) }) it('negotiates page commands independently of the legacy lease stream', () => { expect( BrowserClientHostAttachParams.parse({ authorityRuntimeId: 'runtime-a', browserHostClientId: 'host-a', hostCapabilities: ['webview'], pageCommandProtocolVersion: 1 }) ).toMatchObject({ pageCommandProtocolVersion: 1 }) expect( BrowserClientHostReady.parse({ type: 'ready', authorityEpoch: 'epoch-a', browserHostGeneration: 2, pageCommandProtocolVersion: 1 }) ).toMatchObject({ pageCommandProtocolVersion: 1 }) expect( BrowserClientHostAttachParams.parse({ authorityRuntimeId: 'runtime-a', browserHostClientId: 'host-a', hostCapabilities: ['webview'] }) ).not.toHaveProperty('pageCommandProtocolVersion') }) it('negotiates a complete bounded page inventory independently of commands', () => { const page = { authorityRuntimeId: 'runtime-a', authorityEpoch: 'epoch-old', browserHostClientId: 'host-a', browserHostGeneration: 2, browserPageId: 'page-a', pageHostGeneration: 3, browserProfileId: 'profile-a', executionHostKey: 'native:runtime-a:1', state: 'active' as const, currentUrl: 'https://remote.internal/' } const attach = BrowserClientHostAttachParams.parse({ authorityRuntimeId: 'runtime-a', browserHostClientId: 'host-a', hostCapabilities: ['webview'], pageInventoryProtocolVersion: BROWSER_CLIENT_HOST_PAGE_INVENTORY_PROTOCOL_VERSION, pageInventory: [page] }) expect(attach).toMatchObject({ pageInventoryProtocolVersion: 1, pageInventory: [page] }) expect( BrowserClientHostReady.parse({ type: 'ready', authorityEpoch: 'epoch-a', browserHostGeneration: 4, pageInventoryProtocolVersion: 1 }) ).toMatchObject({ pageInventoryProtocolVersion: 1 }) expect(() => BrowserClientHostAttachParams.parse({ authorityRuntimeId: 'runtime-a', browserHostClientId: 'host-a', hostCapabilities: ['webview'], pageInventoryProtocolVersion: 1 }) ).toThrow() expect(() => BrowserClientHostAttachParams.parse({ authorityRuntimeId: 'runtime-a', browserHostClientId: 'host-a', hostCapabilities: ['webview'], pageInventory: [page] }) ).toThrow() expect(() => BrowserClientHostAttachParams.parse({ authorityRuntimeId: 'runtime-a', browserHostClientId: 'host-a', hostCapabilities: ['webview'], pageInventoryProtocolVersion: 1, pageInventory: [page, page] }) ).toThrow() expect(() => BrowserClientHostAttachParams.parse({ authorityRuntimeId: 'runtime-a', browserHostClientId: 'host-a', hostCapabilities: ['webview'], pageInventoryProtocolVersion: 1, pageInventory: Array.from({ length: 257 }, (_, index) => ({ ...page, browserPageId: `page-${index}` })) }) ).toThrow() expect(() => BrowserClientHostAttachParams.parse({ authorityRuntimeId: 'runtime-a', browserHostClientId: 'host-a', hostCapabilities: ['webview'], pageInventoryProtocolVersion: 1, pageInventory: Array.from({ length: 256 }, (_, index) => ({ ...page, browserPageId: `page-${index}`, currentUrl: `https://remote.internal/${'x'.repeat(4096)}` })) }) ).toThrow('Browser page inventory exceeds its byte budget') expect(BROWSER_CLIENT_HOST_PAGE_INVENTORY_MAX_BYTES).toBeLessThan( REMOTE_RUNTIME_MAX_SUBSCRIPTION_PARAM_BYTES ) expect(() => BrowserClientHostAttachParams.parse({ authorityRuntimeId: 'runtime-a', browserHostClientId: 'host-a', hostCapabilities: ['webview'], pageInventoryProtocolVersion: 1, pageInventory: [{ ...page, browserHostClientId: 'host-b' }] }) ).toThrow('Browser page inventory authority does not match the attaching host') }) it('negotiates reconnect grace only with a complete inventory snapshot', () => { expect( BrowserClientHostAttachParams.parse({ authorityRuntimeId: 'runtime-a', browserHostClientId: 'host-a', hostCapabilities: ['webview'], pageInventoryProtocolVersion: 1, pageInventory: [], leaseReconnectProtocolVersion: BROWSER_CLIENT_HOST_LEASE_RECONNECT_PROTOCOL_VERSION }) ).toMatchObject({ leaseReconnectProtocolVersion: 1 }) expect( BrowserClientHostReady.parse({ type: 'ready', authorityEpoch: 'epoch-a', browserHostGeneration: 2, leaseReconnectProtocolVersion: 1 }) ).toMatchObject({ leaseReconnectProtocolVersion: 1 }) expect(() => BrowserClientHostAttachParams.parse({ authorityRuntimeId: 'runtime-a', browserHostClientId: 'host-a', hostCapabilities: ['webview'], leaseReconnectProtocolVersion: 1 }) ).toThrow('Browser host reconnect requires page inventory negotiation') }) it('bounds inventory identities after JSON escaping without narrowing legacy identities', () => { const authorityRuntimeId = maxInventoryIdentity('runtime-') const browserHostClientId = maxInventoryIdentity('host-') const pageInventory = Array.from({ length: 256 }, (_, index) => ({ authorityRuntimeId, authorityEpoch: maxInventoryIdentity('epoch-'), browserHostClientId, browserHostGeneration: 2, browserPageId: maxInventoryIdentity(`page-${index.toString().padStart(3, '0')}-`), pageHostGeneration: 3, browserProfileId: maxInventoryIdentity('profile-'), executionHostKey: maxInventoryIdentity('execution-'), state: 'active' as const })) const firstPage = pageInventory.at(0) if (!firstPage) { throw new Error('expected inventory page') } expect( BrowserClientHostAttachParams.parse({ authorityRuntimeId: 'runtime-new', browserHostClientId, hostCapabilities: ['webview'], pageInventoryProtocolVersion: 1, pageInventory }).pageInventory ).toHaveLength(256) expect( BrowserClientHostAttachParams.parse({ authorityRuntimeId: 'é'.repeat(256), browserHostClientId: 'host-a', hostCapabilities: ['webview'] }) ).toMatchObject({ authorityRuntimeId: 'é'.repeat(256) }) expect(() => BrowserClientHostAttachParams.parse({ authorityRuntimeId: 'runtime-new', browserHostClientId, hostCapabilities: ['webview'], pageInventoryProtocolVersion: 1, pageInventory: [ { ...firstPage, browserProfileId: `${maxInventoryIdentity('profile-')}x` } ] }) ).toThrow('Browser page inventory identity exceeds its JSON byte budget') }) it('binds create-page commands and results to exact bounded authority', () => { const authority = { authorityRuntimeId: 'runtime-a', authorityEpoch: 'epoch-a', browserHostClientId: 'host-a', browserHostGeneration: 2, browserPageId: 'page-a', pageHostGeneration: 3 } const command = { type: 'command' as const, pageCommandProtocolVersion: 1 as const, ...authority, commandSequence: 4, commandId: 'command-a', command: { type: 'createPage' as const, browserProfileId: 'default', executionHostKey: 'native:runtime-a:5' } } expect(BrowserClientHostCommandEvent.parse(command)).toEqual(command) expect(BrowserClientHostEvent.parse(command)).toEqual(command) expect( BrowserClientHostCommandEvent.parse({ ...command, commandSequence: 5, commandId: 'command-b', command: { type: 'navigate', url: 'https://remote.internal/path' } }) ).toMatchObject({ command: { type: 'navigate', url: 'https://remote.internal/path' } }) expect( BrowserClientHostCommandResultParams.parse({ pageCommandProtocolVersion: 1, ...authority, commandSequence: 4, commandId: 'command-a', result: { status: 'completed' } }) ).toMatchObject({ result: { status: 'completed' } }) expect(BrowserClientHostCommandResultAck.parse({ accepted: false })).toEqual({ accepted: false }) expect(() => BrowserClientHostCommandResultAck.parse({ accepted: 'yes' })).toThrow() }) it.each([ ['zero sequence', { commandSequence: 0 }], ['wrong protocol', { pageCommandProtocolVersion: 2 }], ['empty command id', { commandId: '' }], ['unknown command', { command: { type: 'openAnything' } }], ['oversized navigation', { command: { type: 'navigate', url: `https://${'x'.repeat(8192)}` } }], [ 'oversized profile', { command: { type: 'createPage', browserProfileId: 'x'.repeat(257), executionHostKey: 'native:runtime-a:1' } } ] ])('rejects %s before page-command delivery', (_name, override) => { expect(() => BrowserClientHostCommandEvent.parse({ type: 'command', pageCommandProtocolVersion: 1, authorityRuntimeId: 'runtime-a', authorityEpoch: 'epoch-a', browserHostClientId: 'host-a', browserHostGeneration: 2, browserPageId: 'page-a', pageHostGeneration: 3, commandSequence: 4, commandId: 'command-a', command: { type: 'createPage', browserProfileId: 'default', executionHostKey: 'native:runtime-a:5' }, ...override }) ).toThrow() }) it.each([ { scope: 'attach', field: 'authorityRuntimeId' }, { scope: 'attach', field: 'authorityEpoch' }, { scope: 'attach', field: 'browserHostClientId' }, { scope: 'attach', field: 'browserHostGeneration' }, { scope: 'attach', field: 'executionHost' }, { scope: 'executionHost', field: 'kind' }, { scope: 'executionHost', field: 'runtimeId' }, { scope: 'executionHost', field: 'revision' } ])('requires $field in $scope on tunnel attach', ({ scope, field }) => { const attach = { authorityRuntimeId: 'runtime-a', authorityEpoch: 'epoch-a', browserHostClientId: 'host-a', browserHostGeneration: 1, executionHost: { kind: 'native', runtimeId: 'runtime-a', revision: 1 } } expect(BrowserNetworkTunnelAttachParams.parse(attach)).toEqual(attach) const incomplete = Object.fromEntries( Object.entries(scope === 'attach' ? attach : attach.executionHost).filter( ([key]) => key !== field ) ) expect(() => BrowserNetworkTunnelAttachParams.parse( scope === 'attach' ? incomplete : { ...attach, executionHost: incomplete } ) ).toThrow() }) it('decodes an exact SSH provider authority without changing native v1 attaches', () => { const authority = { authorityRuntimeId: 'runtime-a', authorityEpoch: 'epoch-a', browserHostClientId: 'host-a', browserHostGeneration: 1 } expect( BrowserNetworkTunnelAttachParams.parse({ ...authority, executionHost: { kind: 'native', runtimeId: 'runtime-a', revision: 1 } }) ).toEqual({ ...authority, executionHost: { kind: 'native', runtimeId: 'runtime-a', revision: 1 } }) expect( BrowserNetworkTunnelAttachParams.parse({ ...authority, executionHost: { kind: 'ssh', targetId: 'target-a', providerEpoch: 'provider-epoch-a', connectionGeneration: 2 } }) ).toEqual({ ...authority, executionHost: { kind: 'ssh', targetId: 'target-a', providerEpoch: 'provider-epoch-a', connectionGeneration: 2 } }) expect( BrowserNetworkTunnelAttachParams.parse({ authorityRuntimeId: 'runtime-a', authorityEpoch: 'epoch-a', browserHostClientId: 'client-a', browserHostGeneration: 1, executionHost: { kind: 'wsl', runtimeId: 'runtime-a', revision: 8, distro: 'Ubuntu' } }).executionHost ).toEqual({ kind: 'wsl', runtimeId: 'runtime-a', revision: 8, distro: 'Ubuntu' }) }) it('rejects invalid server-owned route generations', () => { expect(() => BrowserNetworkTunnelEvent.parse({ type: 'ready', tunnelGeneration: 0 })).toThrow() expect(BrowserNetworkTunnelEvent.parse({ type: 'ready', tunnelGeneration: 3 })).toEqual({ type: 'ready', tunnelGeneration: 3 }) }) it('binds revocation to the exact authority and host generation', () => { expect( BrowserClientHostEvent.parse({ type: 'revoked', authorityEpoch: 'epoch-a', browserHostGeneration: 3, reason: 'replaced' }) ).toEqual({ type: 'revoked', authorityEpoch: 'epoch-a', browserHostGeneration: 3, reason: 'replaced' }) }) }) function maxInventoryIdentity(prefix: string): string { let value = prefix while ( value.length < 256 && jsonByteLength(`${value}\0`) <= BROWSER_CLIENT_HOST_PAGE_INVENTORY_IDENTITY_MAX_JSON_BYTES ) { value += '\0' } while ( jsonByteLength(`${value}x`) <= BROWSER_CLIENT_HOST_PAGE_INVENTORY_IDENTITY_MAX_JSON_BYTES ) { value += 'x' } return value } function jsonByteLength(value: unknown): number { return new TextEncoder().encode(JSON.stringify(value)).byteLength }