OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / test / unit / blind-client-browser-state-v3.test.js

196 lines · javascript ref:
import test from 'brittle'
import b4a from 'b4a'
import fs from 'node:fs'
import path from 'node:path'
import { decodeCanonical } from '../../packages/blind-protocol/codec.js'
import {
  FORWARD_HTTPS_REQUEST_KIND_V1,
  FORWARD_HTTPS_RESPONSE_KIND_V1,
  blindForwardHttpsOriginForwardTurnRequestV1
} from '../../packages/blind-protocol/wire-v3.js'
import { assertForwardHttpsSessionV3 } from '../../packages/blind-protocol/client-composition-v3.js'
import {
  BLIND_CLIENT_CONTROL_V3_AUTHORITY,
  FORWARD_HTTPS_BROWSER_TERMINAL_KIND_V3,
  FORWARD_HTTPS_OUTSTANDING_STATE_V3,
  applyForwardHttpsResultV3,
  assertForwardHttpsPersistedSessionRecordV3,
  markForwardHttpsAwaitingDefinitiveTargetV3,
  prepareForwardHttpsOriginPersistenceV3
} from '../../packages/blind-client/browser-forward-state-v3.js'

const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), '../..')
const vector = name => fs.readFileSync(path.join(root, 'packages/blind-protocol/vectors-v3/wire/positive', name))

function endpointFor (originBytes) {
  const origin = decodeCanonical(blindForwardHttpsOriginForwardTurnRequestV1, originBytes, { copyBytes: true })
  const capability = origin.parentCapability
  return {
    version: 3,
    releaseProfileId: 2,
    routeKind: 7,
    wireV3AbiHash: b4a.from(BLIND_CLIENT_CONTROL_V3_AUTHORITY.wireV3AbiHash, 'hex'),
    verifiedEndpointHandleHash: b4a.alloc(32, 0xa1),
    sourceRelayPublicKey: capability.sourceRelayPublicKey,
    sourceDescriptorSequence: capability.sourceDescriptorSequence,
    sourceDescriptorHash: capability.sourceDescriptorHash,
    targetCatalogEntryId: capability.targetCatalogEntryId,
    targetRelayPublicKey: capability.targetRelayPublicKey,
    targetDescriptorSequence: capability.targetDescriptorSequence,
    targetDescriptorHash: capability.targetDescriptorHash,
    signedDescriptorHash: b4a.alloc(32, 0xa2),
    signedHealthHash: b4a.alloc(32, 0xa3),
    descriptorFresh: true,
    signedHealthFresh: true,
    credentialFreeHttps: true,
    cookies: false,
    authorization: false,
    referrer: false,
    redirect: false,
    exactRequestBytes: 65_536,
    exactResultBytes: 65_536,
    continuityBackend: 'INDEXEDDB_PERSISTENT'
  }
}

function input (sessionKey, endpoint, requestBytes) {
  return { sessionKey, verifiedEndpoint: endpoint, requestBytes }
}

function publicSession (record, overrides = {}) {
  return {
    version: 3,
    verifiedEndpoint: record.verifiedEndpoint,
    stableSessionId: record.stableSessionId,
    capabilityPrefixHash: record.capabilityPrefixHash,
    clientSessionNonce: record.clientSessionNonce,
    nextSequence: BigInt(record.nextSequence),
    previousTargetResultHash: record.previousTargetResultHash,
    terminal: record.terminal,
    outstandingState: record.outstandingState,
    outstandingOriginRequestCommitment: record.outstandingOriginRequestCommitment,
    outstandingOriginRequest: record.outstandingOriginRequest,
    lastDefinitiveTargetResult: record.lastDefinitiveTargetResult,
    ...overrides
  }
}

function openedRecord () {
  const origin = vector('open-origin.bin')
  const endpoint = endpointFor(origin)
  const persisted = prepareForwardHttpsOriginPersistenceV3(null, input('session', endpoint, origin))
  return { endpoint, origin, record: persisted.record }
}

test('browser v3 state derives full ID10 from exact signed-vector authority and retries idempotently', t => {
  const { endpoint, origin, record } = openedRecord()
  t.is(record.outstandingState, FORWARD_HTTPS_OUTSTANDING_STATE_V3.PERSISTED_BEFORE_FETCH)
  t.is(record.nextSequence, '0')
  t.alike(record.outstandingOriginRequest, origin)
  t.alike(record.verifiedEndpoint, endpoint)
  t.ok(assertForwardHttpsSessionV3(publicSession(record)))

  const retry = prepareForwardHttpsOriginPersistenceV3(record, input('session', endpoint, origin))
  t.is(retry.disposition, 'EXACT_RETRY')
  t.alike(retry.record.outstandingOriginRequest, origin)
  t.alike(retry.record.verifiedEndpoint, endpoint)
})

test('browser v3 changed outstanding bytes atomically retain closed terminal evidence and absorb all mutation', t => {
  const { endpoint, origin, record } = openedRecord()
  const changed = b4a.from(origin)
  changed[100] ^= 1
  const conflict = prepareForwardHttpsOriginPersistenceV3(record, input('session', endpoint, changed))
  t.is(conflict.disposition, 'CONFLICT_TERMINAL')
  t.is(conflict.record.terminal, 1)
  t.is(conflict.record.terminalKind, FORWARD_HTTPS_BROWSER_TERMINAL_KIND_V3.CORRECTNESS)
  t.is(conflict.record.outstandingState, FORWARD_HTTPS_OUTSTANDING_STATE_V3.NONE)
  t.is(conflict.record.outstandingOriginRequest.byteLength, 0)
  t.alike(conflict.record.terminalEvidence.originRequest, origin)
  t.alike(conflict.record.terminalEvidence.originRequestCommitment, record.outstandingOriginRequestCommitment)
  t.ok(assertForwardHttpsSessionV3(publicSession(conflict.record)))
  t.exception(() => markForwardHttpsAwaitingDefinitiveTargetV3(conflict.record), /terminal/)
  t.exception(() => applyForwardHttpsResultV3(conflict.record, vector('open-target-result.bin')), /terminal/)
  t.exception(() => prepareForwardHttpsOriginPersistenceV3(conflict.record, input('session', endpoint, origin)), /terminal/)
  t.exception(() => assertForwardHttpsPersistedSessionRecordV3({
    ...conflict.record,
    terminalEvidence: { ...conflict.record.terminalEvidence, callerReason: 'forged' }
  }), /unknown or missing/)
})

test('browser v3 verifies exact ID77 internally, retains source results, and advances target atomically', t => {
  const { record } = openedRecord()
  const awaiting = markForwardHttpsAwaitingDefinitiveTargetV3(record)
  const source = applyForwardHttpsResultV3(awaiting, vector('open-source-pre-forward-error.bin'))
  t.is(source.outcome.verified, true)
  t.is(source.outcome.advanced, false)
  t.is(source.record.nextSequence, '0')
  t.is(source.record.outstandingState, FORWARD_HTTPS_OUTSTANDING_STATE_V3.AWAITING_DEFINITIVE_TARGET)
  t.alike(source.record.outstandingOriginRequest, record.outstandingOriginRequest)

  t.exception(() => applyForwardHttpsResultV3(awaiting, b4a.alloc(65_536)), /magic|invalid/)
  t.exception(() => applyForwardHttpsResultV3(awaiting, vector('data-target-ack.bin')), /bind|provenance|commitment/)
  t.is(awaiting.nextSequence, '0')
  t.is(awaiting.outstandingState, FORWARD_HTTPS_OUTSTANDING_STATE_V3.AWAITING_DEFINITIVE_TARGET)

  const target = applyForwardHttpsResultV3(awaiting, vector('open-target-result.bin'))
  t.is(target.outcome.verified, true)
  t.is(target.outcome.advanced, true)
  t.is(target.record.nextSequence, '1')
  t.is(target.record.outstandingState, FORWARD_HTTPS_OUTSTANDING_STATE_V3.NONE)
  t.is(target.record.terminal, 0)
  t.is(target.record.targetFin, false)
  t.alike(target.record.lastDefinitiveTargetResult, vector('open-target-result.bin'))
  t.ok(assertForwardHttpsSessionV3(publicSession(target.record)))
})

test('browser v3 preserves later-turn history and separates target FIN from normal close', t => {
  const opened = openedRecord()
  const first = applyForwardHttpsResultV3(
    markForwardHttpsAwaitingDefinitiveTargetV3(opened.record),
    vector('open-target-result.bin')
  ).record
  const data = vector('data-origin-max.bin')
  const later = prepareForwardHttpsOriginPersistenceV3(first, input('session', opened.endpoint, data)).record
  t.alike(later.lastDefinitiveTargetResult, vector('open-target-result.bin'))
  const fin = applyForwardHttpsResultV3(later, vector('data-target-close.bin'))
  t.is(fin.outcome.responseKind, FORWARD_HTTPS_RESPONSE_KIND_V1.CLOSE)
  t.is(fin.outcome.targetFin, true)
  t.is(fin.outcome.normalClose, false)
  t.is(fin.record.targetFin, true)
  t.is(fin.record.terminal, 0)
  t.is(fin.record.terminalKind, FORWARD_HTTPS_BROWSER_TERMINAL_KIND_V3.NONE)

  const closeOrigin = vector('close-origin.bin')
  const closeOutstanding = prepareForwardHttpsOriginPersistenceV3(first, input('session', opened.endpoint, closeOrigin)).record
  const closed = applyForwardHttpsResultV3(closeOutstanding, vector('close-target-ack.bin'))
  t.is(closed.outcome.requestKind, FORWARD_HTTPS_REQUEST_KIND_V1.CLOSE)
  t.is(closed.outcome.normalClose, true)
  t.is(closed.outcome.targetFin, false)
  t.is(closed.record.terminal, 1)
  t.is(closed.record.terminalKind, FORWARD_HTTPS_BROWSER_TERMINAL_KIND_V3.NORMAL_CLOSE)
  t.ok(assertForwardHttpsSessionV3(publicSession(closed.record)))
})

test('browser v3 recovered advanced state rejects forged close, targetFin, session and prefix before transport', t => {
  const opened = openedRecord()
  let transportCalls = 0
  const beforeTransport = record => {
    assertForwardHttpsPersistedSessionRecordV3(record)
    transportCalls++
  }
  t.exception(() => beforeTransport({ ...opened.record, targetFin: true }), /sequence-zero/)
  const advanced = applyForwardHttpsResultV3(opened.record, vector('open-target-result.bin')).record
  t.exception(() => beforeTransport({ ...advanced, terminal: 1, terminalKind: 'NORMAL_CLOSE' }), /iff|normal-close/)
  t.exception(() => beforeTransport({ ...advanced, targetFin: true }), /sequence-zero|targetFin|CLOSE/)
  t.exception(() => beforeTransport({ ...advanced, stableSessionId: b4a.alloc(32, 0xf1) }), /bind|session/)
  t.exception(() => beforeTransport({ ...advanced, capabilityPrefixHash: b4a.alloc(32, 0xf2) }), /prefix|bind/)
  t.is(transportCalls, 0)

  const closeOutstanding = prepareForwardHttpsOriginPersistenceV3(advanced, input('session', opened.endpoint, vector('close-origin.bin'))).record
  const closed = applyForwardHttpsResultV3(closeOutstanding, vector('close-target-close.bin')).record
  t.exception(() => beforeTransport({ ...closed, terminal: 0, terminalKind: 'NONE' }), /iff|CLOSE/)
  t.exception(() => beforeTransport({ ...closed, targetFin: false }), /targetFin/)
  t.is(transportCalls, 0)
})