OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / test / unit / pairing-protocol.test.js

97 lines · javascript ref:
import test from 'brittle'
import b4a from 'b4a'
import c from 'compact-encoding'
import {
  MAX_FRAME_BYTES,
  pairMessageEncoding
} from 'p2p-hiverelay-client/pairing.js'

function encodePairFrame (msg) {
  const state = { start: 0, end: 0, buffer: null }
  pairMessageEncoding.preencode(state, msg)
  state.buffer = b4a.alloc(state.end)
  pairMessageEncoding.encode(state, msg)
  return state.buffer
}

function declaredStringFrame (len, payload = null) {
  const state = { start: 0, end: 0, buffer: null }
  c.uint.preencode(state, len)
  state.end += payload ? payload.length : 0
  state.buffer = b4a.alloc(state.end)
  c.uint.encode(state, len)
  if (payload) payload.copy(state.buffer, state.start)
  return state.buffer
}

test('pairing protocol encoding: round-trips valid JSON frame', (t) => {
  const frame = encodePairFrame({ type: 'challenge', nonce: 'a'.repeat(64) })
  const result = pairMessageEncoding.decode({ buffer: frame, start: 0, end: frame.length })
  t.is(result.type, 'challenge')
  t.is(result.nonce, 'a'.repeat(64))
})

test('pairing protocol encoding: rejects oversized outbound messages before allocation growth', (t) => {
  const msg = { type: 'identity', bundle: { blob: 'x'.repeat(MAX_FRAME_BYTES + 1) } }
  const preState = { start: 0, end: 0, buffer: null }
  t.exception(() => {
    pairMessageEncoding.preencode(preState, msg)
  }, /frame too large/, 'preencode rejects oversize')
  t.is(preState.end, 0, 'preencode does not grow state.end')

  const encodeState = { start: 0, end: 16, buffer: b4a.alloc(16) }
  t.exception(() => {
    pairMessageEncoding.encode(encodeState, msg)
  }, /frame too large/, 'encode rejects oversize')
  t.is(encodeState.start, 0, 'encode does not advance state.start')
})

test('pairing protocol encoding: rejects oversized declared inbound frame before string decode', (t) => {
  const frame = declaredStringFrame(MAX_FRAME_BYTES + 1)
  let result = null
  t.execution(() => {
    result = pairMessageEncoding.decode({ buffer: frame, start: 0, end: frame.length })
  }, 'oversized declared frame does not throw')
  t.is(result.type, -1)
  t.is(result.error, 'frame too large')
})

test('pairing protocol encoding: rejects malformed and truncated frames without throwing', (t) => {
  const malformed = declaredStringFrame(9, b4a.from('{not json'))
  let malformedResult = null
  t.execution(() => {
    malformedResult = pairMessageEncoding.decode({ buffer: malformed, start: 0, end: malformed.length })
  }, 'malformed JSON does not throw')
  t.is(malformedResult.type, -1)
  t.is(malformedResult.error, 'malformed JSON')

  const truncated = declaredStringFrame(32)
  let truncatedResult = null
  t.execution(() => {
    truncatedResult = pairMessageEncoding.decode({ buffer: truncated, start: 0, end: truncated.length })
  }, 'truncated string does not throw')
  t.is(truncatedResult.type, -1)
  t.is(truncatedResult.error, 'malformed JSON')
})

test('pairing protocol encoding: rejects invalid decode state without throwing', (t) => {
  const frame = encodePairFrame({ type: 'ack' })
  const cases = [
    { name: 'missing state', state: null },
    { name: 'missing buffer', state: { start: 0, end: 0 } },
    { name: 'negative start', state: { buffer: frame, start: -1, end: frame.length } },
    { name: 'end before start', state: { buffer: frame, start: 4, end: 2 } },
    { name: 'end past buffer', state: { buffer: frame, start: 0, end: frame.length + 1 } },
    { name: 'unsafe start', state: { buffer: frame, start: Number.MAX_SAFE_INTEGER + 1, end: frame.length } }
  ]

  for (const { name, state } of cases) {
    let result = null
    t.execution(() => {
      result = pairMessageEncoding.decode(state)
    }, `${name} does not throw`)
    t.is(result.type, -1, `${name} type is -1`)
    t.is(result.error, 'malformed JSON', `${name} error`)
  }
})