Dorian 0d073fa89e Add comprehensive installation and setup documentation
- Add GETTING_STARTED.md with quick start guide and development modes
- Add INSTALL.sh automated installation script
- Add INSTALLATION_CHECKLIST.md, INSTALLATION_SUCCESS.md, and INSTALLATION_SUMMARY.md
- Add QUICK_REFERENCE.md for common commands
- Add SETUP_GUIDE.md with detailed setup instructions
- Update README.md with improved project overview
- Add did-wallet app dependencies and node_modules
2026-01-27 17:18:21 +00:00

1567 lines
42 KiB
JavaScript

import * as ip from '@leichtgewicht/ip-codec'
import * as types from './types.mjs'
import * as rcodes from './rcodes.mjs'
import * as opcodes from './opcodes.mjs'
import * as classes from './classes.mjs'
import * as optioncodes from './optioncodes.mjs'
import * as b from './buffer_utils.mjs'
import { decode as toUtf8 } from 'utf8-codec'
const QUERY_FLAG = 0
const RESPONSE_FLAG = 1 << 15
const FLUSH_MASK = 1 << 15
const NOT_FLUSH_MASK = ~FLUSH_MASK
const QU_MASK = 1 << 15
const NOT_QU_MASK = ~QU_MASK
function codec ({ bytes = 0, encode, decode, encodingLength }) {
encode.bytes = bytes
decode.bytes = bytes
return {
encode,
decode,
encodingLength: encodingLength || (() => bytes)
}
}
export const name = codec({
encode (str, buf, offset) {
if (!buf) buf = new Uint8Array(name.encodingLength(str))
if (!offset) offset = 0
const oldOffset = offset
// strip leading and trailing .
const n = str.replace(/^\.|\.$/gm, '')
if (n.length) {
const list = n.split('.')
for (let i = 0; i < list.length; i++) {
const len = b.write(buf, list[i], offset + 1)
buf[offset] = len
offset += len + 1
}
}
buf[offset++] = 0
name.encode.bytes = offset - oldOffset
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const list = []
let oldOffset = offset
let totalLength = 0
let consumedBytes = 0
let jumped = false
while (true) {
if (offset >= buf.length) {
throw new Error('Cannot decode name (buffer overflow)')
}
const len = buf[offset++]
consumedBytes += jumped ? 0 : 1
if (len === 0) {
break
} else if ((len & 0xc0) === 0) {
if (offset + len > buf.length) {
throw new Error('Cannot decode name (buffer overflow)')
}
totalLength += len + 1
if (totalLength > 254) {
throw new Error('Cannot decode name (name too long)')
}
list.push(toUtf8(buf, offset, offset + len))
offset += len
consumedBytes += jumped ? 0 : len
} else if ((len & 0xc0) === 0xc0) {
if (offset + 1 > buf.length) {
throw new Error('Cannot decode name (buffer overflow)')
}
const jumpOffset = b.readUInt16BE(buf, offset - 1) - 0xc000
if (jumpOffset >= oldOffset) {
// Allow only pointers to prior data. RFC 1035, section 4.1.4 states:
// "[...] an entire domain name or a list of labels at the end of a domain name
// is replaced with a pointer to a prior occurance (sic) of the same name."
throw new Error('Cannot decode name (bad pointer)')
}
offset = jumpOffset
oldOffset = jumpOffset
consumedBytes += jumped ? 0 : 1
jumped = true
} else {
throw new Error('Cannot decode name (bad label)')
}
}
name.decode.bytes = consumedBytes
return list.length === 0 ? '.' : list.join('.')
},
encodingLength (n) {
if (n === '.' || n === '..') return 1
return b.bytelength(n.replace(/^\.|\.$/gm, '')) + 2
}
})
const string = codec({
encode (s, buf, offset) {
if (!buf) buf = new Uint8Array(string.encodingLength(s))
if (!offset) offset = 0
const len = b.write(buf, s, offset + 1)
buf[offset] = len
string.encode.bytes = len + 1
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const len = buf[offset]
const s = toUtf8(buf, offset + 1, offset + 1 + len)
string.decode.bytes = len + 1
return s
},
encodingLength (s) {
return b.bytelength(s) + 1
}
})
const header = codec({
bytes: 12,
encode (h, buf, offset) {
if (!buf) buf = new Uint8Array(header.encodingLength(h))
if (!offset) offset = 0
const flags = (h.flags || 0) & 32767
const type = h.type === 'response' ? RESPONSE_FLAG : QUERY_FLAG
b.writeUInt16BE(buf, h.id || 0, offset)
b.writeUInt16BE(buf, flags | type, offset + 2)
b.writeUInt16BE(buf, h.questions.length, offset + 4)
b.writeUInt16BE(buf, h.answers.length, offset + 6)
b.writeUInt16BE(buf, h.authorities.length, offset + 8)
b.writeUInt16BE(buf, h.additionals.length, offset + 10)
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
if (buf.length < 12) throw new Error('Header must be 12 bytes')
const flags = b.readUInt16BE(buf, offset + 2)
return {
id: b.readUInt16BE(buf, offset),
type: flags & RESPONSE_FLAG ? 'response' : 'query',
flags: flags & 32767,
flag_qr: ((flags >> 15) & 0x1) === 1,
opcode: opcodes.toString((flags >> 11) & 0xf),
flag_aa: ((flags >> 10) & 0x1) === 1,
flag_tc: ((flags >> 9) & 0x1) === 1,
flag_rd: ((flags >> 8) & 0x1) === 1,
flag_ra: ((flags >> 7) & 0x1) === 1,
flag_z: ((flags >> 6) & 0x1) === 1,
flag_ad: ((flags >> 5) & 0x1) === 1,
flag_cd: ((flags >> 4) & 0x1) === 1,
rcode: rcodes.toString(flags & 0xf),
questions: new Array(b.readUInt16BE(buf, offset + 4)),
answers: new Array(b.readUInt16BE(buf, offset + 6)),
authorities: new Array(b.readUInt16BE(buf, offset + 8)),
additionals: new Array(b.readUInt16BE(buf, offset + 10))
}
},
encodingLength () {
return 12
}
})
const runknown = codec({
encode (data, buf, offset) {
if (!buf) buf = new Uint8Array(runknown.encodingLength(data))
if (!offset) offset = 0
const dLen = data.length
b.writeUInt16BE(buf, dLen, offset)
b.copy(data, buf, offset + 2, 0, dLen)
runknown.encode.bytes = dLen + 2
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const len = b.readUInt16BE(buf, offset)
const data = buf.slice(offset + 2, offset + 2 + len)
runknown.decode.bytes = len + 2
return data
},
encodingLength (data) {
return data.length + 2
}
})
const rns = codec({
encode (data, buf, offset) {
if (!buf) buf = new Uint8Array(rns.encodingLength(data))
if (!offset) offset = 0
name.encode(data, buf, offset + 2)
b.writeUInt16BE(buf, name.encode.bytes, offset)
rns.encode.bytes = name.encode.bytes + 2
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const len = b.readUInt16BE(buf, offset)
const dd = name.decode(buf, offset + 2)
rns.decode.bytes = len + 2
return dd
},
encodingLength (data) {
return name.encodingLength(data) + 2
}
})
const rsoa = codec({
encode (data, buf, offset) {
if (!buf) buf = new Uint8Array(rsoa.encodingLength(data))
if (!offset) offset = 0
const oldOffset = offset
offset += 2
name.encode(data.mname, buf, offset)
offset += name.encode.bytes
name.encode(data.rname, buf, offset)
offset += name.encode.bytes
b.writeUInt32BE(buf, data.serial || 0, offset)
offset += 4
b.writeUInt32BE(buf, data.refresh || 0, offset)
offset += 4
b.writeUInt32BE(buf, data.retry || 0, offset)
offset += 4
b.writeUInt32BE(buf, data.expire || 0, offset)
offset += 4
b.writeUInt32BE(buf, data.minimum || 0, offset)
offset += 4
b.writeUInt16BE(buf, offset - oldOffset - 2, oldOffset)
rsoa.encode.bytes = offset - oldOffset
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const oldOffset = offset
const data = {}
offset += 2
data.mname = name.decode(buf, offset)
offset += name.decode.bytes
data.rname = name.decode(buf, offset)
offset += name.decode.bytes
data.serial = b.readUInt32BE(buf, offset)
offset += 4
data.refresh = b.readUInt32BE(buf, offset)
offset += 4
data.retry = b.readUInt32BE(buf, offset)
offset += 4
data.expire = b.readUInt32BE(buf, offset)
offset += 4
data.minimum = b.readUInt32BE(buf, offset)
offset += 4
rsoa.decode.bytes = offset - oldOffset
return data
},
encodingLength (data) {
return 22 + name.encodingLength(data.mname) + name.encodingLength(data.rname)
}
})
const rtxt = codec({
encode (data, buf, offset) {
if (!Array.isArray(data)) data = [data]
for (let i = 0; i < data.length; i++) {
if (typeof data[i] === 'string') {
data[i] = b.from(data[i])
}
if (!b.isU8Arr(data[i])) {
throw new Error('Must be a Buffer')
}
}
if (!buf) buf = new Uint8Array(rtxt.encodingLength(data))
if (!offset) offset = 0
const oldOffset = offset
offset += 2
data.forEach(function (d) {
buf[offset++] = d.length
b.copy(d, buf, offset, 0, d.length)
offset += d.length
})
b.writeUInt16BE(buf, offset - oldOffset - 2, oldOffset)
rtxt.encode.bytes = offset - oldOffset
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const oldOffset = offset
let remaining = b.readUInt16BE(buf, offset)
offset += 2
const data = []
while (remaining > 0) {
const len = buf[offset++]
--remaining
if (remaining < len) {
throw new Error('Buffer overflow')
}
data.push(buf.slice(offset, offset + len))
offset += len
remaining -= len
}
rtxt.decode.bytes = offset - oldOffset
return data
},
encodingLength (data) {
if (!Array.isArray(data)) data = [data]
let length = 2
data.forEach(function (buf) {
if (typeof buf === 'string') {
length += b.bytelength(buf) + 1
} else {
length += buf.length + 1
}
})
return length
}
})
const rnull = codec({
encode (data, buf, offset) {
if (!buf) buf = new Uint8Array(rnull.encodingLength(data))
if (!offset) offset = 0
if (typeof data === 'string') data = b.from(data)
if (!data) data = new Uint8Array(0)
const oldOffset = offset
offset += 2
const len = data.length
b.copy(data, buf, offset, 0, len)
offset += len
b.writeUInt16BE(buf, offset - oldOffset - 2, oldOffset)
rnull.encode.bytes = offset - oldOffset
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const oldOffset = offset
const len = b.readUInt16BE(buf, offset)
offset += 2
const data = buf.slice(offset, offset + len)
offset += len
rnull.decode.bytes = offset - oldOffset
return data
},
encodingLength (data) {
if (!data) return 2
return (b.isU8Arr(data) ? data.length : b.bytelength(data)) + 2
}
})
const rhinfo = codec({
encode (data, buf, offset) {
if (!buf) buf = new Uint8Array(rhinfo.encodingLength(data))
if (!offset) offset = 0
const oldOffset = offset
offset += 2
string.encode(data.cpu, buf, offset)
offset += string.encode.bytes
string.encode(data.os, buf, offset)
offset += string.encode.bytes
b.writeUInt16BE(buf, offset - oldOffset - 2, oldOffset)
rhinfo.encode.bytes = offset - oldOffset
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const oldOffset = offset
const data = {}
offset += 2
data.cpu = string.decode(buf, offset)
offset += string.decode.bytes
data.os = string.decode(buf, offset)
offset += string.decode.bytes
rhinfo.decode.bytes = offset - oldOffset
return data
},
encodingLength (data) {
return string.encodingLength(data.cpu) + string.encodingLength(data.os) + 2
}
})
const rptr = codec({
encode (data, buf, offset) {
if (!buf) buf = new Uint8Array(rptr.encodingLength(data))
if (!offset) offset = 0
name.encode(data, buf, offset + 2)
b.writeUInt16BE(buf, name.encode.bytes, offset)
rptr.encode.bytes = name.encode.bytes + 2
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const data = name.decode(buf, offset + 2)
rptr.decode.bytes = name.decode.bytes + 2
return data
},
encodingLength (data) {
return name.encodingLength(data) + 2
}
})
const rsrv = codec({
encode (data, buf, offset) {
if (!buf) buf = new Uint8Array(rsrv.encodingLength(data))
if (!offset) offset = 0
b.writeUInt16BE(buf, data.priority || 0, offset + 2)
b.writeUInt16BE(buf, data.weight || 0, offset + 4)
b.writeUInt16BE(buf, data.port || 0, offset + 6)
name.encode(data.target, buf, offset + 8)
const len = name.encode.bytes + 6
b.writeUInt16BE(buf, len, offset)
rsrv.encode.bytes = len + 2
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const len = b.readUInt16BE(buf, offset)
const data = {}
data.priority = b.readUInt16BE(buf, offset + 2)
data.weight = b.readUInt16BE(buf, offset + 4)
data.port = b.readUInt16BE(buf, offset + 6)
data.target = name.decode(buf, offset + 8)
rsrv.decode.bytes = len + 2
return data
},
encodingLength (data) {
return 8 + name.encodingLength(data.target)
}
})
const rcaa = codec({
encode (data, buf, offset) {
const len = rcaa.encodingLength(data)
if (!buf) buf = new Uint8Array(rcaa.encodingLength(data))
if (!offset) offset = 0
if (data.issuerCritical) {
data.flags = rcaa.ISSUER_CRITICAL
}
b.writeUInt16BE(buf, len - 2, offset)
offset += 2
buf[offset] = data.flags || 0
offset += 1
string.encode(data.tag, buf, offset)
offset += string.encode.bytes
b.write(buf, data.value, offset)
offset += b.bytelength(data.value)
rcaa.encode.bytes = len
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const len = b.readUInt16BE(buf, offset)
offset += 2
const oldOffset = offset
const data = {}
data.flags = buf[offset]
offset += 1
data.tag = string.decode(buf, offset)
offset += string.decode.bytes
data.value = toUtf8(buf, offset, oldOffset + len)
data.issuerCritical = !!(data.flags & rcaa.ISSUER_CRITICAL)
rcaa.decode.bytes = len + 2
return data
},
encodingLength (data) {
return string.encodingLength(data.tag) + string.encodingLength(data.value) + 2
}
})
rcaa.ISSUER_CRITICAL = 1 << 7
const rmx = codec({
encode (data, buf, offset) {
if (!buf) buf = new Uint8Array(rmx.encodingLength(data))
if (!offset) offset = 0
const oldOffset = offset
offset += 2
b.writeUInt16BE(buf, data.preference || 0, offset)
offset += 2
name.encode(data.exchange, buf, offset)
offset += name.encode.bytes
b.writeUInt16BE(buf, offset - oldOffset - 2, oldOffset)
rmx.encode.bytes = offset - oldOffset
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const oldOffset = offset
const data = {}
offset += 2
data.preference = b.readUInt16BE(buf, offset)
offset += 2
data.exchange = name.decode(buf, offset)
offset += name.decode.bytes
rmx.decode.bytes = offset - oldOffset
return data
},
encodingLength (data) {
return 4 + name.encodingLength(data.exchange)
}
})
const ra = codec({
encode (host, buf, offset) {
if (!buf) buf = new Uint8Array(ra.encodingLength(host))
if (!offset) offset = 0
b.writeUInt16BE(buf, 4, offset)
offset += 2
ip.v4.encode(host, buf, offset)
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
offset += 2
const host = ip.v4.decode(buf, offset)
return host
},
bytes: 6
})
const raaaa = codec({
encode (host, buf, offset) {
if (!buf) buf = new Uint8Array(raaaa.encodingLength(host))
if (!offset) offset = 0
b.writeUInt16BE(buf, 16, offset)
offset += 2
ip.v6.encode(host, buf, offset)
raaaa.encode.bytes = 18
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
offset += 2
const host = ip.v6.decode(buf, offset)
raaaa.decode.bytes = 18
return host
},
bytes: 18
})
const alloc = size => new Uint8Array(size)
const roption = codec({
encode (option, buf, offset) {
if (!buf) buf = new Uint8Array(roption.encodingLength(option))
if (!offset) offset = 0
const oldOffset = offset
const code = optioncodes.toCode(option.code)
b.writeUInt16BE(buf, code, offset)
offset += 2
if (option.data) {
b.writeUInt16BE(buf, option.data.length, offset)
offset += 2
b.copy(option.data, buf, offset)
offset += option.data.length
} else {
switch (code) {
// case 3: NSID. No encode makes sense.
// case 5,6,7: Not implementable
case 8: // ECS
{
// note: do IP math before calling
const spl = option.sourcePrefixLength || 0
const fam = option.family || ip.familyOf(option.ip, alloc)
const ipBuf = ip.encode(option.ip, alloc)
const ipLen = Math.ceil(spl / 8)
b.writeUInt16BE(buf, ipLen + 4, offset)
offset += 2
b.writeUInt16BE(buf, fam, offset)
offset += 2
buf[offset++] = spl
buf[offset++] = option.scopePrefixLength || 0
b.copy(ipBuf, buf, offset, 0, ipLen)
offset += ipLen
}
break
// case 9: EXPIRE (experimental)
// case 10: COOKIE. No encode makes sense.
case 11: // KEEP-ALIVE
if (option.timeout) {
b.writeUInt16BE(buf, 2, offset)
offset += 2
b.writeUInt16BE(buf, option.timeout, offset)
offset += 2
} else {
b.writeUInt16BE(buf, 0, offset)
offset += 2
}
break
case 12: // PADDING
{
const len = option.length || 0
b.writeUInt16BE(buf, len, offset)
offset += 2
buf.fill(0, offset, offset + len)
offset += len
}
break
// case 13: CHAIN. Experimental.
case 14: // KEY-TAG
{
const tagsLen = option.tags.length * 2
b.writeUInt16BE(buf, tagsLen, offset)
offset += 2
for (const tag of option.tags) {
b.writeUInt16BE(buf, tag, offset)
offset += 2
}
}
break
default:
throw new Error(`Unknown roption code: ${option.code}`)
}
}
roption.encode.bytes = offset - oldOffset
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const option = {}
option.code = b.readUInt16BE(buf, offset)
option.type = optioncodes.toString(option.code)
offset += 2
const len = b.readUInt16BE(buf, offset)
offset += 2
option.data = buf.slice(offset, offset + len)
switch (option.code) {
// case 3: NSID. No decode makes sense.
case 8: // ECS
option.family = b.readUInt16BE(buf, offset)
offset += 2
option.sourcePrefixLength = buf[offset++]
option.scopePrefixLength = buf[offset++]
{
const padded = new Uint8Array((option.family === 1) ? 4 : 16)
b.copy(buf, padded, 0, offset, offset + len - 4)
option.ip = ip.decode(padded)
}
break
// case 12: Padding. No decode makes sense.
case 11: // KEEP-ALIVE
if (len > 0) {
option.timeout = b.readUInt16BE(buf, offset)
offset += 2
}
break
case 14:
option.tags = []
for (let i = 0; i < len; i += 2) {
option.tags.push(b.readUInt16BE(buf, offset))
offset += 2
}
// don't worry about default. caller will use data if desired
}
roption.decode.bytes = len + 4
return option
},
encodingLength (option) {
if (option.data) {
return option.data.length + 4
}
const code = optioncodes.toCode(option.code)
switch (code) {
case 8: // ECS
{
const spl = option.sourcePrefixLength || 0
return Math.ceil(spl / 8) + 8
}
case 11: // KEEP-ALIVE
return (typeof option.timeout === 'number') ? 6 : 4
case 12: // PADDING
return option.length + 4
case 14: // KEY-TAG
return 4 + (option.tags.length * 2)
}
throw new Error(`Unknown roption code: ${option.code}`)
}
})
const ropt = codec({
encode (options, buf, offset) {
if (!buf) buf = new Uint8Array(ropt.encodingLength(options))
if (!offset) offset = 0
const oldOffset = offset
const rdlen = encodingLengthList(options, roption)
b.writeUInt16BE(buf, rdlen, offset)
offset = encodeList(options, roption, buf, offset + 2)
ropt.encode.bytes = offset - oldOffset
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const oldOffset = offset
const options = []
let rdlen = b.readUInt16BE(buf, offset)
offset += 2
let o = 0
while (rdlen > 0) {
options[o++] = roption.decode(buf, offset)
offset += roption.decode.bytes
rdlen -= roption.decode.bytes
}
ropt.decode.bytes = offset - oldOffset
return options
},
encodingLength (options) {
return 2 + encodingLengthList(options || [], roption)
}
})
const rdnskey = codec({
encode (key, buf, offset) {
if (!buf) buf = new Uint8Array(rdnskey.encodingLength(key))
if (!offset) offset = 0
const oldOffset = offset
const keydata = key.key
if (!b.isU8Arr(keydata)) {
throw new Error('Key must be a Buffer')
}
offset += 2 // Leave space for length
b.writeUInt16BE(buf, key.flags, offset)
offset += 2
buf[offset] = rdnskey.PROTOCOL_DNSSEC
offset += 1
buf[offset] = key.algorithm
offset += 1
b.copy(keydata, buf, offset, 0, keydata.length)
offset += keydata.length
rdnskey.encode.bytes = offset - oldOffset
b.writeUInt16BE(buf, rdnskey.encode.bytes - 2, oldOffset)
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const oldOffset = offset
const key = {}
const length = b.readUInt16BE(buf, offset)
offset += 2
key.flags = b.readUInt16BE(buf, offset)
offset += 2
if (buf[offset] !== rdnskey.PROTOCOL_DNSSEC) {
throw new Error('Protocol must be 3')
}
offset += 1
key.algorithm = buf[offset]
offset += 1
key.key = buf.slice(offset, oldOffset + length + 2)
offset += key.key.length
rdnskey.decode.bytes = offset - oldOffset
return key
},
encodingLength (key) {
return 6 + b.bytelength(key.key)
}
})
rdnskey.PROTOCOL_DNSSEC = 3
rdnskey.ZONE_KEY = 0x80
rdnskey.SECURE_ENTRYPOINT = 0x8000
const rrrsig = codec({
encode (sig, buf, offset) {
if (!buf) buf = new Uint8Array(rrrsig.encodingLength(sig))
if (!offset) offset = 0
const oldOffset = offset
const signature = sig.signature
if (!b.isU8Arr(signature)) {
throw new Error('Signature must be a Buffer')
}
offset += 2 // Leave space for length
b.writeUInt16BE(buf, types.toType(sig.typeCovered), offset)
offset += 2
buf[offset] = sig.algorithm
offset += 1
buf[offset] = sig.labels
offset += 1
b.writeUInt32BE(buf, sig.originalTTL, offset)
offset += 4
b.writeUInt32BE(buf, sig.expiration, offset)
offset += 4
b.writeUInt32BE(buf, sig.inception, offset)
offset += 4
b.writeUInt16BE(buf, sig.keyTag, offset)
offset += 2
name.encode(sig.signersName, buf, offset)
offset += name.encode.bytes
b.copy(signature, buf, offset, 0, signature.length)
offset += signature.length
rrrsig.encode.bytes = offset - oldOffset
b.writeUInt16BE(buf, rrrsig.encode.bytes - 2, oldOffset)
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const oldOffset = offset
const sig = {}
const length = b.readUInt16BE(buf, offset)
offset += 2
sig.typeCovered = types.toString(b.readUInt16BE(buf, offset))
offset += 2
sig.algorithm = buf[offset]
offset += 1
sig.labels = buf[offset]
offset += 1
sig.originalTTL = b.readUInt32BE(buf, offset)
offset += 4
sig.expiration = b.readUInt32BE(buf, offset)
offset += 4
sig.inception = b.readUInt32BE(buf, offset)
offset += 4
sig.keyTag = b.readUInt16BE(buf, offset)
offset += 2
sig.signersName = name.decode(buf, offset)
offset += name.decode.bytes
sig.signature = buf.slice(offset, oldOffset + length + 2)
offset += sig.signature.length
rrrsig.decode.bytes = offset - oldOffset
return sig
},
encodingLength (sig) {
return 20 +
name.encodingLength(sig.signersName) +
b.bytelength(sig.signature)
}
})
const rrp = codec({
encode (data, buf, offset) {
if (!buf) buf = new Uint8Array(rrp.encodingLength(data))
if (!offset) offset = 0
const oldOffset = offset
offset += 2 // Leave space for length
name.encode(data.mbox || '.', buf, offset)
offset += name.encode.bytes
name.encode(data.txt || '.', buf, offset)
offset += name.encode.bytes
rrp.encode.bytes = offset - oldOffset
b.writeUInt16BE(buf, rrp.encode.bytes - 2, oldOffset)
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const oldOffset = offset
const data = {}
offset += 2
data.mbox = name.decode(buf, offset) || '.'
offset += name.decode.bytes
data.txt = name.decode(buf, offset) || '.'
offset += name.decode.bytes
rrp.decode.bytes = offset - oldOffset
return data
},
encodingLength (data) {
return 2 + name.encodingLength(data.mbox || '.') + name.encodingLength(data.txt || '.')
}
})
const typebitmap = codec({
encode (typelist, buf, offset) {
if (!buf) buf = new Uint8Array(typebitmap.encodingLength(typelist))
if (!offset) offset = 0
const oldOffset = offset
const typesByWindow = []
for (let i = 0; i < typelist.length; i++) {
const typeid = types.toType(typelist[i])
if (typesByWindow[typeid >> 8] === undefined) {
typesByWindow[typeid >> 8] = []
}
typesByWindow[typeid >> 8][(typeid >> 3) & 0x1F] |= 1 << (7 - (typeid & 0x7))
}
for (let i = 0; i < typesByWindow.length; i++) {
if (typesByWindow[i] !== undefined) {
const windowBuf = b.from(typesByWindow[i])
buf[offset] = i
offset += 1
buf[offset] = windowBuf.length
offset += 1
b.copy(windowBuf, buf, offset, 0, windowBuf.length)
offset += windowBuf.length
}
}
typebitmap.encode.bytes = offset - oldOffset
return buf
},
decode (buf, offset, length) {
if (!offset) offset = 0
const oldOffset = offset
const typelist = []
while (offset - oldOffset < length) {
const window = buf[offset]
offset += 1
const windowLength = buf[offset]
offset += 1
for (let i = 0; i < windowLength; i++) {
const b = buf[offset + i]
for (let j = 0; j < 8; j++) {
if (b & (1 << (7 - j))) {
const typeid = types.toString((window << 8) | (i << 3) | j)
typelist.push(typeid)
}
}
}
offset += windowLength
}
typebitmap.decode.bytes = offset - oldOffset
return typelist
},
encodingLength (typelist) {
const extents = []
for (let i = 0; i < typelist.length; i++) {
const typeid = types.toType(typelist[i])
extents[typeid >> 8] = Math.max(extents[typeid >> 8] || 0, typeid & 0xFF)
}
let len = 0
for (let i = 0; i < extents.length; i++) {
if (extents[i] !== undefined) {
len += 2 + Math.ceil((extents[i] + 1) / 8)
}
}
return len
}
})
const rnsec = codec({
encode (record, buf, offset) {
if (!buf) buf = new Uint8Array(rnsec.encodingLength(record))
if (!offset) offset = 0
const oldOffset = offset
offset += 2 // Leave space for length
name.encode(record.nextDomain, buf, offset)
offset += name.encode.bytes
typebitmap.encode(record.rrtypes, buf, offset)
offset += typebitmap.encode.bytes
rnsec.encode.bytes = offset - oldOffset
b.writeUInt16BE(buf, rnsec.encode.bytes - 2, oldOffset)
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const oldOffset = offset
const record = {}
const length = b.readUInt16BE(buf, offset)
offset += 2
record.nextDomain = name.decode(buf, offset)
offset += name.decode.bytes
record.rrtypes = typebitmap.decode(buf, offset, length - (offset - oldOffset))
offset += typebitmap.decode.bytes
rnsec.decode.bytes = offset - oldOffset
return record
},
encodingLength (record) {
return 2 +
name.encodingLength(record.nextDomain) +
typebitmap.encodingLength(record.rrtypes)
}
})
const rnsec3 = codec({
encode (record, buf, offset) {
if (!buf) buf = new Uint8Array(rnsec3.encodingLength(record))
if (!offset) offset = 0
const oldOffset = offset
const salt = record.salt
if (!b.isU8Arr(salt)) {
throw new Error('salt must be a Buffer')
}
const nextDomain = record.nextDomain
if (!b.isU8Arr(nextDomain)) {
throw new Error('nextDomain must be a Buffer')
}
offset += 2 // Leave space for length
buf[offset] = record.algorithm
offset += 1
buf[offset] = record.flags
offset += 1
b.writeUInt16BE(buf, record.iterations, offset)
offset += 2
buf[offset] = salt.length
offset += 1
b.copy(salt, buf, offset, 0, salt.length)
offset += salt.length
buf[offset] = nextDomain.length
offset += 1
b.copy(nextDomain, buf, offset, 0, nextDomain.length)
offset += nextDomain.length
typebitmap.encode(record.rrtypes, buf, offset)
offset += typebitmap.encode.bytes
rnsec3.encode.bytes = offset - oldOffset
b.writeUInt16BE(buf, rnsec3.encode.bytes - 2, oldOffset)
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const oldOffset = offset
const record = {}
const length = b.readUInt16BE(buf, offset)
offset += 2
record.algorithm = buf[offset]
offset += 1
record.flags = buf[offset]
offset += 1
record.iterations = b.readUInt16BE(buf, offset)
offset += 2
const saltLength = buf[offset]
offset += 1
record.salt = buf.slice(offset, offset + saltLength)
offset += saltLength
const hashLength = buf[offset]
offset += 1
record.nextDomain = buf.slice(offset, offset + hashLength)
offset += hashLength
record.rrtypes = typebitmap.decode(buf, offset, length - (offset - oldOffset))
offset += typebitmap.decode.bytes
rnsec3.decode.bytes = offset - oldOffset
return record
},
encodingLength (record) {
return 8 +
record.salt.length +
record.nextDomain.length +
typebitmap.encodingLength(record.rrtypes)
}
})
const rds = codec({
encode (digest, buf, offset) {
if (!buf) buf = new Uint8Array(rds.encodingLength(digest))
if (!offset) offset = 0
const oldOffset = offset
const digestdata = digest.digest
if (!b.isU8Arr(digestdata)) {
throw new Error('Digest must be a Buffer')
}
offset += 2 // Leave space for length
b.writeUInt16BE(buf, digest.keyTag, offset)
offset += 2
buf[offset] = digest.algorithm
offset += 1
buf[offset] = digest.digestType
offset += 1
b.copy(digestdata, buf, offset, 0, digestdata.length)
offset += digestdata.length
rds.encode.bytes = offset - oldOffset
b.writeUInt16BE(buf, rds.encode.bytes - 2, oldOffset)
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const oldOffset = offset
const digest = {}
const length = b.readUInt16BE(buf, offset)
offset += 2
digest.keyTag = b.readUInt16BE(buf, offset)
offset += 2
digest.algorithm = buf[offset]
offset += 1
digest.digestType = buf[offset]
offset += 1
digest.digest = buf.slice(offset, oldOffset + length + 2)
offset += digest.digest.length
rds.decode.bytes = offset - oldOffset
return digest
},
encodingLength (digest) {
return 6 + b.bytelength(digest.digest)
}
})
const rsshfp = codec({
encode (record, buf, offset) {
if (!buf) buf = new Uint8Array(rsshfp.encodingLength(record))
if (!offset) offset = 0
const oldOffset = offset
offset += 2 // The function call starts with the offset pointer at the RDLENGTH field, not the RDATA one
buf[offset] = record.algorithm
offset += 1
buf[offset] = record.hash
offset += 1
const fingerprintLength = b.hexLength(record.fingerprint)
const expectedLength = getFingerprintLengthForHashType(record.hash)
if (fingerprintLength !== expectedLength) {
throw new Error(`Invalid length of fingerprint "${record.fingerprint}" for hashType=${record.hash}: ${fingerprintLength} != ${expectedLength}`)
}
b.writeHex(buf, record.fingerprint, offset, offset += fingerprintLength)
rsshfp.encode.bytes = offset - oldOffset
b.writeUInt16BE(buf, rsshfp.encode.bytes - 2, oldOffset)
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const oldOffset = offset
const record = {}
offset += 2 // Account for the RDLENGTH field
record.algorithm = buf[offset]
offset += 1
record.hash = buf[offset]
offset += 1
const fingerprintLength = getFingerprintLengthForHashType(record.hash)
record.fingerprint = b.toHex(buf, offset, offset + fingerprintLength)
offset += fingerprintLength
rsshfp.decode.bytes = offset - oldOffset
return record
},
encodingLength (record) {
return 4 + b.hexLength(record.fingerprint)
}
})
function getFingerprintLengthForHashType (hashType) {
if (hashType === 1) return 20
if (hashType === 2) return 32
throw new Error(`Invalid hashType=${hashType}, supported=1,2`)
}
rsshfp.getFingerprintLengthForHashType = getFingerprintLengthForHashType
function renc (type) {
switch (type.toUpperCase()) {
case 'A': return ra
case 'PTR': return rptr
case 'CNAME': return rptr
case 'DNAME': return rptr
case 'TXT': return rtxt
case 'NULL': return rnull
case 'AAAA': return raaaa
case 'SRV': return rsrv
case 'HINFO': return rhinfo
case 'CAA': return rcaa
case 'NS': return rns
case 'SOA': return rsoa
case 'MX': return rmx
case 'OPT': return ropt
case 'DNSKEY': return rdnskey
case 'RRSIG': return rrrsig
case 'RP': return rrp
case 'NSEC': return rnsec
case 'NSEC3': return rnsec3
case 'SSHFP': return rsshfp
case 'DS': return rds
}
return runknown
}
export const answer = codec({
encode (a, buf, offset) {
if (!buf) buf = new Uint8Array(answer.encodingLength(a))
if (!offset) offset = 0
const oldOffset = offset
name.encode(a.name, buf, offset)
offset += name.encode.bytes
b.writeUInt16BE(buf, types.toType(a.type), offset)
if (a.type.toUpperCase() === 'OPT') {
if (a.name !== '.') {
throw new Error('OPT name must be root.')
}
b.writeUInt16BE(buf, a.udpPayloadSize || 4096, offset + 2)
buf[offset + 4] = a.extendedRcode || 0
buf[offset + 5] = a.ednsVersion || 0
b.writeUInt16BE(buf, a.flags || 0, offset + 6)
offset += 8
ropt.encode(a.options || [], buf, offset)
offset += ropt.encode.bytes
} else {
let klass = classes.toClass(a.class === undefined ? 'IN' : a.class)
if (a.flush) klass |= FLUSH_MASK // the 1st bit of the class is the flush bit
b.writeUInt16BE(buf, klass, offset + 2)
b.writeUInt32BE(buf, a.ttl || 0, offset + 4)
offset += 8
const enc = renc(a.type)
enc.encode(a.data, buf, offset)
offset += enc.encode.bytes
}
answer.encode.bytes = offset - oldOffset
return buf
},
decode (buf, offset) {
if (!offset) offset = 0
const a = {}
const oldOffset = offset
a.name = name.decode(buf, offset)
offset += name.decode.bytes
a.type = types.toString(b.readUInt16BE(buf, offset))
if (a.type === 'OPT') {
a.udpPayloadSize = b.readUInt16BE(buf, offset + 2)
a.extendedRcode = buf[offset + 4]
a.ednsVersion = buf[offset + 5]
a.flags = b.readUInt16BE(buf, offset + 6)
a.flag_do = ((a.flags >> 15) & 0x1) === 1
a.options = ropt.decode(buf, offset + 8)
offset += 8 + ropt.decode.bytes
} else {
const klass = b.readUInt16BE(buf, offset + 2)
a.ttl = b.readUInt32BE(buf, offset + 4)
a.class = classes.toString(klass & NOT_FLUSH_MASK)
a.flush = !!(klass & FLUSH_MASK)
const enc = renc(a.type)
a.data = enc.decode(buf, offset + 8)
offset += 8 + enc.decode.bytes
}
answer.decode.bytes = offset - oldOffset
return a
},
encodingLength (a) {
const data = (a.data !== null && a.data !== undefined) ? a.data : a.options
return name.encodingLength(a.name) + 8 + renc(a.type).encodingLength(data)
}
})
export const question = codec({
encode (q, buf, offset) {
if (!buf) buf = new Uint8Array(question.encodingLength(q))
if (!offset) offset = 0
const oldOffset = offset
name.encode(q.name, buf, offset)
offset += name.encode.bytes
b.writeUInt16BE(buf, types.toType(q.type), offset)
offset += 2
b.writeUInt16BE(buf, classes.toClass(q.class === undefined ? 'IN' : q.class), offset)
offset += 2
question.encode.bytes = offset - oldOffset
return q
},
decode (buf, offset) {
if (!offset) offset = 0
const oldOffset = offset
const q = {}
q.name = name.decode(buf, offset)
offset += name.decode.bytes
q.type = types.toString(b.readUInt16BE(buf, offset))
offset += 2
q.class = classes.toString(b.readUInt16BE(buf, offset))
offset += 2
const qu = !!(q.class & QU_MASK)
if (qu) q.class &= NOT_QU_MASK
question.decode.bytes = offset - oldOffset
return q
},
encodingLength (q) {
return name.encodingLength(q.name) + 4
}
})
export {
rsoa as soa,
rtxt as txt,
rnull as null,
runknown as unknown,
rns as ns,
rhinfo as hinfo,
rptr as ptr,
rptr as cname,
rptr as dname,
rsrv as srv,
rcaa as caa,
rmx as mx,
ra as a,
raaaa as aaaa,
roption as option,
ropt as opt,
rdnskey as dnskey,
rrrsig as rrsig,
rrp as rp,
rnsec as nsec,
rnsec3 as nsec3,
rds as ds,
rsshfp as sshfp,
renc as enc
}
export const AUTHORITATIVE_ANSWER = 1 << 10
export const TRUNCATED_RESPONSE = 1 << 9
export const RECURSION_DESIRED = 1 << 8
export const RECURSION_AVAILABLE = 1 << 7
export const AUTHENTIC_DATA = 1 << 5
export const CHECKING_DISABLED = 1 << 4
export const DNSSEC_OK = 1 << 15
export const packet = {
encode: function (result, buf, offset) {
const allocing = !buf
if (allocing) buf = new Uint8Array(encodingLength(result))
if (!offset) offset = 0
const oldOffset = offset
if (!result.questions) result.questions = []
if (!result.answers) result.answers = []
if (!result.authorities) result.authorities = []
if (!result.additionals) result.additionals = []
header.encode(result, buf, offset)
offset += header.encode.bytes
offset = encodeList(result.questions, question, buf, offset)
offset = encodeList(result.answers, answer, buf, offset)
offset = encodeList(result.authorities, answer, buf, offset)
offset = encodeList(result.additionals, answer, buf, offset)
packet.encode.bytes = offset - oldOffset
// just a quick sanity check
if (allocing && encode.bytes !== buf.length) {
return buf.slice(0, encode.bytes)
}
return buf
},
decode: function (buf, offset) {
if (!offset) offset = 0
const oldOffset = offset
const result = header.decode(buf, offset)
offset += header.decode.bytes
offset = decodeList(result.questions, question, buf, offset)
offset = decodeList(result.answers, answer, buf, offset)
offset = decodeList(result.authorities, answer, buf, offset)
offset = decodeList(result.additionals, answer, buf, offset)
packet.decode.bytes = offset - oldOffset
return result
},
encodingLength: function (result) {
return header.encodingLength(result) +
encodingLengthList(result.questions || [], question) +
encodingLengthList(result.answers || [], answer) +
encodingLengthList(result.authorities || [], answer) +
encodingLengthList(result.additionals || [], answer)
}
}
packet.encode.bytes = 0
packet.decode.bytes = 0
function sanitizeSingle (input, type) {
if (input.questions) {
throw new Error('Only one .question object expected instead of a .questions array!')
}
const sanitized = Object.assign({
type
}, input)
if (sanitized.question) {
sanitized.questions = [sanitized.question]
delete sanitized.question
}
return sanitized
}
export const query = {
encode: function (result, buf, offset) {
buf = packet.encode(sanitizeSingle(result, 'query'), buf, offset)
query.encode.bytes = packet.encode.bytes
return buf
},
decode: function (buf, offset) {
const res = packet.decode(buf, offset)
query.decode.bytes = packet.decode.bytes
if (res.questions) {
res.question = res.questions[0]
delete res.questions
}
return res
},
encodingLength: function (result) {
return packet.encodingLength(sanitizeSingle(result, 'query'))
}
}
query.encode.bytes = 0
query.decode.bytes = 0
export const response = {
encode: function (result, buf, offset) {
buf = packet.encode(sanitizeSingle(result, 'response'), buf, offset)
response.encode.bytes = packet.encode.bytes
return buf
},
decode: function (buf, offset) {
const res = packet.decode(buf, offset)
response.decode.bytes = packet.decode.bytes
if (res.questions) {
res.question = res.questions[0]
delete res.questions
}
return res
},
encodingLength: function (result) {
return packet.encodingLength(sanitizeSingle(result, 'response'))
}
}
response.encode.bytes = 0
response.decode.bytes = 0
export const encode = packet.encode
export const decode = packet.decode
export const encodingLength = packet.encodingLength
export function streamEncode (result) {
const buf = encode(result)
const combine = new Uint8Array(2 + buf.byteLength)
b.writeUInt16BE(combine, buf.byteLength)
b.copy(buf, combine, 2, 0, buf.length)
streamEncode.bytes = combine.byteLength
return combine
}
streamEncode.bytes = 0
export function streamDecode (sbuf) {
const len = b.readUInt16BE(sbuf, 0)
if (sbuf.byteLength < len + 2) {
// not enough data
return null
}
const result = decode(sbuf.slice(2))
streamDecode.bytes = decode.bytes
return result
}
streamDecode.bytes = 0
export function encodingLengthList (list, enc) {
let len = 0
for (let i = 0; i < list.length; i++) len += enc.encodingLength(list[i])
return len
}
export function encodeList (list, enc, buf, offset) {
for (let i = 0; i < list.length; i++) {
enc.encode(list[i], buf, offset)
offset += enc.encode.bytes
}
return offset
}
export function decodeList (list, enc, buf, offset) {
for (let i = 0; i < list.length; i++) {
list[i] = enc.decode(buf, offset)
offset += enc.decode.bytes
}
return offset
}