#!/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))