feat: add support for HTTPS SVCB (type 65)
* This commit adds support for HTTPS SVCB record, both encoding and decoding.
This commit is contained in:
parent
6bdb761479
commit
ab3c5ca3f5
63
hexdump.js
Normal file
63
hexdump.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
const wout = (s) => {
|
||||||
|
process.stdout.write(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pad_str = (s, len, ch) => {
|
||||||
|
ch = ch || ' ';
|
||||||
|
while (s.length < len) {
|
||||||
|
s = ch + s;
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
const hex_digit = (n, len) => {
|
||||||
|
return pad_str(n.toString(16), len || 2, '0')
|
||||||
|
}
|
||||||
|
|
||||||
|
const printable_ch = (c) => {
|
||||||
|
// [space to '~')
|
||||||
|
if (c > 0x20 && c <= 0x7e) {
|
||||||
|
return String.fromCharCode(c);
|
||||||
|
} else {
|
||||||
|
return '.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hexdump = (buf, len) => {
|
||||||
|
len = len || buf.length;
|
||||||
|
|
||||||
|
const maxline = 16;
|
||||||
|
let ascii = new Array(maxline);
|
||||||
|
let i;
|
||||||
|
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
if (i % maxline == 0) {
|
||||||
|
if (i > 0) {
|
||||||
|
wout(" " + ascii.join(""))
|
||||||
|
wout("\n");
|
||||||
|
}
|
||||||
|
wout(hex_digit(i, 4) + ": ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// output the hex digit
|
||||||
|
wout(hex_digit(buf[i]));
|
||||||
|
wout(" ");
|
||||||
|
ascii[i % maxline] = printable_ch(buf[i]);
|
||||||
|
}
|
||||||
|
if (i % maxline != 0) {
|
||||||
|
let diff = maxline - (i % maxline);
|
||||||
|
wout(" ".repeat(diff))
|
||||||
|
}
|
||||||
|
|
||||||
|
wout(" " + ascii.slice(0, (i % maxline)).join("") + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
let buf = Buffer.alloc(500);
|
||||||
|
for (let i = 0; i < buf.length; i++) {
|
||||||
|
buf[i] = i & 0xff;
|
||||||
|
}
|
||||||
|
hexdump(buf)
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = hexdump;
|
||||||
367
index.js
367
index.js
@ -1473,6 +1473,372 @@ rtlsa.encodingLength = function (cert) {
|
|||||||
return 5 + Buffer.byteLength(cert.certificate)
|
return 5 + Buffer.byteLength(cert.certificate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const svcparam = exports.svcparam = {}
|
||||||
|
|
||||||
|
svcparam.keyToNumber = function(keyName) {
|
||||||
|
switch (keyName.toLowerCase()) {
|
||||||
|
case 'mandatory': return 0
|
||||||
|
case 'alpn' : return 1
|
||||||
|
case 'no-default-alpn' : return 2
|
||||||
|
case 'port' : return 3
|
||||||
|
case 'ipv4hint' : return 4
|
||||||
|
case 'echconfig' : return 5
|
||||||
|
case 'ipv6hint' : return 6
|
||||||
|
case 'odoh' : return 32769
|
||||||
|
case 'key65535' : return 65535
|
||||||
|
}
|
||||||
|
if (!keyName.startsWith('key')) {
|
||||||
|
throw new Error(`Name must start with key: ${keyName}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number.parseInt(keyName.substring(3))
|
||||||
|
}
|
||||||
|
|
||||||
|
svcparam.numberToKeyName = function(number) {
|
||||||
|
switch (number) {
|
||||||
|
case 0 : return 'mandatory'
|
||||||
|
case 1 : return 'alpn'
|
||||||
|
case 2 : return 'no-default-alpn'
|
||||||
|
case 3 : return 'port'
|
||||||
|
case 4 : return 'ipv4hint'
|
||||||
|
case 5 : return 'echconfig'
|
||||||
|
case 6 : return 'ipv6hint'
|
||||||
|
case 32769 : return 'odoh'
|
||||||
|
}
|
||||||
|
|
||||||
|
return `key${number}`
|
||||||
|
}
|
||||||
|
|
||||||
|
svcparam.encode = function(param, buf, offset) {
|
||||||
|
if (!buf) buf = Buffer.allocUnsafe(svcparam.encodingLength(param))
|
||||||
|
if (!offset) offset = 0
|
||||||
|
|
||||||
|
let key = param.key;
|
||||||
|
if (typeof param.key !== 'number') {
|
||||||
|
key = svcparam.keyToNumber(param.key)
|
||||||
|
}
|
||||||
|
buf.writeUInt16BE(key || 0, offset)
|
||||||
|
offset += 2
|
||||||
|
svcparam.encode.bytes = 2
|
||||||
|
|
||||||
|
if (key == 0) { // mandatory
|
||||||
|
let values = param.value
|
||||||
|
if (!Array.isArray(values)) values = [values]
|
||||||
|
buf.writeUInt16BE(values.length*2, offset)
|
||||||
|
offset += 2
|
||||||
|
svcparam.encode.bytes += 2
|
||||||
|
|
||||||
|
for (let val of values) {
|
||||||
|
if (typeof val !== 'number') {
|
||||||
|
val = svcparam.keyToNumber(val)
|
||||||
|
}
|
||||||
|
buf.writeUInt16BE(val, offset)
|
||||||
|
offset += 2
|
||||||
|
svcparam.encode.bytes += 2
|
||||||
|
}
|
||||||
|
} else if (key == 1) { // alpn
|
||||||
|
let val = param.value
|
||||||
|
if (!Array.isArray(val)) val = [val]
|
||||||
|
// The alpn param is prefixed by its length as a single byte, so the
|
||||||
|
// initialValue to reduce function is the length of the array.
|
||||||
|
let total = val.reduce(function(result, id) {
|
||||||
|
return result += id.length
|
||||||
|
}, val.length)
|
||||||
|
|
||||||
|
buf.writeUInt16BE(total, offset)
|
||||||
|
offset += 2
|
||||||
|
svcparam.encode.bytes += 2
|
||||||
|
|
||||||
|
for (let id of val) {
|
||||||
|
buf.writeUInt8(id.length, offset)
|
||||||
|
offset += 1
|
||||||
|
svcparam.encode.bytes += 1
|
||||||
|
|
||||||
|
buf.write(id, offset)
|
||||||
|
offset += id.length
|
||||||
|
svcparam.encode.bytes += id.length
|
||||||
|
}
|
||||||
|
} else if (key == 2) { // no-default-alpn
|
||||||
|
buf.writeUInt16BE(0, offset)
|
||||||
|
offset += 2
|
||||||
|
svcparam.encode.bytes += 2
|
||||||
|
} else if (key == 3) { // port
|
||||||
|
buf.writeUInt16BE(2, offset)
|
||||||
|
offset += 2
|
||||||
|
svcparam.encode.bytes += 2
|
||||||
|
buf.writeUInt16BE(param.value || 0, offset)
|
||||||
|
offset += 2
|
||||||
|
svcparam.encode.bytes += 2
|
||||||
|
} else if (key == 4) { //ipv4hint
|
||||||
|
let val = param.value
|
||||||
|
if (!Array.isArray(val)) val = [val]
|
||||||
|
buf.writeUInt16BE(val.length*4, offset)
|
||||||
|
offset += 2;
|
||||||
|
svcparam.encode.bytes += 2
|
||||||
|
|
||||||
|
for (let host of val) {
|
||||||
|
ip.v4.encode(host, buf, offset)
|
||||||
|
offset += 4
|
||||||
|
svcparam.encode.bytes += 4
|
||||||
|
}
|
||||||
|
} else if (key == 5) { //echconfig
|
||||||
|
if (svcparam.ech) {
|
||||||
|
buf.writeUInt16BE(svcparam.ech.length, offset)
|
||||||
|
offset += 2
|
||||||
|
svcparam.encode.bytes += 2
|
||||||
|
for (let i = 0; i < svcparam.ech.length; i++) {
|
||||||
|
buf.writeUInt8(svcparam.ech[i], offset)
|
||||||
|
offset++
|
||||||
|
}
|
||||||
|
svcparam.encode.bytes += svcparam.ech.length
|
||||||
|
} else {
|
||||||
|
buf.writeUInt16BE(param.value.length, offset)
|
||||||
|
offset += 2
|
||||||
|
svcparam.encode.bytes += 2
|
||||||
|
buf.write(param.value, offset)
|
||||||
|
offset += param.value.length
|
||||||
|
svcparam.encode.bytes += param.value.length
|
||||||
|
}
|
||||||
|
} else if (key == 6) { //ipv6hint
|
||||||
|
let val = param.value
|
||||||
|
if (!Array.isArray(val)) val = [val];
|
||||||
|
buf.writeUInt16BE(val.length*16, offset)
|
||||||
|
offset += 2
|
||||||
|
svcparam.encode.bytes += 2
|
||||||
|
|
||||||
|
for (let host of val) {
|
||||||
|
ip.v6.encode(host, buf, offset)
|
||||||
|
offset += 16
|
||||||
|
svcparam.encode.bytes += 16
|
||||||
|
}
|
||||||
|
} else if (key == 32769) { //odoh
|
||||||
|
if (svcparam.odoh) {
|
||||||
|
buf.writeUInt16BE(svcparam.odoh.length, offset)
|
||||||
|
offset += 2
|
||||||
|
svcparam.encode.bytes += 2
|
||||||
|
for (let i = 0; i < svcparam.odoh.length; i++) {
|
||||||
|
buf.writeUInt8(svcparam.odoh[i], offset)
|
||||||
|
offset++
|
||||||
|
}
|
||||||
|
svcparam.encode.bytes += svcparam.odoh.length
|
||||||
|
svcparam.odoh = null
|
||||||
|
} else {
|
||||||
|
buf.writeUInt16BE(param.value.length, offset)
|
||||||
|
offset += 2
|
||||||
|
svcparam.encode.bytes += 2
|
||||||
|
buf.write(param.value, offset)
|
||||||
|
offset += param.value.length
|
||||||
|
svcparam.encode.bytes += param.value.length
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// XXX: why would we ever succeed here, why not fail with an exception to let the user know?
|
||||||
|
// Unknown option
|
||||||
|
buf.writeUInt16BE(0, offset) // 0 length since we don't know how to encode
|
||||||
|
offset += 2
|
||||||
|
svcparam.encode.bytes += 2
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
svcparam.encode.bytes = 0;
|
||||||
|
|
||||||
|
svcparam.decode = function (buf, offset) {
|
||||||
|
if (!offset) offset = 0
|
||||||
|
let param = {}
|
||||||
|
let id = buf.readUInt16BE(offset)
|
||||||
|
param.key = svcparam.numberToKeyName(id)
|
||||||
|
offset += 2
|
||||||
|
svcparam.decode.bytes = 2
|
||||||
|
|
||||||
|
let len = buf.readUInt16BE(offset)
|
||||||
|
offset += 2
|
||||||
|
svcparam.decode.bytes += 2
|
||||||
|
|
||||||
|
// decode the svcparam value
|
||||||
|
switch (param.key.toLowerCase()) {
|
||||||
|
case 'mandatory':
|
||||||
|
{
|
||||||
|
let mp = []
|
||||||
|
let paramsoff = offset
|
||||||
|
let rem = len
|
||||||
|
while (rem >= 2) {
|
||||||
|
let paramtype = buf.readUInt16BE(paramsoff)
|
||||||
|
mp.push(svcparam.numberToKeyName(paramtype))
|
||||||
|
paramsoff += 2
|
||||||
|
rem -= 2
|
||||||
|
}
|
||||||
|
param.value = mp
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'alpn':
|
||||||
|
{
|
||||||
|
let names = []
|
||||||
|
let nameoff = offset
|
||||||
|
let rem = len
|
||||||
|
while (rem >= 2) {
|
||||||
|
const namelen = buf.readUint8(nameoff)
|
||||||
|
nameoff++
|
||||||
|
rem--
|
||||||
|
if (namelen > rem) {
|
||||||
|
throw new Error(`Invalid SVCB param ALPN length: ${namelen}. Not enough space left in buffer`)
|
||||||
|
}
|
||||||
|
names.push(buf.toString('utf-8', nameoff, nameoff+namelen))
|
||||||
|
nameoff += namelen
|
||||||
|
rem -= namelen
|
||||||
|
}
|
||||||
|
param.value = names
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'no-default-alpn':
|
||||||
|
// data should be empty
|
||||||
|
param.value = 0
|
||||||
|
break
|
||||||
|
case 'port':
|
||||||
|
param.value = buf.readUInt16BE(offset)
|
||||||
|
break
|
||||||
|
case 'ipv4hint':
|
||||||
|
{
|
||||||
|
let ips = []
|
||||||
|
let ipoff = offset
|
||||||
|
let rem = len
|
||||||
|
while (rem >= 4) {
|
||||||
|
ips.push(ip.v4.decode(buf, ipoff))
|
||||||
|
ipoff += 4
|
||||||
|
rem -= 4
|
||||||
|
}
|
||||||
|
param.value = ips
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'ipv6hint':
|
||||||
|
{
|
||||||
|
let ips = []
|
||||||
|
let ipoff = offset
|
||||||
|
let rem = len
|
||||||
|
while (rem >= 16) {
|
||||||
|
ips.push(ip.v6.decode(buf, ipoff))
|
||||||
|
ipoff += 16
|
||||||
|
rem -= 16
|
||||||
|
}
|
||||||
|
param.value = ips
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
param.value = buf.toString('utf-8', offset, offset + len)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += len
|
||||||
|
svcparam.decode.bytes += len
|
||||||
|
|
||||||
|
return param
|
||||||
|
}
|
||||||
|
|
||||||
|
svcparam.decode.bytes = 0;
|
||||||
|
|
||||||
|
svcparam.encodingLength = function (param) {
|
||||||
|
// 2 bytes for type, 2 bytes for length, what's left for the value
|
||||||
|
|
||||||
|
switch (param.key) {
|
||||||
|
case 'mandatory' : return 4 + 2*(Array.isArray(param.value) ? param.value.length : 1)
|
||||||
|
case 'alpn' : {
|
||||||
|
let val = param.value
|
||||||
|
if (!Array.isArray(val)) val = [val]
|
||||||
|
let total = val.reduce(function(result, id) {
|
||||||
|
return result += id.length
|
||||||
|
}, val.length)
|
||||||
|
return 4 + total
|
||||||
|
}
|
||||||
|
case 'no-default-alpn' : return 4
|
||||||
|
case 'port' : return 4 + 2
|
||||||
|
case 'ipv4hint' : return 4 + 4 * (Array.isArray(param.value) ? param.value.length : 1)
|
||||||
|
case 'echconfig' : {
|
||||||
|
if (param.needBase64Decode) {
|
||||||
|
svcparam.ech = Buffer.from(param.value, "base64")
|
||||||
|
return 4 + svcparam.ech.length
|
||||||
|
}
|
||||||
|
return 4 + param.value.length
|
||||||
|
}
|
||||||
|
case 'ipv6hint' : return 4 + 16 * (Array.isArray(param.value) ? param.value.length : 1)
|
||||||
|
case 'odoh' : {
|
||||||
|
if (param.needBase64Decode) {
|
||||||
|
svcparam.odoh = Buffer.from(param.value, "base64")
|
||||||
|
return 4 + svcparam.odoh.length
|
||||||
|
}
|
||||||
|
return 4 + param.value.length
|
||||||
|
}
|
||||||
|
case 'key65535' : return 4
|
||||||
|
default: return 4 // unknown option
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rhttpssvc = exports.httpssvc = {}
|
||||||
|
|
||||||
|
rhttpssvc.encode = function(data, buf, offset) {
|
||||||
|
if (!buf) buf = Buffer.allocUnsafe(rhttpssvc.encodingLength(data))
|
||||||
|
if (!offset) offset = 0
|
||||||
|
|
||||||
|
buf.writeUInt16BE(rhttpssvc.encodingLength(data) - 2 , offset)
|
||||||
|
offset += 2
|
||||||
|
|
||||||
|
buf.writeUInt16BE(data.priority || 0, offset)
|
||||||
|
rhttpssvc.encode.bytes = 4
|
||||||
|
offset += 2
|
||||||
|
name.encode(data.name, buf, offset)
|
||||||
|
rhttpssvc.encode.bytes += name.encode.bytes
|
||||||
|
offset += name.encode.bytes
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(data.values || {})) {
|
||||||
|
let val = {key, value}
|
||||||
|
svcparam.encode(val, buf, offset)
|
||||||
|
offset += svcparam.encode.bytes
|
||||||
|
rhttpssvc.encode.bytes += svcparam.encode.bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
rhttpssvc.encode.bytes = 0
|
||||||
|
|
||||||
|
rhttpssvc.decode = function (buf, offset) {
|
||||||
|
if (!offset) offset = 0
|
||||||
|
let rdlen = buf.readUInt16BE(offset)
|
||||||
|
let oldOffset = offset
|
||||||
|
offset += 2
|
||||||
|
let record = {}
|
||||||
|
record.priority = buf.readUInt16BE(offset)
|
||||||
|
offset += 2
|
||||||
|
rhttpssvc.decode.bytes = 4
|
||||||
|
record.name = name.decode(buf, offset)
|
||||||
|
offset += name.decode.bytes
|
||||||
|
rhttpssvc.decode.bytes += name.decode.bytes
|
||||||
|
|
||||||
|
while (rdlen > rhttpssvc.decode.bytes - 2) {
|
||||||
|
let rec1 = svcparam.decode(buf, offset)
|
||||||
|
offset += svcparam.decode.bytes
|
||||||
|
rhttpssvc.decode.bytes += svcparam.decode.bytes
|
||||||
|
if (!record.values) {
|
||||||
|
record.values = {}
|
||||||
|
}
|
||||||
|
record.values[rec1.key] = rec1.value
|
||||||
|
}
|
||||||
|
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
|
||||||
|
rhttpssvc.decode.bytes = 0;
|
||||||
|
|
||||||
|
rhttpssvc.encodingLength = function (data) {
|
||||||
|
let len =
|
||||||
|
2 + // rdlen
|
||||||
|
2 + // priority
|
||||||
|
name.encodingLength(data.name)
|
||||||
|
for (const [key, value] of Object.entries(data.values || {})) {
|
||||||
|
len += svcparam.encodingLength({key, value})
|
||||||
|
}
|
||||||
|
return len
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const renc = exports.record = function (type) {
|
const renc = exports.record = function (type) {
|
||||||
switch (type.toUpperCase()) {
|
switch (type.toUpperCase()) {
|
||||||
case 'A': return ra
|
case 'A': return ra
|
||||||
@ -1498,6 +1864,7 @@ const renc = exports.record = function (type) {
|
|||||||
case 'DS': return rds
|
case 'DS': return rds
|
||||||
case 'NAPTR': return rnaptr
|
case 'NAPTR': return rnaptr
|
||||||
case 'TLSA': return rtlsa
|
case 'TLSA': return rtlsa
|
||||||
|
case 'HTTPS': return rhttpssvc
|
||||||
}
|
}
|
||||||
return runknown
|
return runknown
|
||||||
}
|
}
|
||||||
|
|||||||
242
test.js
242
test.js
@ -6,6 +6,10 @@ const rcodes = require('./rcodes')
|
|||||||
const opcodes = require('./opcodes')
|
const opcodes = require('./opcodes')
|
||||||
const optioncodes = require('./optioncodes')
|
const optioncodes = require('./optioncodes')
|
||||||
|
|
||||||
|
const ip = require('@leichtgewicht/ip-codec')
|
||||||
|
|
||||||
|
const hexdump = require('./hexdump')
|
||||||
|
|
||||||
tape('unknown', function (t) {
|
tape('unknown', function (t) {
|
||||||
testEncoder(t, packet.unknown, Buffer.from('hello world'))
|
testEncoder(t, packet.unknown, Buffer.from('hello world'))
|
||||||
t.end()
|
t.end()
|
||||||
@ -643,6 +647,244 @@ tape('tlsa', function (t) {
|
|||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const unhexlify = (hex) => {
|
||||||
|
return Buffer.from(hex.match(/[\da-f]{2}/gi).map(function (h) {return parseInt(h, 16)}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// <HTTPS SVCB test cases>
|
||||||
|
const debug_https = false
|
||||||
|
const test_http_decode_encode = (t, testname, packetbuf, expected, skip_encode=false, skip_memcmp_bufs=false) => {
|
||||||
|
const decoded = packet.httpssvc.decode(packetbuf, 0)
|
||||||
|
if (debug_https) {
|
||||||
|
console.log(`${testname}: decode:`)
|
||||||
|
console.log(JSON.stringify(decoded, null, 2))
|
||||||
|
}
|
||||||
|
t.ok(compare(t, decoded, expected), testname + ' decode')
|
||||||
|
const encoded = packet.httpssvc.encode(expected)
|
||||||
|
if (!skip_encode) {
|
||||||
|
if (debug_https) {
|
||||||
|
console.log(`${testname}: encode:`)
|
||||||
|
hexdump(encoded)
|
||||||
|
}
|
||||||
|
if (!skip_memcmp_bufs) {
|
||||||
|
t.ok(compare(t, packetbuf, encoded), testname + ' encode memcmp')
|
||||||
|
}
|
||||||
|
// now decode the encoded buffer and check for sameness
|
||||||
|
const recoded = packet.httpssvc.decode(encoded, 0)
|
||||||
|
if (debug_https) {
|
||||||
|
console.log(`${testname}: recode`)
|
||||||
|
console.log(JSON.stringify(decoded, null, 2))
|
||||||
|
}
|
||||||
|
t.ok(compare(t, recoded, expected), testname + ' recode')
|
||||||
|
}
|
||||||
|
return encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
tape('https svcb', function (t) {
|
||||||
|
// see https://datatracker.ietf.org/doc/rfc9460/ for the test vectors
|
||||||
|
|
||||||
|
// https AliasMode
|
||||||
|
test_http_decode_encode(t, 'https rfc9460 case1 (AliasMode)',
|
||||||
|
unhexlify(
|
||||||
|
"00 13" + // rdata len
|
||||||
|
"00 00" + // priority
|
||||||
|
"03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00" //target
|
||||||
|
),
|
||||||
|
{
|
||||||
|
priority: 0,
|
||||||
|
name: 'foo.example.com'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// https target name is "."
|
||||||
|
test_http_decode_encode(t, 'https rfc9460 case2 (target name ".")',
|
||||||
|
unhexlify(
|
||||||
|
"00 03" + // rdata len
|
||||||
|
"00 01" + // priority
|
||||||
|
"00" // target (root label)
|
||||||
|
),
|
||||||
|
{
|
||||||
|
priority: 1,
|
||||||
|
name: '.'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// https port
|
||||||
|
test_http_decode_encode(t, 'https rfc9460 case3 (port)',
|
||||||
|
unhexlify(
|
||||||
|
"00 19" + // rdata len
|
||||||
|
"00 10" + // priority
|
||||||
|
"03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00" + // target
|
||||||
|
"00 03" + // key 3
|
||||||
|
"00 02" + // length 2
|
||||||
|
"00 35" // value - target (root label)
|
||||||
|
),
|
||||||
|
{
|
||||||
|
priority: 16,
|
||||||
|
name: 'foo.example.com',
|
||||||
|
values: {
|
||||||
|
port: 53
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// https generic key and value
|
||||||
|
test_http_decode_encode(t, 'https rfc9460 case4 (generic key,val)',
|
||||||
|
unhexlify(
|
||||||
|
"00 1c" + // rdata len
|
||||||
|
"00 01" + // priority
|
||||||
|
"03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00" + // target
|
||||||
|
"02 9b" + // key 667
|
||||||
|
"00 05" + // length 5
|
||||||
|
"68 65 6c 6c 6f" // value
|
||||||
|
),
|
||||||
|
{
|
||||||
|
priority: 1,
|
||||||
|
name: 'foo.example.com',
|
||||||
|
values: {
|
||||||
|
key667: 'hello'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true //skip_encode, we cannot encode an unknown key
|
||||||
|
)
|
||||||
|
|
||||||
|
// https generic key and value with decimal escape
|
||||||
|
test_http_decode_encode(t, 'https rfc9460 case5 (generic key,val with escape)',
|
||||||
|
unhexlify(
|
||||||
|
"00 20" + // rdata len
|
||||||
|
"00 01" + // priority
|
||||||
|
"03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00" + // target
|
||||||
|
"02 9b" + // key 667
|
||||||
|
"00 09" + // length 9
|
||||||
|
"68 65 6c 6c 6f d2 71 6f 6f" // value
|
||||||
|
),
|
||||||
|
{
|
||||||
|
priority: 1,
|
||||||
|
name: 'foo.example.com',
|
||||||
|
values: {
|
||||||
|
key667: unhexlify("68 65 6c 6c 6f d2 71 6f 6f").toString("utf-8")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true // skip_encode, we cannot encode an unknown key
|
||||||
|
)
|
||||||
|
|
||||||
|
// https two quoted ipv6 hints
|
||||||
|
test_http_decode_encode(t, 'https rfc9460 case6 (ipv6hint)',
|
||||||
|
unhexlify(
|
||||||
|
"00 37" + // rdata len
|
||||||
|
"00 01" + // priority
|
||||||
|
"03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00" + // target
|
||||||
|
"00 06" + // key 6
|
||||||
|
"00 20" + // length 32
|
||||||
|
"20 01 0d b8 00 00 00 00 00 00 00 00 00 00 00 01" + // first address
|
||||||
|
"20 01 0d b8 00 00 00 00 00 00 00 00 00 53 00 01" // second address
|
||||||
|
),
|
||||||
|
{
|
||||||
|
priority: 1,
|
||||||
|
name: "foo.example.com",
|
||||||
|
values: {
|
||||||
|
ipv6hint: [
|
||||||
|
"2001:db8::1",
|
||||||
|
"2001:db8::53:1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// https ipv6 hint using embedded ipv4 syntax [2001:db8:122:344::192.0.2.33]
|
||||||
|
test_http_decode_encode(t, 'https rfc9460 case7 (ipv6hint v4 syntax)',
|
||||||
|
unhexlify(
|
||||||
|
"00 23" + // rdata len
|
||||||
|
"00 01" + // priority
|
||||||
|
"07 65 78 61 6d 70 6c 65 03 63 6f 6d 00" + // target
|
||||||
|
"00 06" + // key 6
|
||||||
|
"00 10" + // length 16
|
||||||
|
"20 01 0d b8 01 22 03 44 00 00 00 00 c0 00 02 21" // address
|
||||||
|
),
|
||||||
|
{
|
||||||
|
priority: 1,
|
||||||
|
name: "example.com",
|
||||||
|
values: {
|
||||||
|
ipv6hint: [ ip.v6.decode(ip.v6.encode("2001:db8:122:344::192.0.2.33")) ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// case 8
|
||||||
|
// https 16 foo.example.org. (
|
||||||
|
// alpn=h2,h3-19 mandatory=ipv4hint,alpn
|
||||||
|
// ipv4hint=192.0.2.1
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// note: this may not encode to the same buffer due to internal js
|
||||||
|
// ordering of the `values` object storing the params
|
||||||
|
test_http_decode_encode(t, 'https rfc9460 case8 (alpn,mandatory,ipv4hint)',
|
||||||
|
unhexlify(
|
||||||
|
"00 30" + // rdata len
|
||||||
|
"00 10" + // priority
|
||||||
|
"03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 6f 72 67 00" + // target
|
||||||
|
"00 00" + // key 0
|
||||||
|
"00 04" + // param length 4
|
||||||
|
"00 01" + // value: key 1
|
||||||
|
"00 04" + // value: key 4
|
||||||
|
"00 01" + // key 1
|
||||||
|
"00 09" + // param length 9
|
||||||
|
"02" + // alpn length 2
|
||||||
|
"68 32" + // alpn value
|
||||||
|
"05" + // alpn length 5
|
||||||
|
"68 33 2d 31 39" + // alpn value
|
||||||
|
"00 04" + // key 4
|
||||||
|
"00 04" + // param length 4
|
||||||
|
"c0 00 02 01" // param value
|
||||||
|
),
|
||||||
|
{
|
||||||
|
priority: 16,
|
||||||
|
name: "foo.example.org",
|
||||||
|
values: {
|
||||||
|
mandatory: [
|
||||||
|
"alpn",
|
||||||
|
"ipv4hint"
|
||||||
|
],
|
||||||
|
alpn: [
|
||||||
|
"h2",
|
||||||
|
"h3-19"
|
||||||
|
],
|
||||||
|
ipv4hint: [
|
||||||
|
"192.0.2.1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
false, // skip_encode: false
|
||||||
|
true, // skip_memcmp_bufs: true, do not directly memcmp the resulting bufs, see comment above
|
||||||
|
)
|
||||||
|
|
||||||
|
testEncoder(t, packet.httpssvc, {
|
||||||
|
priority: 16,
|
||||||
|
name: "foo.example.org",
|
||||||
|
values: {
|
||||||
|
mandatory: [
|
||||||
|
"alpn",
|
||||||
|
"ipv4hint"
|
||||||
|
],
|
||||||
|
alpn: [
|
||||||
|
"h2",
|
||||||
|
"h3-19"
|
||||||
|
],
|
||||||
|
ipv4hint: [
|
||||||
|
"192.0.2.1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
|
||||||
|
// </HTTPS SVCB test cases>
|
||||||
|
|
||||||
|
|
||||||
tape('unpack', function (t) {
|
tape('unpack', function (t) {
|
||||||
const buf = Buffer.from([
|
const buf = Buffer.from([
|
||||||
0x00, 0x79,
|
0x00, 0x79,
|
||||||
|
|||||||
2
types.js
2
types.js
@ -45,6 +45,7 @@ exports.toString = function (type) {
|
|||||||
case 252: return 'AXFR'
|
case 252: return 'AXFR'
|
||||||
case 251: return 'IXFR'
|
case 251: return 'IXFR'
|
||||||
case 41: return 'OPT'
|
case 41: return 'OPT'
|
||||||
|
case 65: return 'HTTPS'
|
||||||
case 255: return 'ANY'
|
case 255: return 'ANY'
|
||||||
}
|
}
|
||||||
return 'UNKNOWN_' + type
|
return 'UNKNOWN_' + type
|
||||||
@ -95,6 +96,7 @@ exports.toType = function (name) {
|
|||||||
case 'AXFR': return 252
|
case 'AXFR': return 252
|
||||||
case 'IXFR': return 251
|
case 'IXFR': return 251
|
||||||
case 'OPT': return 41
|
case 'OPT': return 41
|
||||||
|
case 'HTTPS': return 65
|
||||||
case 'ANY': return 255
|
case 'ANY': return 255
|
||||||
case '*': return 255
|
case '*': return 255
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user