Compare commits
14
Commits
dfe5e3f86b
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2fff8bf74 | ||
|
|
65b05eb449 | ||
|
|
777f92260e | ||
|
|
5c37d07fcf | ||
|
|
a260a8386b | ||
|
|
ee83912dcd | ||
|
|
8c70ce60a5 | ||
|
|
74972afbbf | ||
|
|
577e88a9d7 | ||
|
|
ab3c5ca3f5 | ||
|
|
6bdb761479 | ||
|
|
380364bd5a | ||
|
|
2f0d514df0 | ||
|
|
8d78b4b53a |
@@ -0,0 +1,204 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
// Allocate a new Uint8Array buffer
|
||||||
|
function allocBuffer (size) {
|
||||||
|
return new Uint8Array(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate an uninitialized Uint8Array (same as allocBuffer for Uint8Array)
|
||||||
|
function allocUnsafeBuffer (size) {
|
||||||
|
return new Uint8Array(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if an object is a Uint8Array
|
||||||
|
function isBuffer (obj) {
|
||||||
|
return obj instanceof Uint8Array
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Uint8Array from data with optional encoding
|
||||||
|
function fromBuffer (data, encoding) {
|
||||||
|
if (data instanceof Uint8Array) {
|
||||||
|
return new Uint8Array(data)
|
||||||
|
}
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return new Uint8Array(data)
|
||||||
|
}
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
if (encoding === 'base64') {
|
||||||
|
return base64ToBuffer(data)
|
||||||
|
} else if (encoding === 'hex') {
|
||||||
|
return hexToBuffer(data)
|
||||||
|
} else {
|
||||||
|
return stringToBuffer(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Uint8Array(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert string to Uint8Array (UTF-8 encoding)
|
||||||
|
function stringToBuffer (str) {
|
||||||
|
const encoder = new TextEncoder()
|
||||||
|
return encoder.encode(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert Base64 string to Uint8Array
|
||||||
|
function base64ToBuffer (base64Str) {
|
||||||
|
if (typeof window !== 'undefined' && window.atob) {
|
||||||
|
// Browser environment
|
||||||
|
const binaryString = window.atob(base64Str)
|
||||||
|
const bytes = new Uint8Array(binaryString.length)
|
||||||
|
for (let i = 0; i < binaryString.length; i++) {
|
||||||
|
bytes[i] = binaryString.charCodeAt(i)
|
||||||
|
}
|
||||||
|
return bytes
|
||||||
|
} else {
|
||||||
|
// Node.js environment
|
||||||
|
return new Uint8Array(Buffer.from(base64Str, 'base64'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert hex string to Uint8Array
|
||||||
|
function hexToBuffer (hexStr) {
|
||||||
|
const bytes = new Uint8Array(hexStr.length / 2)
|
||||||
|
for (let i = 0; i < hexStr.length; i += 2) {
|
||||||
|
bytes[i / 2] = parseInt(hexStr.substr(i, 2), 16)
|
||||||
|
}
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert Uint8Array to hex string
|
||||||
|
function bufferToHex (buf) {
|
||||||
|
let hex = ''
|
||||||
|
for (let i = 0; i < buf.length; i++) {
|
||||||
|
const byte = buf[i]
|
||||||
|
hex += (byte < 16 ? '0' : '') + byte.toString(16)
|
||||||
|
}
|
||||||
|
return hex.toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert Uint8Array to string
|
||||||
|
function bufferToString (buf, encoding, start, end) {
|
||||||
|
encoding = encoding || 'utf-8'
|
||||||
|
start = start || 0
|
||||||
|
end = end || buf.length
|
||||||
|
|
||||||
|
if (encoding !== 'utf-8' && encoding !== 'utf8') {
|
||||||
|
throw new Error('Unsupported encoding: ' + encoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
const slice = buf.slice(start, end)
|
||||||
|
const decoder = new TextDecoder('utf-8')
|
||||||
|
return decoder.decode(slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write string to buffer, return number of bytes written
|
||||||
|
function writeString (buf, str, offset) {
|
||||||
|
offset = offset || 0
|
||||||
|
const encoded = stringToBuffer(str)
|
||||||
|
for (let i = 0; i < encoded.length && offset + i < buf.length; i++) {
|
||||||
|
buf[offset + i] = encoded[i]
|
||||||
|
}
|
||||||
|
return encoded.length
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get byte length of a string
|
||||||
|
function byteLength (str) {
|
||||||
|
const encoder = new TextEncoder()
|
||||||
|
return encoder.encode(str).length
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read big-endian 16-bit unsigned integer
|
||||||
|
function readUInt16BE (buf, offset) {
|
||||||
|
offset = offset || 0
|
||||||
|
return (buf[offset] << 8) | buf[offset + 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write big-endian 16-bit unsigned integer
|
||||||
|
function writeUInt16BE (buf, value, offset) {
|
||||||
|
offset = offset || 0
|
||||||
|
buf[offset] = (value >> 8) & 0xff
|
||||||
|
buf[offset + 1] = value & 0xff
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read big-endian 32-bit unsigned integer
|
||||||
|
function readUInt32BE (buf, offset) {
|
||||||
|
offset = offset || 0
|
||||||
|
return ((buf[offset] << 24) | (buf[offset + 1] << 16) | (buf[offset + 2] << 8) | buf[offset + 3]) >>> 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write big-endian 32-bit unsigned integer
|
||||||
|
function writeUInt32BE (buf, value, offset) {
|
||||||
|
offset = offset || 0
|
||||||
|
buf[offset] = (value >> 24) & 0xff
|
||||||
|
buf[offset + 1] = (value >> 16) & 0xff
|
||||||
|
buf[offset + 2] = (value >> 8) & 0xff
|
||||||
|
buf[offset + 3] = value & 0xff
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read 8-bit unsigned integer
|
||||||
|
function readUInt8 (buf, offset) {
|
||||||
|
offset = offset || 0
|
||||||
|
return buf[offset]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write 8-bit unsigned integer
|
||||||
|
function writeUInt8 (buf, value, offset) {
|
||||||
|
offset = offset || 0
|
||||||
|
buf[offset] = value & 0xff
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy buffer contents
|
||||||
|
function bufferCopy (source, target, targetStart, sourceStart, sourceEnd) {
|
||||||
|
targetStart = targetStart || 0
|
||||||
|
sourceStart = sourceStart || 0
|
||||||
|
sourceEnd = sourceEnd !== undefined ? sourceEnd : source.length
|
||||||
|
|
||||||
|
for (let i = sourceStart; i < sourceEnd; i++) {
|
||||||
|
target[targetStart + (i - sourceStart)] = source[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concatenate multiple buffers
|
||||||
|
function bufferConcat (buffers, totalLength) {
|
||||||
|
if (!Array.isArray(buffers)) {
|
||||||
|
throw new Error('buffers must be an Array')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffers.length === 0) {
|
||||||
|
return new Uint8Array(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
totalLength = totalLength || buffers.reduce((sum, buf) => sum + buf.length, 0)
|
||||||
|
const result = new Uint8Array(totalLength)
|
||||||
|
|
||||||
|
let offset = 0
|
||||||
|
for (let i = 0; i < buffers.length; i++) {
|
||||||
|
const buf = buffers[i]
|
||||||
|
result.set(buf, offset)
|
||||||
|
offset += buf.length
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
allocBuffer,
|
||||||
|
allocUnsafeBuffer,
|
||||||
|
isBuffer,
|
||||||
|
fromBuffer,
|
||||||
|
stringToBuffer,
|
||||||
|
base64ToBuffer,
|
||||||
|
hexToBuffer,
|
||||||
|
bufferToHex,
|
||||||
|
bufferToString,
|
||||||
|
writeString,
|
||||||
|
byteLength,
|
||||||
|
readUInt8,
|
||||||
|
writeUInt8,
|
||||||
|
readUInt16BE,
|
||||||
|
writeUInt16BE,
|
||||||
|
readUInt32BE,
|
||||||
|
writeUInt32BE,
|
||||||
|
bufferCopy,
|
||||||
|
bufferConcat
|
||||||
|
}
|
||||||
@@ -12,6 +12,9 @@ exports.toString = function (klass) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exports.toClass = function (name) {
|
exports.toClass = function (name) {
|
||||||
|
if (typeof name === 'number') {
|
||||||
|
return name
|
||||||
|
}
|
||||||
switch (name.toUpperCase()) {
|
switch (name.toUpperCase()) {
|
||||||
case 'IN': return 1
|
case 'IN': return 1
|
||||||
case 'CS': return 2
|
case 'CS': return 2
|
||||||
|
|||||||
@@ -0,0 +1,277 @@
|
|||||||
|
import {
|
||||||
|
Answer,
|
||||||
|
decode,
|
||||||
|
DecodedPacket,
|
||||||
|
encode,
|
||||||
|
encodingLength,
|
||||||
|
Packet,
|
||||||
|
Question,
|
||||||
|
RECURSION_DESIRED,
|
||||||
|
streamDecode,
|
||||||
|
streamEncode,
|
||||||
|
} from "dns-packet";
|
||||||
|
|
||||||
|
const answer: Answer = {
|
||||||
|
type: "A",
|
||||||
|
name: "localhost",
|
||||||
|
ttl: 3600,
|
||||||
|
data: "127.0.0.1",
|
||||||
|
class: "ANY",
|
||||||
|
flush: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const question: Question = {
|
||||||
|
type: "A",
|
||||||
|
name: "localhost",
|
||||||
|
class: "IN",
|
||||||
|
};
|
||||||
|
|
||||||
|
const inPacket: Packet = {
|
||||||
|
additionals: [answer],
|
||||||
|
authorities: [answer],
|
||||||
|
answers: [answer],
|
||||||
|
flags: 0,
|
||||||
|
id: 0,
|
||||||
|
questions: [question],
|
||||||
|
type: "query",
|
||||||
|
};
|
||||||
|
|
||||||
|
const inputBuf = new Uint8Array(0);
|
||||||
|
const length: number = encodingLength(inPacket);
|
||||||
|
const out: Uint8Array = encode(inPacket, inputBuf, length - length);
|
||||||
|
const outPacket: DecodedPacket = decode(out, 0);
|
||||||
|
const flag_qr: boolean = outPacket.flag_qr;
|
||||||
|
const flag_aa: boolean = outPacket.flag_aa;
|
||||||
|
const flag_tc: boolean = outPacket.flag_tc;
|
||||||
|
const flag_rd: boolean = outPacket.flag_rd;
|
||||||
|
const flag_ra: boolean = outPacket.flag_ra;
|
||||||
|
const flag_z: boolean = outPacket.flag_z;
|
||||||
|
const flag_ad: boolean = outPacket.flag_ad;
|
||||||
|
const flag_cd: boolean = outPacket.flag_cd;
|
||||||
|
|
||||||
|
encode(outPacket);
|
||||||
|
|
||||||
|
const records: Answer[] = [
|
||||||
|
{
|
||||||
|
type: "A",
|
||||||
|
name: "localhost",
|
||||||
|
data: "127.0.0.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "AAAA",
|
||||||
|
name: "localhost",
|
||||||
|
data: "::1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "CNAME",
|
||||||
|
name: "localhost",
|
||||||
|
data: "example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "DNSKEY",
|
||||||
|
name: "localhost",
|
||||||
|
data: {
|
||||||
|
algorithm: 1,
|
||||||
|
flags: 257,
|
||||||
|
key: Buffer.from("test"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "DS",
|
||||||
|
name: "localhost",
|
||||||
|
data: {
|
||||||
|
keyTag: 12345,
|
||||||
|
algorithm: 8,
|
||||||
|
digestType: 1,
|
||||||
|
digest: Buffer.from("test"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "NAPTR",
|
||||||
|
name: "localhost",
|
||||||
|
data: {
|
||||||
|
order: 100,
|
||||||
|
preference: 10,
|
||||||
|
flags: "s",
|
||||||
|
services: "SIP+D2U",
|
||||||
|
regexp: "!^.*$!sip:[email protected]!",
|
||||||
|
replacement: "_sip._udp.example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "NS",
|
||||||
|
name: "localhost",
|
||||||
|
data: "ns1.localhost",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "NSEC",
|
||||||
|
name: "localhost",
|
||||||
|
data: {
|
||||||
|
nextDomain: "a.domain",
|
||||||
|
rrtypes: ["A", "TXT", "RRSIG"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "NSEC3",
|
||||||
|
name: "localhost",
|
||||||
|
data: {
|
||||||
|
algorithm: 1,
|
||||||
|
flags: 0,
|
||||||
|
iterations: 2,
|
||||||
|
salt: Buffer.from("test"),
|
||||||
|
nextDomain: Buffer.from("test"), // Hashed per RFC5155
|
||||||
|
rrtypes: ["A", "TXT", "RRSIG"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "MX",
|
||||||
|
name: "localhost",
|
||||||
|
data: {
|
||||||
|
preference: 10,
|
||||||
|
exchange: "mx.localhost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "TXT",
|
||||||
|
name: "localhost",
|
||||||
|
data: "test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "TXT",
|
||||||
|
name: "localhost",
|
||||||
|
data: Buffer.from("test"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "TXT",
|
||||||
|
name: "localhost",
|
||||||
|
data: ["foo", "bar"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "SRV",
|
||||||
|
name: "_imap._tcp.localhost",
|
||||||
|
data: {
|
||||||
|
priority: 10,
|
||||||
|
weight: 60,
|
||||||
|
port: 5060,
|
||||||
|
target: "imap.example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "SOA",
|
||||||
|
name: "localhost",
|
||||||
|
data: {
|
||||||
|
mname: "localhost",
|
||||||
|
rname: "hostmaster.localhost",
|
||||||
|
serial: 2021122101,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "CAA",
|
||||||
|
name: "localhost",
|
||||||
|
data: {
|
||||||
|
issuerCritical: false,
|
||||||
|
tag: "issue",
|
||||||
|
value: "ca.example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "TXT",
|
||||||
|
name: "version.bind",
|
||||||
|
class: "CH",
|
||||||
|
data: "1.2.3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "OPT",
|
||||||
|
name: ".",
|
||||||
|
udpPayloadSize: 65535,
|
||||||
|
extendedRcode: 255,
|
||||||
|
ednsVersion: 255,
|
||||||
|
flags: 65535,
|
||||||
|
flag_do: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
code: 8,
|
||||||
|
type: "CLIENT_SUBNET",
|
||||||
|
sourcePrefixLength: 0,
|
||||||
|
scopePrefixLength: 0,
|
||||||
|
ip: "127.0.0.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 8,
|
||||||
|
ip: "127.0.0.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 11,
|
||||||
|
type: "TCP_KEEPALIVE",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 11,
|
||||||
|
timeout: 2468,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 12,
|
||||||
|
length: 13,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 14,
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 14,
|
||||||
|
tags: [256],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "RP",
|
||||||
|
name: "localhost",
|
||||||
|
data: {
|
||||||
|
mbox: "admin.example.com",
|
||||||
|
txt: "txt.example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "RRSIG",
|
||||||
|
name: "localhost",
|
||||||
|
data: {
|
||||||
|
typeCovered: "A",
|
||||||
|
algorithm: 8,
|
||||||
|
labels: 1,
|
||||||
|
originalTTL: 3600,
|
||||||
|
expiration: Date.now(),
|
||||||
|
inception: Date.now(),
|
||||||
|
keyTag: 12345,
|
||||||
|
signersName: "a.name",
|
||||||
|
signature: Buffer.from("test"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "SSHFP",
|
||||||
|
name: "localhost",
|
||||||
|
data: {
|
||||||
|
algorithm: 1,
|
||||||
|
hash: 1,
|
||||||
|
fingerprint: "A108C9F834354D5B37AF988141C9294822F5BC00",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
encode({ answers: records });
|
||||||
|
|
||||||
|
// https://github.com/mafintosh/dns-packet/blob/5aebb85c3221292e994d01b68cadf067e78efabf/examples/tcp.js
|
||||||
|
function getRandomInt(min: number, max: number): number {
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
let buf = streamEncode({
|
||||||
|
type: "query",
|
||||||
|
id: getRandomInt(1, 65534),
|
||||||
|
flags: RECURSION_DESIRED,
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
type: "A",
|
||||||
|
name: "google.com",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
streamDecode(buf);
|
||||||
|
buf = buf.slice(2 + streamDecode.bytes);
|
||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const wout = (s) => {
|
||||||
|
if (typeof process !== 'undefined' && process.stdout) {
|
||||||
|
process.stdout.write(s)
|
||||||
|
} else if (typeof console !== 'undefined') {
|
||||||
|
if (s === '\n') {
|
||||||
|
console.log('')
|
||||||
|
} else {
|
||||||
|
process.stdout = process.stdout || ''
|
||||||
|
process.stdout += s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const padStr = (s, len, ch) => {
|
||||||
|
ch = ch || ' '
|
||||||
|
while (s.length < len) {
|
||||||
|
s = ch + s
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
const hexDigit = (n, len) => {
|
||||||
|
return padStr(n.toString(16), len || 2, '0')
|
||||||
|
}
|
||||||
|
|
||||||
|
const printableCh = (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(hexDigit(i, 4) + ': ')
|
||||||
|
}
|
||||||
|
|
||||||
|
// output the hex digit
|
||||||
|
wout(hexDigit(buf[i]))
|
||||||
|
wout(' ')
|
||||||
|
ascii[i % maxline] = printableCh(buf[i])
|
||||||
|
}
|
||||||
|
if (i % maxline !== 0) {
|
||||||
|
let diff = maxline - (i % maxline)
|
||||||
|
wout(' '.repeat(diff))
|
||||||
|
}
|
||||||
|
|
||||||
|
wout(' ' + ascii.slice(0, (i % maxline)).join('') + '\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
let buf = new Uint8Array(500)
|
||||||
|
for (let i = 0; i < buf.length; i++) {
|
||||||
|
buf[i] = i & 0xff
|
||||||
|
}
|
||||||
|
hexdump(buf)
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = hexdump
|
||||||
Vendored
+597
@@ -0,0 +1,597 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently defined set of DNS record types.
|
||||||
|
*/
|
||||||
|
export type RecordType =
|
||||||
|
| "A"
|
||||||
|
| "AAAA"
|
||||||
|
| "AFSDB"
|
||||||
|
| "APL"
|
||||||
|
| "AXFR"
|
||||||
|
| "CAA"
|
||||||
|
| "CDNSKEY"
|
||||||
|
| "CDS"
|
||||||
|
| "CERT"
|
||||||
|
| "CNAME"
|
||||||
|
| "DNAME"
|
||||||
|
| "DHCID"
|
||||||
|
| "DLV"
|
||||||
|
| "DNSKEY"
|
||||||
|
| "DS"
|
||||||
|
| "HINFO"
|
||||||
|
| "HIP"
|
||||||
|
| "HTTPS"
|
||||||
|
| "IXFR"
|
||||||
|
| "IPSECKEY"
|
||||||
|
| "KEY"
|
||||||
|
| "KX"
|
||||||
|
| "LOC"
|
||||||
|
| "MX"
|
||||||
|
| "NAPTR"
|
||||||
|
| "NS"
|
||||||
|
| "NSEC"
|
||||||
|
| "NSEC3"
|
||||||
|
| "NSEC3PARAM"
|
||||||
|
| "NULL"
|
||||||
|
| "OPT"
|
||||||
|
| "PTR"
|
||||||
|
| "RRSIG"
|
||||||
|
| "RP"
|
||||||
|
| "SIG"
|
||||||
|
| "SOA"
|
||||||
|
| "SPF"
|
||||||
|
| "SRV"
|
||||||
|
| "SSHFP"
|
||||||
|
| "SVCB"
|
||||||
|
| "TA"
|
||||||
|
| "TKEY"
|
||||||
|
| "TLSA"
|
||||||
|
| "TSIG"
|
||||||
|
| "TXT"
|
||||||
|
| "URI";
|
||||||
|
|
||||||
|
export type RecordClass = "IN" | "CS" | "CH" | "HS" | "ANY";
|
||||||
|
|
||||||
|
export type OpCode = "QUERY" | "IQUERY" | "STATUS" | "NOTIFY" | "UPDATE" | string;
|
||||||
|
|
||||||
|
export type RCode = "NOERROR" | "FORMERR" | "SERVFAIL" | "NXDOMAIN" | "NOTIMP" | "REFUSED" | "YXDOMAIN" | "YXRRSET" | "NXRRSET" | "NOTAUTH" | "NOTZONE" | string;
|
||||||
|
|
||||||
|
export interface Question {
|
||||||
|
type: RecordType;
|
||||||
|
name: string;
|
||||||
|
class?: RecordClass | undefined;
|
||||||
|
qu_bit?: boolean | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data interfaces for various record types
|
||||||
|
export interface CaaData {
|
||||||
|
issuerCritical?: boolean | undefined;
|
||||||
|
flags?: number | undefined;
|
||||||
|
tag: "issue" | "issuewild" | "iodef";
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DnskeyData {
|
||||||
|
flags: number;
|
||||||
|
algorithm: number;
|
||||||
|
key: string | Uint8Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DsData {
|
||||||
|
keyTag: number;
|
||||||
|
algorithm: number;
|
||||||
|
digestType: number;
|
||||||
|
digest: string | Uint8Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HInfoData {
|
||||||
|
cpu: string;
|
||||||
|
os: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MxData {
|
||||||
|
preference?: number | undefined;
|
||||||
|
exchange: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NaptrData {
|
||||||
|
order: number;
|
||||||
|
preference: number;
|
||||||
|
flags: string;
|
||||||
|
services: string;
|
||||||
|
regexp: string;
|
||||||
|
replacement: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NsecData {
|
||||||
|
nextDomain: string;
|
||||||
|
rrtypes: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Nsec3Data {
|
||||||
|
algorithm: number;
|
||||||
|
flags: number;
|
||||||
|
iterations: number;
|
||||||
|
salt: Uint8Array;
|
||||||
|
nextDomain: Uint8Array;
|
||||||
|
rrtypes: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RpData {
|
||||||
|
mbox: string;
|
||||||
|
txt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RrsigData {
|
||||||
|
typeCovered: RecordType | string;
|
||||||
|
algorithm: number;
|
||||||
|
labels: number;
|
||||||
|
originalTTL: number;
|
||||||
|
expiration: number;
|
||||||
|
inception: number;
|
||||||
|
keyTag: number;
|
||||||
|
signersName: string;
|
||||||
|
signature: string | Uint8Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SrvData {
|
||||||
|
port: number;
|
||||||
|
target: string;
|
||||||
|
priority?: number | undefined;
|
||||||
|
weight?: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SoaData {
|
||||||
|
mname: string;
|
||||||
|
rname: string;
|
||||||
|
serial?: number | undefined;
|
||||||
|
refresh?: number | undefined;
|
||||||
|
retry?: number | undefined;
|
||||||
|
expire?: number | undefined;
|
||||||
|
minimum?: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SshfpData {
|
||||||
|
algorithm: number;
|
||||||
|
hash: number;
|
||||||
|
fingerprint: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TlsaData {
|
||||||
|
usage: number;
|
||||||
|
selector: number;
|
||||||
|
matchingType: number;
|
||||||
|
certificate: string | Uint8Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TxtData = string | Uint8Array | Array<string | Uint8Array>;
|
||||||
|
|
||||||
|
// SVCB/HTTPS Parameter types
|
||||||
|
export interface SvcParamMandatory {
|
||||||
|
key: "mandatory" | 0;
|
||||||
|
value: Array<string | number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SvcParamAlpn {
|
||||||
|
key: "alpn" | 1;
|
||||||
|
value: string | string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SvcParamNoDefaultAlpn {
|
||||||
|
key: "no-default-alpn" | 2;
|
||||||
|
value?: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SvcParamPort {
|
||||||
|
key: "port" | 3;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SvcParamIpv4Hint {
|
||||||
|
key: "ipv4hint" | 4;
|
||||||
|
value: string | string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SvcParamEchConfig {
|
||||||
|
key: "echconfig" | 5;
|
||||||
|
value: string | Uint8Array;
|
||||||
|
needBase64Decode?: boolean | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SvcParamIpv6Hint {
|
||||||
|
key: "ipv6hint" | 6;
|
||||||
|
value: string | string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SvcParamDohPath {
|
||||||
|
key: "dohpath" | 7;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SvcParamOdoh {
|
||||||
|
key: "odoh" | 32769;
|
||||||
|
value: string | Uint8Array;
|
||||||
|
needBase64Decode?: boolean | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SvcParamUnknown {
|
||||||
|
key: string | number;
|
||||||
|
value?: unknown | undefined;
|
||||||
|
data?: Uint8Array | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SvcParam =
|
||||||
|
| SvcParamMandatory
|
||||||
|
| SvcParamAlpn
|
||||||
|
| SvcParamNoDefaultAlpn
|
||||||
|
| SvcParamPort
|
||||||
|
| SvcParamIpv4Hint
|
||||||
|
| SvcParamEchConfig
|
||||||
|
| SvcParamIpv6Hint
|
||||||
|
| SvcParamDohPath
|
||||||
|
| SvcParamOdoh
|
||||||
|
| SvcParamUnknown;
|
||||||
|
|
||||||
|
export interface HttpsData {
|
||||||
|
priority: number;
|
||||||
|
name: string;
|
||||||
|
values?: Record<string, SvcParam["value"]> | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SvcbData {
|
||||||
|
priority: number;
|
||||||
|
name: string;
|
||||||
|
values?: Record<string, SvcParam["value"]> | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic answer types
|
||||||
|
export interface GenericAnswer<T> {
|
||||||
|
type: T;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BaseAnswer<T, D> extends GenericAnswer<T> {
|
||||||
|
ttl?: number | undefined;
|
||||||
|
class?: RecordClass | undefined;
|
||||||
|
flush?: boolean | undefined;
|
||||||
|
data: D;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Answer type groupings
|
||||||
|
export type StringRecordType = "A" | "AAAA" | "CNAME" | "DNAME" | "NS" | "PTR";
|
||||||
|
|
||||||
|
export type OtherRecordType =
|
||||||
|
| "AFSDB"
|
||||||
|
| "APL"
|
||||||
|
| "AXFR"
|
||||||
|
| "CDNSKEY"
|
||||||
|
| "CDS"
|
||||||
|
| "CERT"
|
||||||
|
| "DHCID"
|
||||||
|
| "DLV"
|
||||||
|
| "HIP"
|
||||||
|
| "IPSECKEY"
|
||||||
|
| "IXFR"
|
||||||
|
| "KEY"
|
||||||
|
| "KX"
|
||||||
|
| "LOC"
|
||||||
|
| "NSEC3PARAM"
|
||||||
|
| "NULL"
|
||||||
|
| "SIG"
|
||||||
|
| "TA"
|
||||||
|
| "TKEY"
|
||||||
|
| "TSIG"
|
||||||
|
| "URI";
|
||||||
|
|
||||||
|
// Specific answer types
|
||||||
|
export type StringAnswer = BaseAnswer<StringRecordType, string>;
|
||||||
|
export type BufferAnswer = BaseAnswer<OtherRecordType, Uint8Array>;
|
||||||
|
export type CaaAnswer = BaseAnswer<"CAA", CaaData>;
|
||||||
|
export type DnskeyAnswer = BaseAnswer<"DNSKEY", DnskeyData>;
|
||||||
|
export type DSAnswer = BaseAnswer<"DS", DsData>;
|
||||||
|
export type HInfoAnswer = BaseAnswer<"HINFO", HInfoData>;
|
||||||
|
export type MxAnswer = BaseAnswer<"MX", MxData>;
|
||||||
|
export type NaptrAnswer = BaseAnswer<"NAPTR", NaptrData>;
|
||||||
|
export type Nsec3Answer = BaseAnswer<"NSEC3", Nsec3Data>;
|
||||||
|
export type NsecAnswer = BaseAnswer<"NSEC", NsecData>;
|
||||||
|
export type RpAnswer = BaseAnswer<"RP", RpData>;
|
||||||
|
export type RrsigAnswer = BaseAnswer<"RRSIG", RrsigData>;
|
||||||
|
export type SoaAnswer = BaseAnswer<"SOA", SoaData>;
|
||||||
|
export type SrvAnswer = BaseAnswer<"SRV", SrvData>;
|
||||||
|
export type SshfpAnswer = BaseAnswer<"SSHFP", SshfpData>;
|
||||||
|
export type TlsaAnswer = BaseAnswer<"TLSA", TlsaData>;
|
||||||
|
export type TxtAnswer = BaseAnswer<"TXT", TxtData>;
|
||||||
|
export type SvcbAnswer = BaseAnswer<"SVCB", SvcbData>;
|
||||||
|
export type HttpsAnswer = BaseAnswer<"HTTPS", HttpsData>;
|
||||||
|
|
||||||
|
// OPT record
|
||||||
|
interface OptCodes {
|
||||||
|
OPTION_0: 0;
|
||||||
|
LLQ: 1;
|
||||||
|
UL: 2;
|
||||||
|
NSID: 3;
|
||||||
|
OPTION_4: 4;
|
||||||
|
DAU: 5;
|
||||||
|
DHU: 6;
|
||||||
|
N3U: 7;
|
||||||
|
CLIENT_SUBNET: 8;
|
||||||
|
EXPIRE: 9;
|
||||||
|
COOKIE: 10;
|
||||||
|
TCP_KEEPALIVE: 11;
|
||||||
|
PADDING: 12;
|
||||||
|
CHAIN: 13;
|
||||||
|
KEY_TAG: 14;
|
||||||
|
DEVICEID: 26946;
|
||||||
|
OPTION_65535: 65535;
|
||||||
|
}
|
||||||
|
|
||||||
|
type OptCodeType = keyof OptCodes;
|
||||||
|
type OptCode<K extends OptCodeType> = OptCodes[K];
|
||||||
|
|
||||||
|
interface GenericOpt<T extends OptCodeType> {
|
||||||
|
code: OptCode<T>;
|
||||||
|
type?: T | undefined;
|
||||||
|
data?: Uint8Array | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ClientSubnetOpt extends GenericOpt<"CLIENT_SUBNET"> {
|
||||||
|
family?: number | undefined;
|
||||||
|
sourcePrefixLength?: number | undefined;
|
||||||
|
scopePrefixLength?: number | undefined;
|
||||||
|
ip?: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KeepAliveOpt extends GenericOpt<"TCP_KEEPALIVE"> {
|
||||||
|
timeout?: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PaddingOpt extends GenericOpt<"PADDING"> {
|
||||||
|
length?: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TagOpt extends GenericOpt<"KEY_TAG"> {
|
||||||
|
tags: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PacketOpt = ClientSubnetOpt | KeepAliveOpt | PaddingOpt | TagOpt;
|
||||||
|
|
||||||
|
export interface OptAnswer extends GenericAnswer<"OPT"> {
|
||||||
|
udpPayloadSize: number;
|
||||||
|
extendedRcode: number;
|
||||||
|
ednsVersion: number;
|
||||||
|
flags: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the DNS DO bit is set
|
||||||
|
*/
|
||||||
|
flag_do: boolean;
|
||||||
|
|
||||||
|
options: PacketOpt[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete answer union type
|
||||||
|
export type Answer =
|
||||||
|
| StringAnswer
|
||||||
|
| BufferAnswer
|
||||||
|
| CaaAnswer
|
||||||
|
| DnskeyAnswer
|
||||||
|
| DSAnswer
|
||||||
|
| HInfoAnswer
|
||||||
|
| HttpsAnswer
|
||||||
|
| MxAnswer
|
||||||
|
| NaptrAnswer
|
||||||
|
| Nsec3Answer
|
||||||
|
| NsecAnswer
|
||||||
|
| OptAnswer
|
||||||
|
| RpAnswer
|
||||||
|
| RrsigAnswer
|
||||||
|
| SoaAnswer
|
||||||
|
| SrvAnswer
|
||||||
|
| SshfpAnswer
|
||||||
|
| SvcbAnswer
|
||||||
|
| TlsaAnswer
|
||||||
|
| TxtAnswer;
|
||||||
|
|
||||||
|
export interface Packet {
|
||||||
|
/**
|
||||||
|
* Whether the packet is a query or a response. This field may be
|
||||||
|
* omitted if it is clear from the context of usage what type of packet
|
||||||
|
* it is.
|
||||||
|
*/
|
||||||
|
type?: "query" | "response" | undefined;
|
||||||
|
|
||||||
|
id?: number | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bit-mask combination of zero or more of:
|
||||||
|
* {@link AUTHORITATIVE_ANSWER},
|
||||||
|
* {@link TRUNCATED_RESPONSE},
|
||||||
|
* {@link RECURSION_DESIRED},
|
||||||
|
* {@link RECURSION_AVAILABLE},
|
||||||
|
* {@link AUTHENTIC_DATA},
|
||||||
|
* {@link CHECKING_DISABLED},
|
||||||
|
* {@link DNSSEC_OK}.
|
||||||
|
*/
|
||||||
|
flags?: number | undefined;
|
||||||
|
|
||||||
|
opcode?: OpCode | number | undefined;
|
||||||
|
rcode?: RCode | number | undefined;
|
||||||
|
|
||||||
|
questions?: Question[] | undefined;
|
||||||
|
answers?: Answer[] | undefined;
|
||||||
|
additionals?: Answer[] | undefined;
|
||||||
|
authorities?: Answer[] | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoded packet with individual flag bits extracted.
|
||||||
|
*/
|
||||||
|
export interface DecodedPacket extends Packet {
|
||||||
|
flag_qr: boolean;
|
||||||
|
flag_aa: boolean;
|
||||||
|
flag_tc: boolean;
|
||||||
|
flag_rd: boolean;
|
||||||
|
flag_ra: boolean;
|
||||||
|
flag_z: boolean;
|
||||||
|
flag_ad: boolean;
|
||||||
|
flag_cd: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
export const AUTHORITATIVE_ANSWER: number;
|
||||||
|
export const TRUNCATED_RESPONSE: number;
|
||||||
|
export const RECURSION_DESIRED: number;
|
||||||
|
export const RECURSION_AVAILABLE: number;
|
||||||
|
export const AUTHENTIC_DATA: number;
|
||||||
|
export const CHECKING_DISABLED: number;
|
||||||
|
export const DNSSEC_OK: number;
|
||||||
|
export const NXDOMAIN: number;
|
||||||
|
|
||||||
|
// Main functions
|
||||||
|
export function encode(packet: Packet, buf?: Uint8Array | ArrayBufferLike, offset?: number): Uint8Array;
|
||||||
|
|
||||||
|
export namespace encode {
|
||||||
|
let bytes: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decode(buf: Uint8Array | ArrayBufferLike, offset?: number): DecodedPacket;
|
||||||
|
|
||||||
|
export namespace decode {
|
||||||
|
let bytes: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodingLength(packet: Packet): number;
|
||||||
|
|
||||||
|
export function streamEncode(packet: Packet): Uint8Array;
|
||||||
|
|
||||||
|
export namespace streamEncode {
|
||||||
|
let bytes: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function streamDecode(buf: Uint8Array | ArrayBufferLike, offset?: number): DecodedPacket | null;
|
||||||
|
|
||||||
|
export namespace streamDecode {
|
||||||
|
let bytes: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility/Helper exports
|
||||||
|
export const name: Codec<string> & {
|
||||||
|
encode(
|
||||||
|
str: string,
|
||||||
|
buf?: Uint8Array | ArrayBufferLike,
|
||||||
|
offset?: number,
|
||||||
|
options?: { mail?: boolean }
|
||||||
|
): Uint8Array;
|
||||||
|
decode(buf: Uint8Array | ArrayBufferLike, offset?: number, options?: { mail?: boolean }): string;
|
||||||
|
encodingLength(name: string): number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const question: Codec<Question>;
|
||||||
|
|
||||||
|
export const answer: Codec<Answer>;
|
||||||
|
|
||||||
|
export const svcparam: Codec<SvcParam> & {
|
||||||
|
keyToNumber(keyName: string): number;
|
||||||
|
numberToKeyName(number: number): string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const svcb: Codec<SvcbData>;
|
||||||
|
|
||||||
|
export const httpssvc: Codec<HttpsData>;
|
||||||
|
|
||||||
|
export const a: Codec<string>;
|
||||||
|
|
||||||
|
export const aaaa: Codec<string>;
|
||||||
|
|
||||||
|
export const cname: Codec<string>;
|
||||||
|
|
||||||
|
export const dname: Codec<string>;
|
||||||
|
|
||||||
|
export const ptr: Codec<string>;
|
||||||
|
|
||||||
|
export const ns: Codec<string>;
|
||||||
|
|
||||||
|
export const mx: Codec<MxData>;
|
||||||
|
|
||||||
|
export const srv: Codec<SrvData>;
|
||||||
|
|
||||||
|
export const caa: Codec<CaaData> & {
|
||||||
|
ISSUER_CRITICAL: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const txt: Codec<Uint8Array[]> & {
|
||||||
|
encode(data: TxtData, buf?: Uint8Array, offset?: number): Uint8Array;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const null_: Codec<Uint8Array> & {
|
||||||
|
encode(data: Uint8Array | string, buf?: Uint8Array, offset?: number): Uint8Array;
|
||||||
|
encodingLength(data?: Uint8Array | string): number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hinfo: Codec<HInfoData>;
|
||||||
|
|
||||||
|
export const soa: Codec<SoaData>;
|
||||||
|
|
||||||
|
export const naptr: Codec<NaptrData>;
|
||||||
|
|
||||||
|
export const dnskey: Codec<DnskeyData> & {
|
||||||
|
PROTOCOL_DNSSEC: number;
|
||||||
|
ZONE_KEY: number;
|
||||||
|
SECURE_ENTRYPOINT: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rrsig: Codec<RrsigData>;
|
||||||
|
|
||||||
|
export const rp: Codec<RpData>;
|
||||||
|
|
||||||
|
export const nsec: Codec<NsecData>;
|
||||||
|
|
||||||
|
export const nsec3: Codec<Nsec3Data>;
|
||||||
|
|
||||||
|
export const ds: Codec<DsData>;
|
||||||
|
|
||||||
|
export const sshfp: Codec<SshfpData> & {
|
||||||
|
getFingerprintLengthForHashType(hashType: number): number | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const tlsa: Codec<TlsaData>;
|
||||||
|
|
||||||
|
export const opt: Codec<PacketOpt[]>;
|
||||||
|
|
||||||
|
export const option: Codec<PacketOpt>;
|
||||||
|
|
||||||
|
export const unknown: Codec<Uint8Array> & {
|
||||||
|
encode(data: Uint8Array | string, buf?: Uint8Array, offset?: number): Uint8Array;
|
||||||
|
encodingLength(data: Uint8Array | string): number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Codec interface for type-safe encode/decode implementations
|
||||||
|
export interface Codec<T = any> {
|
||||||
|
encode(data: T, buf?: Uint8Array | ArrayBufferLike, offset?: number): Uint8Array;
|
||||||
|
decode(buf: Uint8Array | ArrayBufferLike, offset?: number): T;
|
||||||
|
encodingLength(data: T): number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function record(type: "A" | "AAAA" | "CNAME" | "DNAME" | "NS" | "PTR"): Codec<string>;
|
||||||
|
export function record(type: "MX"): Codec<MxData>;
|
||||||
|
export function record(type: "SRV"): Codec<SrvData>;
|
||||||
|
export function record(type: "SOA"): Codec<SoaData>;
|
||||||
|
export function record(type: "TXT"): Codec<Uint8Array[]>;
|
||||||
|
export function record(type: "CAA"): Codec<CaaData>;
|
||||||
|
export function record(type: "HINFO"): Codec<HInfoData>;
|
||||||
|
export function record(type: "NAPTR"): Codec<NaptrData>;
|
||||||
|
export function record(type: "RP"): Codec<RpData>;
|
||||||
|
export function record(type: "RRSIG"): Codec<RrsigData>;
|
||||||
|
export function record(type: "DS"): Codec<DsData>;
|
||||||
|
export function record(type: "DNSKEY"): Codec<DnskeyData>;
|
||||||
|
export function record(type: "NSEC"): Codec<NsecData>;
|
||||||
|
export function record(type: "NSEC3"): Codec<Nsec3Data>;
|
||||||
|
export function record(type: "SSHFP"): Codec<SshfpData>;
|
||||||
|
export function record(type: "TLSA"): Codec<TlsaData>;
|
||||||
|
export function record(type: "SVCB"): Codec<SvcbData>;
|
||||||
|
export function record(type: "HTTPS"): Codec<HttpsData>;
|
||||||
|
export function record(type: RecordType): Codec<any>;
|
||||||
|
|
||||||
|
export {};
|
||||||
|
|
||||||
+7
-2
@@ -19,14 +19,17 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@leichtgewicht/ip-codec": "^2.0.1"
|
"@leichtgewicht/ip-codec": "^2.0.1"
|
||||||
},
|
},
|
||||||
|
"types": "index.d.ts",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/node": "*",
|
||||||
"eslint": "^5.14.1",
|
"eslint": "^5.14.1",
|
||||||
"eslint-config-standard": "^12.0.0",
|
"eslint-config-standard": "^12.0.0",
|
||||||
"eslint-plugin-import": "^2.16.0",
|
"eslint-plugin-import": "^2.16.0",
|
||||||
"eslint-plugin-node": "^8.0.1",
|
"eslint-plugin-node": "^8.0.1",
|
||||||
"eslint-plugin-promise": "^4.0.1",
|
"eslint-plugin-promise": "^4.0.1",
|
||||||
"eslint-plugin-standard": "^4.0.0",
|
"eslint-plugin-standard": "^4.0.0",
|
||||||
"tape": "^4.10.1"
|
"tape": "^4.10.1",
|
||||||
|
"typescript": "^6.0.2"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"dns",
|
"dns",
|
||||||
@@ -38,10 +41,12 @@
|
|||||||
],
|
],
|
||||||
"files": [
|
"files": [
|
||||||
"index.js",
|
"index.js",
|
||||||
|
"index.d.ts",
|
||||||
"types.js",
|
"types.js",
|
||||||
"rcodes.js",
|
"rcodes.js",
|
||||||
"opcodes.js",
|
"opcodes.js",
|
||||||
"classes.js",
|
"classes.js",
|
||||||
"optioncodes.js"
|
"optioncodes.js",
|
||||||
|
"buffer.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,12 @@ 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, new Uint8Array('hello world'))
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -15,9 +19,9 @@ tape('txt', function (t) {
|
|||||||
testEncoder(t, packet.txt, [])
|
testEncoder(t, packet.txt, [])
|
||||||
testEncoder(t, packet.txt, ['hello world'])
|
testEncoder(t, packet.txt, ['hello world'])
|
||||||
testEncoder(t, packet.txt, ['hello', 'world'])
|
testEncoder(t, packet.txt, ['hello', 'world'])
|
||||||
testEncoder(t, packet.txt, [Buffer.from([0, 1, 2, 3, 4, 5])])
|
testEncoder(t, packet.txt, [new Uint8Array([0, 1, 2, 3, 4, 5])])
|
||||||
testEncoder(t, packet.txt, ['a', 'b', Buffer.from([0, 1, 2, 3, 4, 5])])
|
testEncoder(t, packet.txt, ['a', 'b', new Uint8Array([0, 1, 2, 3, 4, 5])])
|
||||||
testEncoder(t, packet.txt, ['', Buffer.allocUnsafe(0)])
|
testEncoder(t, packet.txt, ['', new Uint8Array(0)])
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -25,16 +29,17 @@ tape('txt-scalar-string', function (t) {
|
|||||||
const buf = packet.txt.encode('hi')
|
const buf = packet.txt.encode('hi')
|
||||||
const val = packet.txt.decode(buf)
|
const val = packet.txt.decode(buf)
|
||||||
t.ok(val.length === 1, 'array length')
|
t.ok(val.length === 1, 'array length')
|
||||||
t.ok(val[0].toString() === 'hi', 'data')
|
const decoded = val[0] instanceof Uint8Array ? new TextDecoder().decode(val[0]) : val[0].toString()
|
||||||
|
t.ok(decoded === 'hi', 'data')
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
tape('txt-scalar-buffer', function (t) {
|
tape('txt-scalar-buffer', function (t) {
|
||||||
const data = Buffer.from([0, 1, 2, 3, 4, 5])
|
const data = new Uint8Array([0, 1, 2, 3, 4, 5])
|
||||||
const buf = packet.txt.encode(data)
|
const buf = packet.txt.encode(data)
|
||||||
const val = packet.txt.decode(buf)
|
const val = packet.txt.decode(buf)
|
||||||
t.ok(val.length === 1, 'array length')
|
t.ok(val.length === 1, 'array length')
|
||||||
t.ok(val[0].equals(data), 'data')
|
t.ok(compare(t, val[0], data), 'data')
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -46,7 +51,7 @@ tape('txt-invalid-data', function (t) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
tape('null', function (t) {
|
tape('null', function (t) {
|
||||||
testEncoder(t, packet.null, Buffer.from([0, 1, 2, 3, 4, 5]))
|
testEncoder(t, packet.null, new Uint8Array([0, 1, 2, 3, 4, 5]))
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -164,6 +169,34 @@ tape('query', function (t) {
|
|||||||
}]
|
}]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
testEncoder(t, packet, {
|
||||||
|
type: 'query',
|
||||||
|
questions: [{
|
||||||
|
type: 'A',
|
||||||
|
class: 'IN',
|
||||||
|
name: 'hello.a.com',
|
||||||
|
qu_bit: false
|
||||||
|
}, {
|
||||||
|
type: 'SRV',
|
||||||
|
name: 'hello.srv.com',
|
||||||
|
qu_bit: false
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
|
||||||
|
testEncoder(t, packet, {
|
||||||
|
type: 'query',
|
||||||
|
questions: [{
|
||||||
|
type: 'A',
|
||||||
|
class: 'IN',
|
||||||
|
name: 'hello.a.com',
|
||||||
|
qu_bit: true
|
||||||
|
}, {
|
||||||
|
type: 'SRV',
|
||||||
|
name: 'hello.srv.com',
|
||||||
|
qu_bit: true
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -227,7 +260,7 @@ tape('response', function (t) {
|
|||||||
answers: [{
|
answers: [{
|
||||||
type: 'NULL',
|
type: 'NULL',
|
||||||
name: 'hello.null.com',
|
name: 'hello.null.com',
|
||||||
data: Buffer.from([1, 2, 3, 4, 5])
|
data: new Uint8Array([1, 2, 3, 4, 5])
|
||||||
}]
|
}]
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -283,7 +316,7 @@ tape('rcode', function (t) {
|
|||||||
|
|
||||||
tape('name_encoding', function (t) {
|
tape('name_encoding', function (t) {
|
||||||
let data = 'foo.example.com'
|
let data = 'foo.example.com'
|
||||||
const buf = Buffer.allocUnsafe(255)
|
const buf = new Uint8Array(255)
|
||||||
let offset = 0
|
let offset = 0
|
||||||
packet.name.encode(data, buf, offset)
|
packet.name.encode(data, buf, offset)
|
||||||
t.ok(packet.name.encode.bytes === 17, 'name encoding length matches')
|
t.ok(packet.name.encode.bytes === 17, 'name encoding length matches')
|
||||||
@@ -352,42 +385,57 @@ tape('name_encoding', function (t) {
|
|||||||
|
|
||||||
tape('name_decoding', function (t) {
|
tape('name_decoding', function (t) {
|
||||||
// The two most significant bits of a valid label header must be either both zero or both one
|
// The two most significant bits of a valid label header must be either both zero or both one
|
||||||
t.throws(function () { packet.name.decode(Buffer.from([0x80])) }, /Cannot decode name \(bad label\)$/)
|
t.throws(function () { packet.name.decode(new Uint8Array([0x80])) }, /Cannot decode name \(bad label\)$/)
|
||||||
t.throws(function () { packet.name.decode(Buffer.from([0xb0])) }, /Cannot decode name \(bad label\)$/)
|
t.throws(function () { packet.name.decode(new Uint8Array([0xb0])) }, /Cannot decode name \(bad label\)$/)
|
||||||
|
|
||||||
// Ensure there's enough buffer to read
|
// Ensure there's enough buffer to read
|
||||||
t.throws(function () { packet.name.decode(Buffer.from([])) }, /Cannot decode name \(buffer overflow\)$/)
|
t.throws(function () { packet.name.decode(new Uint8Array([])) }, /Cannot decode name \(buffer overflow\)$/)
|
||||||
t.throws(function () { packet.name.decode(Buffer.from([0x01, 0x00])) }, /Cannot decode name \(buffer overflow\)$/)
|
t.throws(function () { packet.name.decode(new Uint8Array([0x01, 0x00])) }, /Cannot decode name \(buffer overflow\)$/)
|
||||||
t.throws(function () { packet.name.decode(Buffer.from([0x01])) }, /Cannot decode name \(buffer overflow\)$/)
|
t.throws(function () { packet.name.decode(new Uint8Array([0x01])) }, /Cannot decode name \(buffer overflow\)$/)
|
||||||
t.throws(function () { packet.name.decode(Buffer.from([0xc0])) }, /Cannot decode name \(buffer overflow\)$/)
|
t.throws(function () { packet.name.decode(new Uint8Array([0xc0])) }, /Cannot decode name \(buffer overflow\)$/)
|
||||||
|
|
||||||
// Allow only pointers backwards
|
// Allow only pointers backwards
|
||||||
t.throws(function () { packet.name.decode(Buffer.from([0xc0, 0x00])) }, /Cannot decode name \(bad pointer\)$/)
|
t.throws(function () { packet.name.decode(new Uint8Array([0xc0, 0x00])) }, /Cannot decode name \(bad pointer\)$/)
|
||||||
t.throws(function () { packet.name.decode(Buffer.from([0xc0, 0x01])) }, /Cannot decode name \(bad pointer\)$/)
|
t.throws(function () { packet.name.decode(new Uint8Array([0xc0, 0x01])) }, /Cannot decode name \(bad pointer\)$/)
|
||||||
|
|
||||||
// A name can be only 253 characters (when connected with dots)
|
// A name can be only 253 characters (when connected with dots)
|
||||||
const maxLength = Buffer.alloc(255)
|
const maxLength = new Uint8Array(255)
|
||||||
maxLength.fill(Buffer.from([0x01, 0x61]), 0, 254)
|
for (let i = 0; i < 127; i++) {
|
||||||
|
maxLength[i * 2] = 0x01
|
||||||
|
maxLength[i * 2 + 1] = 0x61
|
||||||
|
}
|
||||||
|
maxLength[254] = 0x00
|
||||||
t.ok(packet.name.decode(maxLength) === new Array(127).fill('a').join('.'))
|
t.ok(packet.name.decode(maxLength) === new Array(127).fill('a').join('.'))
|
||||||
|
|
||||||
const tooLong = Buffer.alloc(256)
|
const tooLong = new Uint8Array(256)
|
||||||
tooLong.fill(Buffer.from([0x01, 0x61]))
|
for (let i = 0; i < 128; i++) {
|
||||||
|
tooLong[i * 2] = 0x01
|
||||||
|
tooLong[i * 2 + 1] = 0x61
|
||||||
|
}
|
||||||
t.throws(function () { packet.name.decode(tooLong) }, /Cannot decode name \(name too long\)$/)
|
t.throws(function () { packet.name.decode(tooLong) }, /Cannot decode name \(name too long\)$/)
|
||||||
|
|
||||||
// Ensure jumps don't reset the total length counter
|
// Ensure jumps don't reset the total length counter
|
||||||
const tooLongWithJump = Buffer.alloc(403)
|
const tooLongWithJump = new Uint8Array(403)
|
||||||
tooLongWithJump.fill(Buffer.from([0x01, 0x61]), 0, 200)
|
for (let i = 0; i < 100; i++) {
|
||||||
tooLongWithJump.fill(Buffer.from([0x01, 0x61]), 201, 401)
|
tooLongWithJump[i * 2] = 0x01
|
||||||
|
tooLongWithJump[i * 2 + 1] = 0x61
|
||||||
|
}
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
tooLongWithJump[201 + i * 2] = 0x01
|
||||||
|
tooLongWithJump[201 + i * 2 + 1] = 0x61
|
||||||
|
}
|
||||||
tooLongWithJump.set([0xc0, 0x00], 401)
|
tooLongWithJump.set([0xc0, 0x00], 401)
|
||||||
t.throws(function () { packet.name.decode(tooLongWithJump, 201) }, /Cannot decode name \(name too long\)$/)
|
t.throws(function () { packet.name.decode(tooLongWithJump, 201) }, /Cannot decode name \(name too long\)$/)
|
||||||
|
|
||||||
// Ensure a jump to a null byte doesn't add extra dots
|
// Ensure a jump to a null byte doesn't add extra dots
|
||||||
t.ok(packet.name.decode(Buffer.from([0x00, 0x01, 0x61, 0xc0, 0x00]), 1) === 'a')
|
t.ok(packet.name.decode(new Uint8Array([0x00, 0x01, 0x61, 0xc0, 0x00]), 1) === 'a')
|
||||||
|
|
||||||
// Ensure deeply nested pointers don't cause "Maximum call stack size exceeded" errors
|
// Ensure deeply nested pointers don't cause "Maximum call stack size exceeded" errors
|
||||||
const buf = Buffer.alloc(16386)
|
const buf = new Uint8Array(16386)
|
||||||
for (let i = 0; i < 16384; i += 2) {
|
for (let i = 0; i < 16384; i += 2) {
|
||||||
buf.writeUInt16BE(0xc000 | i, i + 2)
|
const value = 0xc000 | i
|
||||||
|
buf[i + 2] = (value >> 8) & 0xff
|
||||||
|
buf[i + 3] = value & 0xff
|
||||||
}
|
}
|
||||||
t.ok(packet.name.decode(buf, 16384) === '.')
|
t.ok(packet.name.decode(buf, 16384) === '.')
|
||||||
|
|
||||||
@@ -487,7 +535,7 @@ tape('dnskey', function (t) {
|
|||||||
testEncoder(t, packet.dnskey, {
|
testEncoder(t, packet.dnskey, {
|
||||||
flags: packet.dnskey.SECURE_ENTRYPOINT | packet.dnskey.ZONE_KEY,
|
flags: packet.dnskey.SECURE_ENTRYPOINT | packet.dnskey.ZONE_KEY,
|
||||||
algorithm: 1,
|
algorithm: 1,
|
||||||
key: Buffer.from([0, 1, 2, 3, 4, 5])
|
key: new Uint8Array([0, 1, 2, 3, 4, 5])
|
||||||
})
|
})
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
@@ -502,12 +550,12 @@ tape('rrsig', function (t) {
|
|||||||
inception: 1233,
|
inception: 1233,
|
||||||
keyTag: 2345,
|
keyTag: 2345,
|
||||||
signersName: 'foo.com',
|
signersName: 'foo.com',
|
||||||
signature: Buffer.from([0, 1, 2, 3, 4, 5])
|
signature: new Uint8Array([0, 1, 2, 3, 4, 5])
|
||||||
}
|
}
|
||||||
testEncoder(t, packet.rrsig, testRRSIG)
|
testEncoder(t, packet.rrsig, testRRSIG)
|
||||||
|
|
||||||
// Check the signature length is correct with extra junk at the end
|
// Check the signature length is correct with extra junk at the end
|
||||||
const buf = Buffer.allocUnsafe(packet.rrsig.encodingLength(testRRSIG) + 4)
|
const buf = new Uint8Array(packet.rrsig.encodingLength(testRRSIG) + 4)
|
||||||
packet.rrsig.encode(testRRSIG, buf)
|
packet.rrsig.encode(testRRSIG, buf)
|
||||||
const val2 = packet.rrsig.decode(buf)
|
const val2 = packet.rrsig.decode(buf)
|
||||||
t.ok(compare(t, testRRSIG, val2))
|
t.ok(compare(t, testRRSIG, val2))
|
||||||
@@ -557,9 +605,9 @@ tape('nsec', function (t) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Test with the sample NSEC from https://tools.ietf.org/html/rfc4034#section-4.3
|
// Test with the sample NSEC from https://tools.ietf.org/html/rfc4034#section-4.3
|
||||||
var sampleNSEC = Buffer.from('003704686f7374076578616d706c6503636f6d00' +
|
var sampleNSEC = unhexlify('003704686f7374076578616d706c6503636f6d00' +
|
||||||
'0006400100000003041b000000000000000000000000000000000000000000000' +
|
'0006400100000003041b000000000000000000000000000000000000000000000' +
|
||||||
'000000020', 'hex')
|
'000000020')
|
||||||
var decoded = packet.nsec.decode(sampleNSEC)
|
var decoded = packet.nsec.decode(sampleNSEC)
|
||||||
t.ok(compare(t, decoded, {
|
t.ok(compare(t, decoded, {
|
||||||
nextDomain: 'host.example.com',
|
nextDomain: 'host.example.com',
|
||||||
@@ -576,8 +624,8 @@ tape('nsec3', function (t) {
|
|||||||
algorithm: 1,
|
algorithm: 1,
|
||||||
flags: 0,
|
flags: 0,
|
||||||
iterations: 257,
|
iterations: 257,
|
||||||
salt: Buffer.from([42, 42, 42]),
|
salt: new Uint8Array([42, 42, 42]),
|
||||||
nextDomain: Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]),
|
nextDomain: new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]),
|
||||||
rrtypes: ['A', 'DNSKEY', 'CAA', 'DLV']
|
rrtypes: ['A', 'DNSKEY', 'CAA', 'DLV']
|
||||||
})
|
})
|
||||||
t.end()
|
t.end()
|
||||||
@@ -588,7 +636,7 @@ tape('ds', function (t) {
|
|||||||
keyTag: 1234,
|
keyTag: 1234,
|
||||||
algorithm: 1,
|
algorithm: 1,
|
||||||
digestType: 1,
|
digestType: 1,
|
||||||
digest: Buffer.from([0, 1, 2, 3, 4, 5])
|
digest: new Uint8Array([0, 1, 2, 3, 4, 5])
|
||||||
})
|
})
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
@@ -610,13 +658,543 @@ tape('tlsa', function (t) {
|
|||||||
usage: 3,
|
usage: 3,
|
||||||
selector: 1,
|
selector: 1,
|
||||||
matchingType: 1,
|
matchingType: 1,
|
||||||
certificate: Buffer.from([0, 1, 2, 3, 4, 5])
|
certificate: new Uint8Array([0, 1, 2, 3, 4, 5])
|
||||||
})
|
})
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const unhexlify = (hex) => {
|
||||||
|
return new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) { return parseInt(h, 16) }))
|
||||||
|
}
|
||||||
|
|
||||||
|
// <HTTPS SVCB test cases>
|
||||||
|
const debugHttps = false
|
||||||
|
|
||||||
|
const testScvbDecodeEncode = (t, testname, packetbuf, expected, skipEncode = false, skipMemcmpBufs = false) => {
|
||||||
|
const decoded = packet.svcb.decode(packetbuf, 0)
|
||||||
|
if (debugHttps) {
|
||||||
|
console.log(`${testname}: decode:`)
|
||||||
|
console.log(JSON.stringify(decoded, null, 2))
|
||||||
|
}
|
||||||
|
t.ok(compare(t, decoded, expected), 'svcb ' + testname + ' decode')
|
||||||
|
const encoded = packet.svcb.encode(expected)
|
||||||
|
if (!skipEncode) {
|
||||||
|
if (debugHttps) {
|
||||||
|
console.log(`${testname}: encode:`)
|
||||||
|
hexdump(encoded)
|
||||||
|
}
|
||||||
|
if (!skipMemcmpBufs) {
|
||||||
|
t.ok(compare(t, packetbuf, encoded), 'svcb ' + testname + ' encode memcmp')
|
||||||
|
}
|
||||||
|
// now decode the encoded buffer and check for sameness
|
||||||
|
const recoded = packet.svcb.decode(encoded, 0)
|
||||||
|
if (debugHttps) {
|
||||||
|
console.log(`${testname}: recode`)
|
||||||
|
console.log(JSON.stringify(decoded, null, 2))
|
||||||
|
}
|
||||||
|
t.ok(compare(t, recoded, expected), 'svcb ' + testname + ' recode')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testHttpsDecodeEncode = (t, testname, packetbuf, expected, skipEncode = false, skipMemcmpBufs = false) => {
|
||||||
|
const decoded = packet.httpssvc.decode(packetbuf, 0)
|
||||||
|
if (debugHttps) {
|
||||||
|
console.log(`${testname}: decode:`)
|
||||||
|
console.log(JSON.stringify(decoded, null, 2))
|
||||||
|
}
|
||||||
|
t.ok(compare(t, decoded, expected), 'https ' + testname + ' decode')
|
||||||
|
const encoded = packet.httpssvc.encode(expected)
|
||||||
|
if (!skipEncode) {
|
||||||
|
if (debugHttps) {
|
||||||
|
console.log(`${testname}: encode:`)
|
||||||
|
hexdump(encoded)
|
||||||
|
}
|
||||||
|
if (!skipMemcmpBufs) {
|
||||||
|
t.ok(compare(t, packetbuf, encoded), 'https ' + testname + ' encode memcmp')
|
||||||
|
}
|
||||||
|
// now decode the encoded buffer and check for sameness
|
||||||
|
const recoded = packet.httpssvc.decode(encoded, 0)
|
||||||
|
if (debugHttps) {
|
||||||
|
console.log(`${testname}: recode`)
|
||||||
|
console.log(JSON.stringify(decoded, null, 2))
|
||||||
|
}
|
||||||
|
t.ok(compare(t, recoded, expected), 'https ' + testname + ' recode')
|
||||||
|
}
|
||||||
|
return encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
const testHttpsSvcbDecodeEncode = (t, testname, packetbuf, expected, skipEncode = false, skipMemcmpBufs = false) => {
|
||||||
|
testScvbDecodeEncode(t, testname, packetbuf, expected, skipEncode, skipMemcmpBufs)
|
||||||
|
return testHttpsDecodeEncode(t, testname, packetbuf, expected, skipEncode, skipMemcmpBufs)
|
||||||
|
}
|
||||||
|
|
||||||
|
tape('https svcb', function (t) {
|
||||||
|
// for the test vectors see:
|
||||||
|
// https://datatracker.ietf.org/doc/rfc9460/
|
||||||
|
// https://github.com/MikeBishop/dns-alt-svc/blob/main/draft-ietf-dnsop-svcb-https.md
|
||||||
|
|
||||||
|
testEncoder(t, packet.svcb, {
|
||||||
|
priority: 16,
|
||||||
|
name: 'foo.example.org',
|
||||||
|
values: {
|
||||||
|
mandatory: [
|
||||||
|
'alpn',
|
||||||
|
'ipv4hint'
|
||||||
|
],
|
||||||
|
alpn: [
|
||||||
|
'h2',
|
||||||
|
'h3-19'
|
||||||
|
],
|
||||||
|
ipv4hint: [
|
||||||
|
'192.0.2.1'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
testEncoder(t, packet.httpssvc, {
|
||||||
|
priority: 16,
|
||||||
|
name: 'foo.example.org',
|
||||||
|
values: {
|
||||||
|
mandatory: [
|
||||||
|
'alpn',
|
||||||
|
'ipv4hint'
|
||||||
|
],
|
||||||
|
alpn: [
|
||||||
|
'h2',
|
||||||
|
'h3-19'
|
||||||
|
],
|
||||||
|
ipv4hint: [
|
||||||
|
'192.0.2.1'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// https AliasMode
|
||||||
|
testHttpsSvcbDecodeEncode(t, '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 "."
|
||||||
|
testHttpsSvcbDecodeEncode(t, 'rfc9460 case2 (target name ".")',
|
||||||
|
unhexlify(
|
||||||
|
'00 03' + // rdata len
|
||||||
|
'00 01' + // priority
|
||||||
|
'00' // target (root label)
|
||||||
|
),
|
||||||
|
{
|
||||||
|
priority: 1,
|
||||||
|
name: '.'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// https port
|
||||||
|
testHttpsSvcbDecodeEncode(t, '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
|
||||||
|
testHttpsSvcbDecodeEncode(t, '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 // skipEncode, we cannot encode an unknown key
|
||||||
|
)
|
||||||
|
|
||||||
|
// https generic key and value with decimal escape
|
||||||
|
testHttpsSvcbDecodeEncode(t, '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: new TextDecoder('utf-8').decode(unhexlify('68 65 6c 6c 6f d2 71 6f 6f'))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true // skipEncode, we cannot encode an unknown key
|
||||||
|
)
|
||||||
|
|
||||||
|
// https two quoted ipv6 hints
|
||||||
|
testHttpsSvcbDecodeEncode(t, '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]
|
||||||
|
testHttpsSvcbDecodeEncode(t, '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
|
||||||
|
testHttpsSvcbDecodeEncode(t, '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, // skipEncode: false
|
||||||
|
true // skipMemcmpBufs: true, do not directly memcmp the resulting bufs, see comment above
|
||||||
|
)
|
||||||
|
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
|
||||||
|
tape('cloudflare real world svcb/https', (t) => {
|
||||||
|
const httpsBuf = unhexlify(
|
||||||
|
'ef 23 81 80 00 01 00 01 00 00 00 01 09 63 6f 6d' +
|
||||||
|
'6d 75 6e 69 74 79 0a 63 6c 6f 75 64 66 6c 61 72' +
|
||||||
|
'65 03 63 6f 6d 00 00 41 00 01 09 63 6f 6d 6d 75' +
|
||||||
|
'6e 69 74 79 0a 63 6c 6f 75 64 66 6c 61 72 65 03' +
|
||||||
|
'63 6f 6d 00 00 41 00 01 00 00 00 3c 00 3d 00 01' +
|
||||||
|
'00 00 01 00 06 02 68 33 02 68 32 00 04 00 08 68' +
|
||||||
|
'12 02 43 68 12 03 43 00 06 00 20 26 06 47 00 00' +
|
||||||
|
'00 00 00 00 00 00 00 68 12 02 43 26 06 47 00 00' +
|
||||||
|
'00 00 00 00 00 00 00 68 12 03 43 00 00 29 02 00' +
|
||||||
|
'00 00 00 00 00 00'
|
||||||
|
)
|
||||||
|
const svcbBuf = unhexlify(
|
||||||
|
'ef 23 81 80 00 01 00 01 00 00 00 01 09 63 6f 6d' +
|
||||||
|
'6d 75 6e 69 74 79 0a 63 6c 6f 75 64 66 6c 61 72' +
|
||||||
|
'65 03 63 6f 6d 00 00 40 00 01 09 63 6f 6d 6d 75' +
|
||||||
|
'6e 69 74 79 0a 63 6c 6f 75 64 66 6c 61 72 65 03' +
|
||||||
|
'63 6f 6d 00 00 40 00 01 00 00 00 3c 00 3d 00 01' +
|
||||||
|
'00 00 01 00 06 02 68 33 02 68 32 00 04 00 08 68' +
|
||||||
|
'12 02 43 68 12 03 43 00 06 00 20 26 06 47 00 00' +
|
||||||
|
'00 00 00 00 00 00 00 68 12 02 43 26 06 47 00 00' +
|
||||||
|
'00 00 00 00 00 00 00 68 12 03 43 00 00 29 02 00' +
|
||||||
|
'00 00 00 00 00 00'
|
||||||
|
)
|
||||||
|
|
||||||
|
let expected = {
|
||||||
|
id: 61219,
|
||||||
|
type: 'response',
|
||||||
|
flags: 384,
|
||||||
|
flag_qr: true,
|
||||||
|
opcode: 'QUERY',
|
||||||
|
flag_aa: false,
|
||||||
|
flag_tc: false,
|
||||||
|
flag_rd: true,
|
||||||
|
flag_ra: true,
|
||||||
|
flag_z: false,
|
||||||
|
flag_ad: false,
|
||||||
|
flag_cd: false,
|
||||||
|
rcode: 'NOERROR',
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
name: 'community.cloudflare.com',
|
||||||
|
type: 'HTTPS',
|
||||||
|
qu_bit: false,
|
||||||
|
class: 'IN'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
answers: [
|
||||||
|
{
|
||||||
|
name: 'community.cloudflare.com',
|
||||||
|
type: 'HTTPS',
|
||||||
|
ttl: 60,
|
||||||
|
class: 'IN',
|
||||||
|
flush: false,
|
||||||
|
data: {
|
||||||
|
priority: 1,
|
||||||
|
name: '.',
|
||||||
|
values: {
|
||||||
|
alpn: [
|
||||||
|
'h3',
|
||||||
|
'h2'
|
||||||
|
],
|
||||||
|
ipv4hint: [
|
||||||
|
'104.18.2.67',
|
||||||
|
'104.18.3.67'
|
||||||
|
],
|
||||||
|
ipv6hint: [
|
||||||
|
'2606:4700::6812:243',
|
||||||
|
'2606:4700::6812:343'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
authorities: [],
|
||||||
|
additionals: [
|
||||||
|
{
|
||||||
|
name: '.',
|
||||||
|
type: 'OPT',
|
||||||
|
udpPayloadSize: 512,
|
||||||
|
extendedRcode: 0,
|
||||||
|
ednsVersion: 0,
|
||||||
|
flags: 0,
|
||||||
|
flag_do: false,
|
||||||
|
options: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const decodedHttps = packet.decode(httpsBuf)
|
||||||
|
t.ok(decodedHttps, 'cloudflare real world https decoded')
|
||||||
|
if (debugHttps) {
|
||||||
|
console.log(`cloudflare real world: decodedHttps:`)
|
||||||
|
console.log(JSON.stringify(decodedHttps, null, 2))
|
||||||
|
}
|
||||||
|
t.ok(compare(t, decodedHttps, expected), 'cloudflare real world https compare')
|
||||||
|
|
||||||
|
const decodedSvcb = packet.decode(svcbBuf)
|
||||||
|
t.ok(decodedSvcb, 'cloudflare real world svcb decoded')
|
||||||
|
if (debugHttps) {
|
||||||
|
console.log(`cloudflare real world: decodedSvcb:`)
|
||||||
|
console.log(JSON.stringify(decodedSvcb, null, 2))
|
||||||
|
}
|
||||||
|
expected.questions[0].type = expected.answers[0].type = 'SVCB'
|
||||||
|
t.ok(compare(t, decodedSvcb, expected), 'cloudflare real world svcb compare')
|
||||||
|
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
|
||||||
|
tape('google resolver SVCB real world', function (t) {
|
||||||
|
const svcbBuf = unhexlify(
|
||||||
|
'd0 90 85 80 00 01' +
|
||||||
|
'00 02 00 00 00 04 04 5f 64 6e 73 08 72 65 73 6f' +
|
||||||
|
'6c 76 65 72 04 61 72 70 61 00 00 40 00 01 c0 0c' +
|
||||||
|
'00 40 00 01 00 01 51 80 00 16 00 01 03 64 6e 73' +
|
||||||
|
'06 67 6f 6f 67 6c 65 00 00 01 00 04 03 64 6f 74' +
|
||||||
|
'c0 0c 00 40 00 01 00 01 51 80 00 2c 00 02 03 64' +
|
||||||
|
'6e 73 06 67 6f 6f 67 6c 65 00 00 01 00 06 02 68' +
|
||||||
|
'32 02 68 33 00 07 00 10 2f 64 6e 73 2d 71 75 65' +
|
||||||
|
'72 79 7b 3f 64 6e 73 7d 03 64 6e 73 06 67 6f 6f' +
|
||||||
|
'67 6c 65 00 00 01 00 01 00 01 51 80 00 04 08 08' +
|
||||||
|
'08 08 c0 7e 00 01 00 01 00 01 51 80 00 04 08 08' +
|
||||||
|
'04 04 c0 7e 00 1c 00 01 00 01 51 80 00 10 20 01' +
|
||||||
|
'48 60 48 60 00 00 00 00 00 00 00 00 88 88 c0 7e' +
|
||||||
|
'00 1c 00 01 00 01 51 80 00 10 20 01 48 60 48 60' +
|
||||||
|
'00 00 00 00 00 00 00 00 88 44'
|
||||||
|
)
|
||||||
|
|
||||||
|
const expected = {
|
||||||
|
id: 53392,
|
||||||
|
type: 'response',
|
||||||
|
flags: 1408,
|
||||||
|
flag_qr: true,
|
||||||
|
opcode: 'QUERY',
|
||||||
|
flag_aa: true,
|
||||||
|
flag_tc: false,
|
||||||
|
flag_rd: true,
|
||||||
|
flag_ra: true,
|
||||||
|
flag_z: false,
|
||||||
|
flag_ad: false,
|
||||||
|
flag_cd: false,
|
||||||
|
rcode: 'NOERROR',
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
name: '_dns.resolver.arpa',
|
||||||
|
type: 'SVCB',
|
||||||
|
qu_bit: false,
|
||||||
|
class: 'IN'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
answers: [
|
||||||
|
{
|
||||||
|
name: '_dns.resolver.arpa',
|
||||||
|
type: 'SVCB',
|
||||||
|
ttl: 86400,
|
||||||
|
class: 'IN',
|
||||||
|
flush: false,
|
||||||
|
data: {
|
||||||
|
priority: 1,
|
||||||
|
name: 'dns.google',
|
||||||
|
values: {
|
||||||
|
alpn: [
|
||||||
|
'dot'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '_dns.resolver.arpa',
|
||||||
|
type: 'SVCB',
|
||||||
|
ttl: 86400,
|
||||||
|
class: 'IN',
|
||||||
|
flush: false,
|
||||||
|
data: {
|
||||||
|
priority: 2,
|
||||||
|
name: 'dns.google',
|
||||||
|
values: {
|
||||||
|
alpn: [
|
||||||
|
'h2',
|
||||||
|
'h3'
|
||||||
|
],
|
||||||
|
dohpath: '/dns-query{?dns}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
authorities: [],
|
||||||
|
additionals: [
|
||||||
|
{
|
||||||
|
name: 'dns.google',
|
||||||
|
type: 'A',
|
||||||
|
ttl: 86400,
|
||||||
|
class: 'IN',
|
||||||
|
flush: false,
|
||||||
|
data: '8.8.8.8'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dns.google',
|
||||||
|
type: 'A',
|
||||||
|
ttl: 86400,
|
||||||
|
class: 'IN',
|
||||||
|
flush: false,
|
||||||
|
data: '8.8.4.4'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dns.google',
|
||||||
|
type: 'AAAA',
|
||||||
|
ttl: 86400,
|
||||||
|
class: 'IN',
|
||||||
|
flush: false,
|
||||||
|
data: '2001:4860:4860::8888'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dns.google',
|
||||||
|
type: 'AAAA',
|
||||||
|
ttl: 86400,
|
||||||
|
class: 'IN',
|
||||||
|
flush: false,
|
||||||
|
data: '2001:4860:4860::8844'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const decodedSvcb = packet.decode(svcbBuf)
|
||||||
|
t.ok(decodedSvcb, 'google real world svcb decoded')
|
||||||
|
if (debugHttps) {
|
||||||
|
console.log(`google real world svcb: decodedSvcb:`)
|
||||||
|
console.log(JSON.stringify(decodedSvcb, null, 2))
|
||||||
|
}
|
||||||
|
t.ok(compare(t, decodedSvcb, expected), 'google real world svcb compare')
|
||||||
|
|
||||||
|
// now test encoding and decoding the individual answer data
|
||||||
|
for (let answer of expected.answers) {
|
||||||
|
const encodedData = packet.svcb.encode(answer.data)
|
||||||
|
const decodedData = packet.svcb.decode(encodedData)
|
||||||
|
t.ok(compare(t, answer.data, decodedData), 'google real world answer recode')
|
||||||
|
}
|
||||||
|
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
|
||||||
|
// </HTTPS SVCB test cases>
|
||||||
|
|
||||||
tape('unpack', function (t) {
|
tape('unpack', function (t) {
|
||||||
const buf = Buffer.from([
|
const buf = new Uint8Array([
|
||||||
0x00, 0x79,
|
0x00, 0x79,
|
||||||
0xde, 0xad, 0x85, 0x00, 0x00, 0x01, 0x00, 0x01,
|
0xde, 0xad, 0x85, 0x00, 0x00, 0x01, 0x00, 0x01,
|
||||||
0x00, 0x02, 0x00, 0x02, 0x02, 0x6f, 0x6a, 0x05,
|
0x00, 0x02, 0x00, 0x02, 0x02, 0x6f, 0x6a, 0x05,
|
||||||
@@ -696,7 +1274,7 @@ function testEncoder (t, rpacket, val) {
|
|||||||
t.ok(compare(t, val, val3), 'decoded object match on re-encode')
|
t.ok(compare(t, val, val3), 'decoded object match on re-encode')
|
||||||
t.ok(compare(t, val2, val3), 're-encoded decoded object match on re-encode')
|
t.ok(compare(t, val2, val3), 're-encoded decoded object match on re-encode')
|
||||||
|
|
||||||
const bigger = Buffer.allocUnsafe(buf2.length + 10)
|
const bigger = new Uint8Array(buf2.length + 10)
|
||||||
|
|
||||||
const buf3 = rpacket.encode(val, bigger, 10)
|
const buf3 = rpacket.encode(val, bigger, 10)
|
||||||
const val4 = rpacket.decode(buf3, 10)
|
const val4 = rpacket.decode(buf3, 10)
|
||||||
@@ -707,7 +1285,13 @@ function testEncoder (t, rpacket, val) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function compare (t, a, b) {
|
function compare (t, a, b) {
|
||||||
if (Buffer.isBuffer(a)) return a.toString('hex') === b.toString('hex')
|
if (a instanceof Uint8Array && b instanceof Uint8Array) {
|
||||||
|
if (a.length !== b.length) return false
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
if (a[i] !== b[i]) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
if (typeof a === 'object' && a && b) {
|
if (typeof a === 'object' && a && b) {
|
||||||
const keys = Object.keys(a)
|
const keys = Object.keys(a)
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "node16",
|
||||||
|
"lib": [
|
||||||
|
"es6"
|
||||||
|
],
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"strictFunctionTypes": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"types": ["node"],
|
||||||
|
"noEmit": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"ignoreDeprecations": "6.0",
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"dns-packet": ["./index.d.ts"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"index.d.ts",
|
||||||
|
"dns-packet-tests.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -45,6 +45,8 @@ 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 64: return 'SVCB'
|
||||||
|
case 65: return 'HTTPS'
|
||||||
case 255: return 'ANY'
|
case 255: return 'ANY'
|
||||||
}
|
}
|
||||||
return 'UNKNOWN_' + type
|
return 'UNKNOWN_' + type
|
||||||
@@ -95,6 +97,8 @@ 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 'SVCB': return 64
|
||||||
|
case 'HTTPS': return 65
|
||||||
case 'ANY': return 255
|
case 'ANY': return 255
|
||||||
case '*': return 255
|
case '*': return 255
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user