OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer / test / test_vectors.js

229 lines · javascript ref:
#!/usr/bin/env node
// Generate test vectors for compact encoding wire compatibility.
// Outputs JSON with expected hex bytes for each test case.
// Usage: node test/test_vectors.js > test/vectors.json

const c = require('compact-encoding')
const sodium = require('sodium-universal')
const b4a = require('b4a')

// We'll use compact-encoding-net via dht-rpc's peer module approach.
// Inline the ipv4Address encoding:
const net_ipv4 = {
  preencode (state) { state.end += 4 },
  encode (state, string) {
    let i = 0
    while (i < string.length) {
      let n = 0
      let ch
      while (i < string.length && (ch = string.charCodeAt(i++)) !== 0x2e) {
        n = n * 10 + (ch - 0x30)
      }
      state.buffer[state.start++] = n
    }
  },
  decode (state) {
    return (
      state.buffer[state.start++] + '.' +
      state.buffer[state.start++] + '.' +
      state.buffer[state.start++] + '.' +
      state.buffer[state.start++]
    )
  }
}

const ipv4Address = {
  preencode (state, m) {
    net_ipv4.preencode(state, m.host)
    c.uint16.preencode(state, m.port)
  },
  encode (state, m) {
    net_ipv4.encode(state, m.host)
    c.uint16.encode(state, m.port)
  },
  decode (state) {
    return {
      host: net_ipv4.decode(state),
      port: c.uint16.decode(state)
    }
  }
}

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

function toHex (buf) {
  return b4a.toString(buf, 'hex')
}

// Node ID generation (same as dht-rpc/lib/peer.js)
function nodeId (host, port) {
  const out = b4a.allocUnsafeSlow(32)
  const addr = out.subarray(0, 6)
  ipv4Address.encode({ start: 0, end: 6, buffer: addr }, { host, port })
  sodium.crypto_generichash(out, addr)
  return out
}

const vectors = {
  uint: [],
  uint16: [],
  uint32: [],
  fixed32: [],
  buffer: [],
  ipv4Address: [],
  nodeId: [],
  dhtRequest: [],
  dhtResponse: []
}

// ---- uint (varint) vectors ----
const uintValues = [0, 1, 42, 252, 253, 4200, 65535, 65536, 0x12345678, 0xFFFFFFFF]
for (const v of uintValues) {
  const buf = encode(c.uint, v)
  vectors.uint.push({ value: v, hex: toHex(buf), bytes: buf.length })
}

// ---- uint16 ----
for (const v of [0, 1, 256, 49737, 65535]) {
  const buf = encode(c.uint16, v)
  vectors.uint16.push({ value: v, hex: toHex(buf), bytes: buf.length })
}

// ---- uint32 ----
for (const v of [0, 1, 65536, 0x12345678, 0xFFFFFFFF]) {
  const buf = encode(c.uint32, v)
  vectors.uint32.push({ value: v, hex: toHex(buf), bytes: buf.length })
}

// ---- fixed32 ----
const fixed32Val = b4a.alloc(32, 0)
for (let i = 0; i < 32; i++) fixed32Val[i] = i
vectors.fixed32.push({ hex: toHex(fixed32Val), input: toHex(fixed32Val) })

// ---- buffer ----
vectors.buffer.push({ value: null, hex: '00' })
vectors.buffer.push({ value: '', hex: toHex(encode(c.buffer, b4a.alloc(0))) })
vectors.buffer.push({ value: 'hello', hex: toHex(encode(c.buffer, b4a.from('hello'))) })

const buf200 = b4a.alloc(200, 0xAB)
vectors.buffer.push({ value: '200 bytes of 0xAB', hex: toHex(encode(c.buffer, buf200)) })

// ---- ipv4Address ----
const addrs = [
  { host: '88.99.3.86', port: 49737 },
  { host: '142.93.90.113', port: 49737 },
  { host: '138.68.147.8', port: 49737 },
  { host: '192.168.1.1', port: 4242 },
  { host: '0.0.0.0', port: 0 },
  { host: '255.255.255.255', port: 65535 }
]
for (const addr of addrs) {
  const buf = encode(ipv4Address, addr)
  vectors.ipv4Address.push({
    host: addr.host,
    port: addr.port,
    hex: toHex(buf),
    bytes: buf.length
  })
}

// ---- nodeId ----
for (const addr of addrs.slice(0, 4)) {
  const id = nodeId(addr.host, addr.port)
  vectors.nodeId.push({
    host: addr.host,
    port: addr.port,
    hex: toHex(id)
  })
}

// ---- DHT request encoding ----
// Encode a FIND_NODE request (internal, command=2, with target)
{
  const VERSION = 0b11
  const REQUEST_ID = (0b0000 << 4) | VERSION  // 0x03
  const target = b4a.alloc(32, 0)
  for (let i = 0; i < 32; i++) target[i] = i

  const myId = nodeId('10.0.0.1', 12345)
  const to = { host: '88.99.3.86', port: 49737 }
  const tid = 42

  // Flags: HAS_ID(1) | INTERNAL(4) | TARGET(8) = 0x0D
  const flags = 1 | 4 | 8
  const command = 2 // FIND_NODE

  const state = { start: 0, end: 0, buffer: null }
  state.end = 1 + 1 + 2 + 6 + 32 // type+flags+tid+to+id
  c.uint.preencode(state, command)
  state.end += 32 // target

  state.buffer = b4a.alloc(state.end)
  state.buffer[state.start++] = REQUEST_ID
  state.buffer[state.start++] = flags
  c.uint16.encode(state, tid)
  ipv4Address.encode(state, to)
  c.fixed32.encode(state, myId)
  c.uint.encode(state, command)
  c.fixed32.encode(state, target)

  vectors.dhtRequest.push({
    description: 'FIND_NODE with HAS_ID and TARGET',
    tid,
    to,
    flags,
    command,
    hex: toHex(state.buffer),
    bytes: state.buffer.length
  })
}

// ---- DHT response encoding ----
{
  const VERSION = 0b11
  const RESPONSE_ID = (0b0001 << 4) | VERSION  // 0x13
  const tid = 42
  const to = { host: '10.0.0.1', port: 12345 }
  const responderId = nodeId('88.99.3.86', 49737)
  const token = b4a.alloc(32, 0xFF)

  const closerNodes = [
    { host: '142.93.90.113', port: 49737 },
    { host: '138.68.147.8', port: 49737 }
  ]

  // Flags: HAS_ID(1) | TOKEN(2) | CLOSER_NODES(4) = 0x07
  const flags = 1 | 2 | 4
  const closerArray = c.array(ipv4Address)

  const state = { start: 0, end: 0, buffer: null }
  state.end = 1 + 1 + 2 + 6 + 32 + 32 // type+flags+tid+to+id+token
  closerArray.preencode(state, closerNodes)

  state.buffer = b4a.alloc(state.end)
  state.buffer[state.start++] = RESPONSE_ID
  state.buffer[state.start++] = flags
  c.uint16.encode(state, tid)
  ipv4Address.encode(state, to)
  c.fixed32.encode(state, responderId)
  c.fixed32.encode(state, token)
  closerArray.encode(state, closerNodes)

  vectors.dhtResponse.push({
    description: 'Response with HAS_ID, TOKEN, CLOSER_NODES',
    tid,
    to,
    flags,
    hex: toHex(state.buffer),
    bytes: state.buffer.length
  })
}

console.log(JSON.stringify(vectors, null, 2))