import { describe, test, expect } from 'vitest' import { MUX_HEADER_BYTES, encodeMuxFrame, decodeHeader, decodeMuxFrame, encodeOpen, decodeOpen, encodeWindowUpdate, decodeWindowUpdate, encodeGoaway, decodeGoaway, TYPE_TO_BYTE, BYTE_TO_TYPE, type MuxFrameHeader, type MuxFrameType, type MuxOpen, } from '../mux/frame-codec.js' const ALL_TYPES: readonly MuxFrameType[] = [ 'open', 'data', 'close', 'ping', 'pong', 'windowUpdate', 'goaway', ] function header(over: Partial): MuxFrameHeader { return { version: 1, type: 'data', fin: false, rst: false, streamId: 7, payloadLen: 0, ...over } } describe('frame codec (T2, §4.1) — re-exported from the frozen relay-contracts codec', () => { test('round-trips every frame type incl. fin/rst bit packing', () => { for (const type of ALL_TYPES) { const payload = new Uint8Array([1, 2, 3]) const h = header({ type, fin: true, rst: false, streamId: 42, payloadLen: payload.length }) const decoded = decodeMuxFrame(encodeMuxFrame(h, payload)) expect(decoded.header.type).toBe(type) expect(decoded.header.fin).toBe(true) expect(decoded.header.rst).toBe(false) expect(decoded.header.streamId).toBe(42) expect([...decoded.payload]).toEqual([1, 2, 3]) } }) test('streamId=0 link-level frames decode with streamId===0', () => { const h = header({ type: 'ping', streamId: 0, payloadLen: 0 }) expect(decodeHeader(encodeMuxFrame(h, new Uint8Array(0))).streamId).toBe(0) }) test('rst bit round-trips independently of fin', () => { const h = header({ type: 'close', fin: false, rst: true, payloadLen: 0 }) const d = decodeHeader(encodeMuxFrame(h, new Uint8Array(0))) expect(d.rst).toBe(true) expect(d.fin).toBe(false) }) test('type ⇄ byte maps agree with §4.1 (0x01 OPEN … 0x07 GOAWAY)', () => { expect(TYPE_TO_BYTE.open).toBe(0x01) expect(TYPE_TO_BYTE.goaway).toBe(0x07) expect(BYTE_TO_TYPE[0x02]).toBe('data') }) test('negative: truncated buffer (< 15B) throws', () => { expect(() => decodeHeader(new Uint8Array(MUX_HEADER_BYTES - 1))).toThrow() }) test('negative: payloadLen larger than actual payload throws', () => { const buf = encodeMuxFrame(header({ payloadLen: 2 }), new Uint8Array([9, 9])) // Claim a longer payload than present. buf[14] = 5 expect(() => decodeMuxFrame(buf)).toThrow() }) test('negative: unknown type byte throws', () => { const buf = encodeMuxFrame(header({ type: 'data', payloadLen: 0 }), new Uint8Array(0)) buf[1] = 0x08 // unknown type code expect(() => decodeHeader(buf)).toThrow() buf[1] = 0x00 expect(() => decodeHeader(buf)).toThrow() }) test('negative: version ≠ 0x01 throws', () => { const buf = encodeMuxFrame(header({ payloadLen: 0 }), new Uint8Array(0)) buf[0] = 0x02 expect(() => decodeHeader(buf)).toThrow() }) test('OPEN payload round-trips via CBOR + Zod guard', () => { const open: MuxOpen = { streamId: 3, subdomain: 'alice', requestPath: '/term?join=x', originHeader: 'https://alice.term.example.com', remoteAddrHash: 'deadbeef', capabilityTokenRef: 'jti-1', } expect(decodeOpen(encodeOpen(open))).toEqual(open) }) test('decodeOpen rejects a malformed/partial CBOR payload (never returns partial)', () => { expect(() => decodeOpen(new Uint8Array([0x00, 0x01, 0x02]))).toThrow() }) test('WINDOW_UPDATE + GOAWAY int codecs round-trip', () => { expect(decodeWindowUpdate(encodeWindowUpdate(65_536))).toBe(65_536) const g = decodeGoaway(encodeGoaway(9, 'revoked')) expect(g).toEqual({ lastStreamId: 9, reason: 'revoked' }) }) })