diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0698f10 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# CHANGELOG + +## 2023.04.06 + +- imports `Buffer` from Deno's internals +- adds `NAPTR` support from upstream +- declares optional parameters in functions +- adds tests +- removes import map +- updates README diff --git a/README.md b/README.md index 4984da9..a7c0550 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,14 @@ This module is forked from [mafintosh/dns-packet](https://github.com/mafintosh/dns-packet) because I was trying to track down a Deno compile issue and thought `dns-packet` was the culprit. I was mistaken but I went through the work of refactoring this so no need for that to go to waste. Improvements? Modularity. I'm not a fan of everything in one massive file. Added benefit of TypeScript is that several linting issues were caught and fixed. -This README will be updated when I'm done with my larger project that contains this module. For now, just import what you need from `mod.ts` and refer to the original repo for details. +This README will be updated when I'm done with my larger project that imports this module. For now, just import what you need from `mod.ts` and refer to [`test.ts`](/test.ts) for usage/reference. + +High-level changes: +- written in TypeScript (for Deno) + - caught and fixed issues with upstream code + - you can still import into Node.js projects +- modularity + - every record is its own file + - utility functions are in their own folder as well +- record functions are now classes so they must be instantiated with `new` +- replaced `decode.bytes` and `encode.bytes` with `decodeBytes`and `encodeBytes` respectively diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..ac5f1e6 --- /dev/null +++ b/TODO.md @@ -0,0 +1,6 @@ +# TODO + +- [ ] get rid of implicit `any` types +- [ ] get all tests running +- [ ] figure out `rrtpyes` issue in the NSEC test +- [ ] add examples diff --git a/deno.json b/deno.json index 40d6849..8dd8c4d 100644 --- a/deno.json +++ b/deno.json @@ -2,7 +2,6 @@ "compilerOptions": { "noImplicitAny": false }, - "importMap": "import_map.json", "lint": { "rules": { "exclude": ["no-explicit-any"] diff --git a/deno.lock b/deno.lock index b9b9072..e0250a6 100644 --- a/deno.lock +++ b/deno.lock @@ -1,34 +1,20 @@ { "version": "2", "remote": { - "https://deno.land/std@0.166.0/_util/asserts.ts": "d0844e9b62510f89ce1f9878b046f6a57bf88f208a10304aab50efcb48365272", - "https://deno.land/std@0.166.0/_util/os.ts": "8a33345f74990e627b9dfe2de9b040004b08ea5146c7c9e8fe9a29070d193934", - "https://deno.land/std@0.166.0/bytes/equals.ts": "3c3558c3ae85526f84510aa2b48ab2ad7bdd899e2e0f5b7a8ffc85acb3a6043a", - "https://deno.land/std@0.166.0/bytes/mod.ts": "b2e342fd3669176a27a4e15061e9d588b89c1aaf5008ab71766e23669565d179", - "https://deno.land/std@0.166.0/encoding/base64.ts": "c57868ca7fa2fbe919f57f88a623ad34e3d970d675bdc1ff3a9d02bba7409db2", - "https://deno.land/std@0.166.0/encoding/base64url.ts": "a5f82a9fa703bd85a5eb8e7c1296bc6529e601ebd9642cc2b5eaa6b38fa9e05a", - "https://deno.land/std@0.166.0/node/_utils.ts": "1085a229e910b3a6672f3c3be05507591811dc67be2ae2188e9fc9ef4376b172", - "https://deno.land/std@0.166.0/node/internal/buffer.mjs": "70b74b34f1617b3492aee6dec35c7bdabbf26e034bfcf640aa61b3d63c5c850f", - "https://deno.land/std@0.166.0/node/internal/crypto/_keys.ts": "63229ff3d8d15b5bd0a1d2ebc19313cbb8ac969875bf16df1ce4f2b497d74fb5", - "https://deno.land/std@0.166.0/node/internal/crypto/constants.ts": "d2c8821977aef55e4d66414d623c24a2447791a8b49b6404b8db32d81e20c315", - "https://deno.land/std@0.166.0/node/internal/error_codes.ts": "ac03c4eae33de3a69d6c98e8678003207eecf75a6900eb847e3fea3c8c9e6d8f", - "https://deno.land/std@0.166.0/node/internal/errors.ts": "b9aec7d1fe3eaf21322d0ea9dc2fcb344055d6b0c7a1bd0f62a0c379a5baa799", - "https://deno.land/std@0.166.0/node/internal/hide_stack_frames.ts": "a91962ec84610bc7ec86022c4593cdf688156a5910c07b5bcd71994225c13a03", - "https://deno.land/std@0.166.0/node/internal/normalize_encoding.mjs": "3779ec8a7adf5d963b0224f9b85d1bc974a2ec2db0e858396b5d3c2c92138a0a", - "https://deno.land/std@0.166.0/node/internal/primordials.mjs": "7cf5afe471583e4a384eeea109bdff276b6b7f3a3af830f99f951fb7d57ef423", - "https://deno.land/std@0.166.0/node/internal/util.mjs": "35d24fb775468cd24443bcf0ec68904b8aa44e5b53845491a5e3382421917f9a", - "https://deno.land/std@0.166.0/node/internal/util/inspect.mjs": "1ddace0c97719d2cc0869ba177d375e96051301352ec235cbfb2ecbfcd4e8fba", - "https://deno.land/std@0.166.0/node/internal/util/types.ts": "de6e2b7f9b9985ab881b1e78f05ae51d1fc829ae1584063df21e57b35312f3c4", - "https://deno.land/std@0.166.0/node/internal/validators.mjs": "67deae0f488d013c8bf485742a5478112b8e48837cb2458c4a8b2669cf7017db", - "https://deno.land/std@0.166.0/node/internal_binding/_libuv_winerror.ts": "801e05c2742ae6cd42a5f0fd555a255a7308a65732551e962e5345f55eedc519", - "https://deno.land/std@0.166.0/node/internal_binding/_node.ts": "e4075ba8a37aef4eb5b592c8e3807c39cb49ca8653faf8e01a43421938076c1b", - "https://deno.land/std@0.166.0/node/internal_binding/_utils.ts": "1c50883b5751a9ea1b38951e62ed63bacfdc9d69ea665292edfa28e1b1c5bd94", - "https://deno.land/std@0.166.0/node/internal_binding/_winerror.ts": "8811d4be66f918c165370b619259c1f35e8c3e458b8539db64c704fbde0a7cd2", - "https://deno.land/std@0.166.0/node/internal_binding/buffer.ts": "781e1d13adc924864e6e37ecb5152e8a4e994cf394695136e451c47f00bda76c", - "https://deno.land/std@0.166.0/node/internal_binding/constants.ts": "1ad4de9f76733320527c8bc841b5e4dd5869424924384157a72f3b171bd05b08", - "https://deno.land/std@0.166.0/node/internal_binding/string_decoder.ts": "5cb1863763d1e9b458bc21d6f976f16d9c18b3b3f57eaf0ade120aee38fba227", - "https://deno.land/std@0.166.0/node/internal_binding/types.ts": "4c26fb74ba2e45de553c15014c916df6789529a93171e450d5afb016b4c765e7", - "https://deno.land/std@0.166.0/node/internal_binding/util.ts": "faf5146c3cc3b2d6c26026a818b4a16e91488ab26e63c069f36ba3c3ae24c97b", - "https://deno.land/std@0.166.0/node/internal_binding/uv.ts": "27922aaec43de314afd99dfca1ce8f4d51ced9f5195e4917b54d387766404f61" + "https://deno.land/std@0.182.0/fmt/colors.ts": "d67e3cd9f472535241a8e410d33423980bec45047e343577554d3356e1f0ef4e", + "https://deno.land/std@0.182.0/testing/_diff.ts": "1a3c044aedf77647d6cac86b798c6417603361b66b54c53331b312caeb447aea", + "https://deno.land/std@0.182.0/testing/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", + "https://deno.land/std@0.182.0/testing/asserts.ts": "e16d98b4d73ffc4ed498d717307a12500ae4f2cbe668f1a215632d19fcffc22f" + }, + "npm": { + "specifiers": { + "@types/node": "@types/node@18.11.18" + }, + "packages": { + "@types/node@18.11.18": { + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", + "dependencies": {} + } + } } } diff --git a/import_map.json b/import_map.json deleted file mode 100644 index 941f539..0000000 --- a/import_map.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "imports": { - "/": "./", - "./": "./" - } -} diff --git a/mod.ts b/mod.ts index 0d2c044..e3e67e2 100644 --- a/mod.ts +++ b/mod.ts @@ -19,6 +19,7 @@ export { DS } from "./src/records/ds.ts"; export { PTR as CNAME } from "./src/records/ptr.ts"; export { PTR as DNAME } from "./src/records/ptr.ts"; export { HINFO } from "./src/records/hinfo.ts"; +export { NAPTR } from "./src/records/naptr.ts"; export { MX } from "./src/records/mx.ts"; export { NS } from "./src/records/ns.ts"; export { NSEC } from "./src/records/nsec.ts"; diff --git a/src/answer.ts b/src/answer.ts index 5f6cd2c..269e0e5 100644 --- a/src/answer.ts +++ b/src/answer.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; /// util @@ -22,10 +22,11 @@ export class Answer { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { - if (!offset) offset = 0; + decode(buf, offset?) { + if (!offset) + offset = 0; - const a: { [key: string]: any; } = {}; + const a: { [key: string]: unknown; } = {}; const name = new Name(); const oldOffset = offset; const opt = new OPT(); @@ -39,7 +40,7 @@ export class Answer { a.extendedRcode = buf.readUInt8(offset + 4); a.ednsVersion = buf.readUInt8(offset + 5); a.flags = buf.readUInt16BE(offset + 6); - a.flag_do = ((a.flags >> 15) & 0x1) === 1; + a.flag_do = ((Number(a.flags) >> 15) & 0x1) === 1; a.options = opt.decode(buf, offset + 8); offset += 8 + opt.decodeBytes; } else { @@ -59,7 +60,7 @@ export class Answer { return a; } - encode(a, buf, offset) { + encode(a, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(a)); diff --git a/src/default.ts b/src/default.ts index a409a37..29854a4 100644 --- a/src/default.ts +++ b/src/default.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; /// util diff --git a/src/header.ts b/src/header.ts index a7f5bb1..ab78fb1 100644 --- a/src/header.ts +++ b/src/header.ts @@ -15,7 +15,7 @@ export class Header { decodeBytes = 12; encodeBytes = 12; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; @@ -47,7 +47,7 @@ export class Header { } } - encode(h, buf, offset) { + encode(h, buf?, offset?) { if (!buf) buf = this.encodingLength(); diff --git a/src/name.ts b/src/name.ts index 05da0bc..2b2de4d 100644 --- a/src/name.ts +++ b/src/name.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; @@ -13,7 +13,7 @@ export class Name { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; @@ -74,7 +74,7 @@ export class Name { list.join("."); } - encode(str, buf, offset) { + encode(str, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(str)); diff --git a/src/question.ts b/src/question.ts index e6aad92..68ae5ba 100644 --- a/src/question.ts +++ b/src/question.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; /// util @@ -20,13 +20,13 @@ export class Question { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; const name = new Name(); const oldOffset = offset; - const q: { [key: string]: any; } = {}; + const q: { [key: string]: unknown; } = {}; q.name = name.decode(buf, offset); offset += name.decodeBytes; @@ -35,16 +35,16 @@ export class Question { q.class = classes.toString(buf.readUInt16BE(offset)); offset += 2; - const qu = !!(q.class & QU_MASK); + const qu = !!(Number(q.class) & QU_MASK); - if (qu) - q.class &= NOT_QU_MASK; + if (qu) // `q.class &= NOT_QU_MASK` is equivalent to `q.class = q.class & NOT_QU_MASK`. + q.class = Number(q.class) & NOT_QU_MASK; this.decodeBytes = offset - oldOffset; return q; } - encode(q, buf, offset) { + encode(q, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(q)); diff --git a/src/record.ts b/src/record.ts index 3cc2273..5f816c2 100644 --- a/src/record.ts +++ b/src/record.ts @@ -10,6 +10,7 @@ import { DNSKEY } from "./records/dnskey.ts"; import { DS } from "./records/ds.ts"; import { HINFO } from "./records/hinfo.ts"; import { MX } from "./records/mx.ts"; +import { NAPTR } from "./records/naptr.ts"; import { NS } from "./records/ns.ts"; import { NSEC } from "./records/nsec.ts"; import { NSEC3 } from "./records/nsec3.ts"; @@ -56,6 +57,9 @@ export function Record(type) { case "MX": return new MX(); + case "NAPTR": + return new NAPTR(); + case "NS": return new NS(); diff --git a/src/records/a.ts b/src/records/a.ts index f23befc..a57eea5 100644 --- a/src/records/a.ts +++ b/src/records/a.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; /// util @@ -17,7 +17,7 @@ export class A { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; @@ -28,7 +28,7 @@ export class A { return host; } - encode(host, buf, offset) { + encode(host, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength()); diff --git a/src/records/aaaa.ts b/src/records/aaaa.ts index bbc77ce..f25e69d 100644 --- a/src/records/aaaa.ts +++ b/src/records/aaaa.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; /// util @@ -17,7 +17,7 @@ export class AAAA { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; @@ -28,7 +28,7 @@ export class AAAA { return host; } - encode(host, buf, offset) { + encode(host, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength()); diff --git a/src/records/caa.ts b/src/records/caa.ts index 101ec6d..eb39af6 100644 --- a/src/records/caa.ts +++ b/src/records/caa.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; /// util @@ -18,31 +18,31 @@ export class CAA { encodeBytes = 0; ISSUER_CRITICAL = 1 << 7; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; const len = buf.readUInt16BE(offset); offset += 2; - const data: { [key: string]: any; } = {}; + const data: { [key: string]: unknown; } = {}; const oldOffset = offset; - const sstring = new String(); + const string = new String(); data.flags = buf.readUInt8(offset); offset += 1; - data.tag = sstring.decode(buf, offset); - offset += sstring.decodeBytes; + data.tag = string.decode(buf, offset); + offset += string.decodeBytes; data.value = buf.toString("utf-8", offset, oldOffset + len); - data.issuerCritical = !!(data.flags & this.ISSUER_CRITICAL); + data.issuerCritical = !!(Number(data.flags) & this.ISSUER_CRITICAL); this.decodeBytes = len + 2; return data; } - encode(data, buf, offset) { + encode(data, buf?, offset?) { const len = this.encodingLength(data); - const sstring = new String(); + const string = new String(); if (!buf) buf = Buffer.alloc(this.encodingLength(data)); @@ -57,8 +57,8 @@ export class CAA { offset += 2; buf.writeUInt8(data.flags || 0, offset); offset += 1; - sstring.encode(data.tag, buf, offset); - offset += sstring.encodeBytes; + string.encode(data.tag, buf, offset); + offset += string.encodeBytes; buf.write(data.value, offset); offset += Buffer.byteLength(data.value); this.encodeBytes = len; @@ -67,10 +67,10 @@ export class CAA { } encodingLength(data) { - const sstring = new String(); + const string = new String(); - return sstring.encodingLength(data.tag) + - sstring.encodingLength(data.value) + + return string.encodingLength(data.tag) + + string.encodingLength(data.value) + 2; } } diff --git a/src/records/dnskey.ts b/src/records/dnskey.ts index 5a045d9..a954914 100644 --- a/src/records/dnskey.ts +++ b/src/records/dnskey.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; @@ -16,12 +16,12 @@ export class DNSKEY { SECURE_ENTRYPOINT = 0x8000; ZONE_KEY = 0x80; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; const oldOffset = offset; - const key: { [key: string]: any; } = {}; + const key: { [key: string]: unknown; } = {}; const length = buf.readUInt16BE(offset); offset += 2; @@ -35,13 +35,13 @@ export class DNSKEY { key.algorithm = buf.readUInt8(offset); offset += 1; key.key = buf.slice(offset, oldOffset + length + 2); - offset += key.key.length; + offset += (key.key as Buffer).length; this.decodeBytes = offset - oldOffset; return key; } - encode(key, buf, offset) { + encode(key, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(key)); diff --git a/src/records/ds.ts b/src/records/ds.ts index 1518b3e..7fa1d22 100644 --- a/src/records/ds.ts +++ b/src/records/ds.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; @@ -13,11 +13,11 @@ export class DS { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; - const digest: { [key: string]: any; } = {}; + const digest: { [key: string]: unknown; } = {}; const length = buf.readUInt16BE(offset); const oldOffset = offset; @@ -29,13 +29,13 @@ export class DS { digest.digestType = buf.readUInt8(offset); offset += 1; digest.digest = buf.slice(offset, oldOffset + length + 2); - offset += digest.digest.length; + offset += (digest.digest as Buffer).length; this.decodeBytes = offset - oldOffset; return digest; } - encode(digest, buf, offset) { + encode(digest, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(digest)); diff --git a/src/records/hinfo.ts b/src/records/hinfo.ts index 07dfd5d..0a5ccea 100644 --- a/src/records/hinfo.ts +++ b/src/records/hinfo.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; /// util @@ -17,11 +17,11 @@ export class HINFO { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; - const data: { [key: string]: any; } = {}; + const data: { [key: string]: unknown; } = {}; const oldOffset = offset; const sstring = new String(); @@ -35,7 +35,7 @@ export class HINFO { return data; } - encode(data, buf, offset) { + encode(data, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(data)); diff --git a/src/records/mx.ts b/src/records/mx.ts index 186f881..0a436e9 100644 --- a/src/records/mx.ts +++ b/src/records/mx.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; /// util @@ -17,11 +17,11 @@ export class MX { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; - const data: { [key: string]: any; } = {}; + const data: { [key: string]: unknown; } = {}; const name = new Name(); const oldOffset = offset; @@ -35,7 +35,7 @@ export class MX { return data; } - encode(data, buf, offset) { + encode(data, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(data)); diff --git a/src/records/naptr.ts b/src/records/naptr.ts new file mode 100644 index 0000000..d401126 --- /dev/null +++ b/src/records/naptr.ts @@ -0,0 +1,97 @@ + + + +/// import + +import { Buffer } from "node:buffer"; + +/// util + +import { Name } from "../name.ts"; +import { String } from "../string.ts"; + + + +/// export + +export class NAPTR { + decodeBytes = 0; + encodeBytes = 0; + + decode(buf, offset?) { + if (!offset) + offset = 0; + + const data: { [key: string]: unknown; } = {}; + const name = new Name(); + const oldOffset = offset; + const string = new String(); + + offset += 2; + data.order = buf.readUInt16BE(offset); + + offset += 2; + data.preference = buf.readUInt16BE(offset); + + offset += 2; + data.flags = string.decode(buf, offset); + + offset += string.decodeBytes; + data.services = string.decode(buf, offset); + + offset += string.decodeBytes; + data.regexp = string.decode(buf, offset); + + offset += string.decodeBytes; + data.replacement = name.decode(buf, offset); + + offset += name.decodeBytes; + this.decodeBytes = offset - oldOffset; + + return data; + } + + encode(data, buf?, offset?) { + if (!buf) + buf = Buffer.alloc(this.encodingLength(data)); + + if (!offset) + offset = 0; + + const name = new Name(); + const oldOffset = offset; + const string = new String(); + + offset += 2; + buf.writeUInt16BE(data.order || 0, offset); + offset += 2; + buf.writeUInt16BE(data.preference || 0, offset); + offset += 2; + + string.encode(data.flags, buf, offset); + offset += string.encodeBytes; + + string.encode(data.services, buf, offset); + offset += string.encodeBytes; + + string.encode(data.regexp, buf, offset); + offset += string.encodeBytes; + + name.encode(data.replacement, buf, offset); + offset += name.encodeBytes; + + this.encodeBytes = offset - oldOffset; + buf.writeUInt16BE(this.encodeBytes - 2, oldOffset); + + return buf; + } + + encodingLength(data) { + const string = new String(); + + return string.encodingLength(data.flags) + + string.encodingLength(data.services) + + string.encodingLength(data.regexp) + + new Name().encodingLength(data.replacement) + 6; + } +} diff --git a/src/records/ns.ts b/src/records/ns.ts index 0fe5ed1..f1d24c3 100644 --- a/src/records/ns.ts +++ b/src/records/ns.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; /// util @@ -17,7 +17,7 @@ export class NS { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; @@ -28,7 +28,7 @@ export class NS { return dd; } - encode(data, buf, offset) { + encode(data, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(data)); diff --git a/src/records/nsec.ts b/src/records/nsec.ts index 601803c..0b5269d 100644 --- a/src/records/nsec.ts +++ b/src/records/nsec.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; /// util @@ -18,14 +18,14 @@ export class NSEC { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; const length = buf.readUInt16BE(offset); const name = new Name(); const oldOffset = offset; - const record: { [key: string]: any; } = {}; + const record: { [key: string]: unknown; } = {}; const typebitmap = new TypeBitmap(); offset += 2; @@ -38,7 +38,7 @@ export class NSEC { return record; } - encode(record, buf, offset) { + encode(record, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(record)); diff --git a/src/records/nsec3.ts b/src/records/nsec3.ts index 8d42359..9b90dbe 100644 --- a/src/records/nsec3.ts +++ b/src/records/nsec3.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; /// util @@ -17,13 +17,13 @@ export class NSEC3 { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; const length = buf.readUInt16BE(offset); const oldOffset = offset; - const record: { [key: string]: any; } = {}; + const record: { [key: string]: unknown; } = {}; const typebitmap = new TypeBitmap(); offset += 2; @@ -50,7 +50,7 @@ export class NSEC3 { return record; } - encode(record, buf, offset) { + encode(record, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(record)); @@ -88,6 +88,10 @@ export class NSEC3 { offset += typebitmap.encodeBytes; this.decodeBytes = offset - oldOffset; + + // if (this.encodeBytes - 2 < 0 || > 65535) + // throw new Error("value is out of range"); + buf.writeUInt16BE(this.encodeBytes - 2, oldOffset); return buf; } diff --git a/src/records/null.ts b/src/records/null.ts index 11542d8..b58849c 100644 --- a/src/records/null.ts +++ b/src/records/null.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; @@ -13,7 +13,7 @@ export class NULL { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; @@ -29,7 +29,7 @@ export class NULL { return data; } - encode(data, buf, offset) { + encode(data, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(data)); diff --git a/src/records/opt.ts b/src/records/opt.ts index b2f380c..c9ca2f6 100644 --- a/src/records/opt.ts +++ b/src/records/opt.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; /// util @@ -18,13 +18,13 @@ export class OPT { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; const oldOffset = offset; const option = new OPTION(); - const options: Array<{ [key: string]: any; }> = []; + const options: Array<{ [key: string]: unknown; }> = []; let o = 0; let rdlen = buf.readUInt16BE(offset); @@ -40,7 +40,7 @@ export class OPT { return options; } - encode(options, buf, offset) { + encode(options, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(options)); diff --git a/src/records/option.ts b/src/records/option.ts index ab7eec7..d084af5 100644 --- a/src/records/option.ts +++ b/src/records/option.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; /// util @@ -18,11 +18,11 @@ export class OPTION { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; - const option: { [key: string]: any; } = {}; + const option: { [key: string]: unknown; } = {}; option.code = buf.readUInt16BE(offset); option.type = optioncodes.toString(option.code); offset += 2; @@ -62,7 +62,7 @@ export class OPTION { option.tags = []; for (let i = 0; i < len; i += 2) { - option.tags.push(buf.readUInt16BE(offset)); + (option.tags as unknown[]).push(buf.readUInt16BE(offset)); offset += 2; } } @@ -74,7 +74,7 @@ export class OPTION { return option; } - encode(option, buf, offset) { + encode(option, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(option)); diff --git a/src/records/ptr.ts b/src/records/ptr.ts index 4dc31b2..1895725 100644 --- a/src/records/ptr.ts +++ b/src/records/ptr.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; /// util @@ -17,7 +17,7 @@ export class PTR { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; @@ -28,7 +28,7 @@ export class PTR { return data; } - encode(data, buf, offset) { + encode(data, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(data)); diff --git a/src/records/rp.ts b/src/records/rp.ts index f75df88..afc51d4 100644 --- a/src/records/rp.ts +++ b/src/records/rp.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; /// util @@ -17,11 +17,11 @@ export class RP { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; - const data: { [key: string]: any; } = {}; + const data: { [key: string]: unknown; } = {}; const name = new Name(); const oldOffset = offset; @@ -35,7 +35,7 @@ export class RP { return data; } - encode(data, buf, offset) { + encode(data, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(data)); diff --git a/src/records/rrsig.ts b/src/records/rrsig.ts index 38f9862..f3bf585 100644 --- a/src/records/rrsig.ts +++ b/src/records/rrsig.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; /// util @@ -18,14 +18,14 @@ export class RRSIG { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; const oldOffset = offset; const length = buf.readUInt16BE(offset); const name = new Name(); - const sig: { [key: string]: any; } = {}; + const sig: { [key: string]: unknown; } = {}; offset += 2; sig.typeCovered = types.toString(buf.readUInt16BE(offset)); @@ -45,13 +45,13 @@ export class RRSIG { sig.signersName = name.decode(buf, offset); offset += name.decodeBytes; sig.signature = buf.slice(offset, oldOffset + length + 2); - offset += sig.signature.length; + offset += (sig.signature as Buffer).length; this.decodeBytes = offset - oldOffset; return sig; } - encode(sig, buf, offset) { + encode(sig, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(sig)); diff --git a/src/records/soa.ts b/src/records/soa.ts index ba73e93..1c523c6 100644 --- a/src/records/soa.ts +++ b/src/records/soa.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; /// util @@ -17,11 +17,11 @@ export class SOA { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; - const data: { [key: string]: any; } = {}; + const data: { [key: string]: unknown; } = {}; const name = new Name(); const oldOffset = offset; @@ -45,7 +45,7 @@ export class SOA { return data; } - encode(data, buf, offset) { + encode(data, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(data)); diff --git a/src/records/srv.ts b/src/records/srv.ts index 9484082..8489399 100644 --- a/src/records/srv.ts +++ b/src/records/srv.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; /// util @@ -17,24 +17,24 @@ export class SRV { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; - const data: { [key: string]: any; } = {}; + const data: { [key: string]: unknown; } = {}; const len = buf.readUInt16BE(offset); const name = new Name(); - data.priority = buf.readUInt16BE(offset + 2); - data.weight = buf.readUInt16BE(offset + 4); data.port = buf.readUInt16BE(offset + 6); + data.priority = buf.readUInt16BE(offset + 2); data.target = name.decode(buf, offset + 8); + data.weight = buf.readUInt16BE(offset + 4); this.decodeBytes = len + 2; return data; } - encode(data, buf, offset) { + encode(data, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(data)); diff --git a/src/records/sshfp.ts b/src/records/sshfp.ts index 4ae5186..9952fe2 100644 --- a/src/records/sshfp.ts +++ b/src/records/sshfp.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; @@ -13,12 +13,12 @@ export class SSHFP { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; const oldOffset = offset; - const record: { [key: string]: any; } = {}; + const record: { [key: string]: unknown; } = {}; offset += 2; // Account for the RDLENGTH field record.algorithm = buf[offset]; @@ -35,7 +35,7 @@ export class SSHFP { return record; } - encode(record, buf, offset) { + encode(record, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(record)); diff --git a/src/records/txt.ts b/src/records/txt.ts index 66118d8..afa478f 100644 --- a/src/records/txt.ts +++ b/src/records/txt.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; @@ -13,7 +13,7 @@ export class TXT { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; @@ -40,7 +40,7 @@ export class TXT { return data; } - encode(data, buf, offset) { + encode(data, buf?, offset?) { if (!Array.isArray(data)) data = [data]; diff --git a/src/records/unknown.ts b/src/records/unknown.ts index 7625f07..f8b2ec6 100644 --- a/src/records/unknown.ts +++ b/src/records/unknown.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; @@ -13,7 +13,7 @@ export class UNKNOWN { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?: number) { if (!offset) offset = 0; @@ -24,7 +24,7 @@ export class UNKNOWN { return data; } - encode(data, buf, offset) { + encode(data, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(data)); diff --git a/src/stream.ts b/src/stream.ts index 1a459a9..7cde3d1 100644 --- a/src/stream.ts +++ b/src/stream.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; /// util diff --git a/src/string.ts b/src/string.ts index aa58e01..7cad3dd 100644 --- a/src/string.ts +++ b/src/string.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; @@ -13,7 +13,7 @@ export class String { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset) { + decode(buf, offset?) { if (!offset) offset = 0; @@ -24,7 +24,7 @@ export class String { return s; } - encode(s, buf, offset) { + encode(s, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(s)); diff --git a/src/typebitmap.ts b/src/typebitmap.ts index 7983732..29380c4 100644 --- a/src/typebitmap.ts +++ b/src/typebitmap.ts @@ -3,7 +3,7 @@ /// import -import { Buffer } from "https://deno.land/std@0.166.0/node/internal/buffer.mjs"; +import { Buffer } from "node:buffer"; /// util @@ -17,12 +17,15 @@ export class TypeBitmap { decodeBytes = 0; encodeBytes = 0; - decode(buf, offset, length) { + decode(buf, offset?, length?) { if (!offset) offset = 0; + if (!length) + length = 0; + const oldOffset = offset; - const typelist = []; + const typelist: Array = []; while (offset - oldOffset < length) { const window = buf.readUInt8(offset); @@ -36,7 +39,6 @@ export class TypeBitmap { for (let j = 0; j < 8; j++) { if (b & (1 << (7 - j))) { const typeid = types.toString((window << 8) | (i << 3) | j); - // @ts-ignore | TS2345 [ERROR]: Argument of type "string" is not assignable to parameter of type "never". typelist.push(typeid); } } @@ -49,7 +51,7 @@ export class TypeBitmap { return typelist; } - encode(typelist, buf, offset) { + encode(typelist, buf?, offset?) { if (!buf) buf = Buffer.alloc(this.encodingLength(typelist)); @@ -57,7 +59,7 @@ export class TypeBitmap { offset = 0; const oldOffset = offset; - const typesByWindow: Array<{ [key: string]: any; }> = []; + const typesByWindow: unknown[] = []; let i; for (i = 0; i < typelist.length; i++) { @@ -66,12 +68,12 @@ export class TypeBitmap { if (typesByWindow[typeid >> 8] === undefined) typesByWindow[typeid >> 8] = []; - typesByWindow[typeid >> 8][(typeid >> 3) & 0x1F] |= 1 << (7 - (typeid & 0x7)); + (typesByWindow[typeid >> 8] as number[])[(typeid >> 3) & 0x1F] |= 1 << (7 - (typeid & 0x7)); } for (i = 0; i < typesByWindow.length; i++) { if (typesByWindow[i] !== undefined) { - const windowBuf = Buffer.from(typesByWindow[i]); + const windowBuf = Buffer.from((typesByWindow[i] as number[])); buf.writeUInt8(i, offset); offset += 1; diff --git a/src/utility/ip-codec.ts b/src/utility/ip-codec.ts index db22f97..552d6ba 100644 --- a/src/utility/ip-codec.ts +++ b/src/utility/ip-codec.ts @@ -245,9 +245,7 @@ export function encode(ip, buff, offset?) { } export function familyOf(string) { - return sizeOf(string) === v4.size ? - 1 : - 2; + return sizeOf(string) === v4.size ? 1 : 2; } export function sizeOf(ip) { diff --git a/test.ts b/test.ts new file mode 100644 index 0000000..0159845 --- /dev/null +++ b/test.ts @@ -0,0 +1,727 @@ + + + +/// import + +import { assertEquals, assertStrictEquals, assertThrows } from "https://deno.land/std@0.182.0/testing/asserts.ts"; +import { Buffer } from "node:buffer"; +import * as Packet from "./mod.ts"; + +/// util + +import * as opcodes from "./src/utility/opcodes.ts"; +import * as optioncodes from "./src/utility/optioncodes.ts"; +import * as rcodes from "./src/utility/rcodes.ts"; + + + +/// program + +Deno.test("a", () => { + testEncoder(new Packet.A(), "127.0.0.1"); +}); + +Deno.test("aaaa", () => { + testEncoder(new Packet.AAAA(), "fe80::1"); +}); + +Deno.test("caa", () => { + testEncoder(new Packet.CAA(), { tag: "issue", value: "letsencrypt.org" }); + testEncoder(new Packet.CAA(), { issuerCritical: true, tag: "issue", value: "letsencrypt.org" }); + testEncoder(new Packet.CAA(), { flags: 128, issuerCritical: true, tag: "issue", value: "letsencrypt.org" }); +}); + +Deno.test("cname", () => { + testEncoder(new Packet.CNAME(), "hello.cname.world.examplename"); +}); + +Deno.test("dname", () => { + testEncoder(new Packet.DNAME(), "hello.dname.world.examplename"); +}); + +Deno.test("dnskey", () => { + const packet = new Packet.DNSKEY(); + + testEncoder(packet, { + algorithm: 1, + flags: packet.SECURE_ENTRYPOINT | packet.ZONE_KEY, + key: Buffer.from([0, 1, 2, 3, 4, 5]) + }) +}); + +Deno.test("ds", () => { + testEncoder(new Packet.DS(), { + algorithm: 1, + digest: Buffer.from([0, 1, 2, 3, 4, 5]), + digestType: 1, + keyTag: 1234 + }); +}); + +Deno.test("hinfo", () => { + testEncoder(new Packet.HINFO(), { cpu: "risc", os: "xp" }); +}); + +Deno.test("mx", () => { + testEncoder(new Packet.MX(), { exchange: "mx.hello.world.examplename" }); + testEncoder(new Packet.MX(), { exchange: "mx.hello.world.examplename", preference: 10 }); +}); + +Deno.test("name_decoding", () => { + // The two most significant bits of a valid label header must be either both zero or both one + assertThrows(() => { new Packet.Name().decode(Buffer.from([0x80])) }, "Cannot decode name (bad label)"); + assertThrows(() => { new Packet.Name().decode(Buffer.from([0xb0])) }, "Cannot decode name (bad label)"); + + // Ensure there is enough buffer to read + assertThrows(() => { new Packet.Name().decode(Buffer.from([])) }, "Cannot decode name (buffer overflow)"); + assertThrows(() => { new Packet.Name().decode(Buffer.from([0x01, 0x00])) }, "Cannot decode name (buffer overflow)"); + assertThrows(() => { new Packet.Name().decode(Buffer.from([0x01])) }, "Cannot decode name (buffer overflow)"); + assertThrows(() => { new Packet.Name().decode(Buffer.from([0xc0])) }, "Cannot decode name (buffer overflow)"); + + // Allow only pointers backwards + assertThrows(() => { new Packet.Name().decode(Buffer.from([0xc0, 0x00])) }, "Cannot decode name (bad pointer)"); + assertThrows(() => { new Packet.Name().decode(Buffer.from([0xc0, 0x01])) }, "Cannot decode name (bad pointer)"); + + // A name can be only 253 characters (when connected with dots) + const maxLength = Buffer.alloc(255); + maxLength.fill(Buffer.from([0x01, 0x61]), 0, 254); + assertEquals(new Packet.Name().decode(maxLength), new Array(127).fill("a").join(".")); + + const tooLong = Buffer.alloc(256); + tooLong.fill(Buffer.from([0x01, 0x61])); + assertThrows(() => { new Packet.Name().decode(tooLong) }, "Cannot decode name (name too long)"); + + // Ensure jumps do not reset the total length counter + const tooLongWithJump = Buffer.alloc(403); + tooLongWithJump.fill(Buffer.from([0x01, 0x61]), 0, 200); + tooLongWithJump.fill(Buffer.from([0x01, 0x61]), 201, 401); + tooLongWithJump.set([0xc0, 0x00], 401); + assertThrows(() => { new Packet.Name().decode(tooLongWithJump, 201) }, "Cannot decode name (name too long)"); + + // Ensure a jump to a null byte does not add extra dots + assertEquals(new Packet.Name().decode(Buffer.from([0x00, 0x01, 0x61, 0xc0, 0x00]), 1), "a"); + + // Ensure deeply nested pointers do not cause "Maximum call stack size exceeded" errors + const buf = Buffer.alloc(16386); + + for (let i = 0; i < 16384; i += 2) { + buf.writeUInt16BE(0xc000 | i, i + 2); + } + + assertEquals(new Packet.Name().decode(buf, 16384), "."); +}); + +Deno.test("name_encoding", () => { + const buf = Buffer.allocUnsafe(255); + const packet = new Packet.Name(); + let data = "com.foo.examplename"; + let offset = 0; + + packet.encode(data, buf, offset); + assertStrictEquals(packet.encodeBytes, 21, "name encoding length matches"); + let dd = packet.decode(buf, offset); + assertStrictEquals(data, dd, "encode/decode matches"); + offset += packet.encodeBytes; + + data = "examplename"; + packet.encode(data, buf, offset); + assertStrictEquals(packet.encodeBytes, 13, "name encoding length matches"); + dd = packet.decode(buf, offset); + assertStrictEquals(data, dd, "encode/decode matches"); + offset += packet.encodeBytes; + + data = "foo.examplename."; + packet.encode(data, buf, offset); + assertStrictEquals(packet.encodeBytes, 17, "name encoding length matches"); + dd = packet.decode(buf, offset); + assertStrictEquals(data.slice(0, -1), dd, "encode/decode matches"); + offset += packet.encodeBytes; + + data = "."; + packet.encode(data, buf, offset); + assertStrictEquals(packet.encodeBytes, 1, "name encoding length matches"); + dd = packet.decode(buf, offset); + assertStrictEquals(data, dd, "encode/decode matches"); +}); + +Deno.test("naptr", () => { + testEncoder(new Packet.NAPTR(), { + flags: "S", + order: 1, + preference: 1, + regexp: "!^.*$!sip:customer-service@xuexample.com!", + replacement: "_sip._udp.xuexample.com", + services: "SIP+D2T" + }); +}); + +Deno.test("ns", () => { + testEncoder(new Packet.NS(), "ns.world.examplename"); +}); + +Deno.test("nsec", () => { + const packet = new Packet.NSEC(); + + // `rrtypes` has an issue with being alphabetical... + // testEncoder(packet, { + // nextDomain: "foo.examplename", + // rrtypes: ["A", "CAA", "DLV", "DNSKEY"] + // }); + + testEncoder(packet, { + nextDomain: "foo.examplename", + rrtypes: ["TXT"] // 16 + }); + + testEncoder(packet, { + nextDomain: "foo.examplename", + rrtypes: ["TKEY"] // 249 + }); + + // `rrtypes` has an issue with being alphabetical... + // testEncoder(packet, { + // nextDomain: "foo.examplename", + // rrtypes: ["NSEC", "RRSIG"] + // }); + + // `rrtypes` has an issue with being alphabetical... + // testEncoder(packet, { + // nextDomain: "foo.examplename", + // rrtypes: ["RRSIG", "TXT"] + // }); + + // `rrtypes` has an issue with being alphabetical... + // testEncoder(packet, { + // nextDomain: "foo.examplename", + // rrtypes: ["NSEC", "TXT"] + // }); + + // Test with sample NSEC from below `assertEquals` + // ex: packet.encode().toString("hex") + const sampleNSEC = Buffer.from("003704686f73740b6578616d706c656e616d6500" + + "0006400100000003041b000000000000000000000000000000000000000000000" + + "000000020", "hex" + ); + + const decoded = packet.decode(sampleNSEC); + + assertStrictEquals(compare(decoded, { + nextDomain: "host.examplename", + rrtypes: ["A", "MX", "RRSIG", "NSEC", "UNKNOWN_1234"] + }), true); + + const reencoded = packet.encode(decoded); + + assertEquals(sampleNSEC.length, reencoded.length); + assertEquals(sampleNSEC, reencoded); +}); + +Deno.test("null", () => { + testEncoder(new Packet.NULL(), Buffer.from([0, 1, 2, 3, 4, 5])); +}); + +Deno.test("optioncodes", () => { + const opts = [ + [0, "OPTION_0"], + [1, "LLQ"], + [2, "UL"], + [3, "NSID"], + [4, "OPTION_4"], + [5, "DAU"], + [6, "DHU"], + [7, "N3U"], + [8, "CLIENT_SUBNET"], + [9, "EXPIRE"], + [10, "COOKIE"], + [11, "TCP_KEEPALIVE"], + [12, "PADDING"], + [13, "CHAIN"], + [14, "KEY_TAG"], + [26946, "DEVICEID"], + [65535, "OPTION_65535"], + [64000, "OPTION_64000"], + [65002, "OPTION_65002"], + [-1, null] + ]; + + for (const [code, str] of opts) { + const s = optioncodes.toString(code); + + assertStrictEquals(compare(s, str), true, `${code} => ${str}`); + assertStrictEquals(compare(optioncodes.toCode(s), code), true, `${str} => ${code}`); + } + + assertStrictEquals(compare(optioncodes.toCode("INVALIDINVALID"), -1), true); +}); + +Deno.test("ptr", () => { + testEncoder(new Packet.PTR(), "hello.world.examplename"); +}); + +Deno.test("query", () => { + testEncoder(new Packet.DEFAULT(), { + questions: [ + { + name: "hello.a.examplename", + type: "A" + }, { + name: "hello.srv.examplename", + type: "SRV" + } + ], + type: "query" + }); + + testEncoder(new Packet.DEFAULT(), { + questions: [ + { + class: "CH", + name: "hello.a.examplename", + type: "A" + }, { + name: "hello.srv.examplename", + type: "SRV" + } + ], + type: "query" + }); + + testEncoder(new Packet.DEFAULT(), { + id: 42, + questions: [ + { + class: "IN", + name: "hello.a.examplename", + type: "A" + }, { + name: "hello.srv.examplename", + type: "SRV" + } + ], + type: "query" + }); +}); + +Deno.test("rcode", () => { + const errors = ["NOERROR", "FORMERR", "SERVFAIL", "NXDOMAIN", "NOTIMP", "REFUSED", "YXDOMAIN", "YXRRSET", "NXRRSET", "NOTAUTH", "NOTZONE", "RCODE_11", "RCODE_12", "RCODE_13", "RCODE_14", "RCODE_15"]; + + const ops = ["QUERY", "IQUERY", "STATUS", "OPCODE_3", "NOTIFY", "UPDATE", "OPCODE_6", "OPCODE_7", "OPCODE_8", "OPCODE_9", "OPCODE_10", "OPCODE_11", "OPCODE_12", "OPCODE_13", "OPCODE_14", "OPCODE_15"]; + + const packet = new Packet.DEFAULT(); + + for (const i in errors) { + const code = rcodes.toRcode(errors[i]); + assertStrictEquals(errors[i], rcodes.toString(code), `rcode conversion from/to string matches: ${rcodes.toString(code)}`); + } + + for (const i in ops) { + const ocode = opcodes.toOpcode(ops[i]); + assertStrictEquals(ops[i], opcodes.toString(ocode), `opcode conversion from/to string matches: ${opcodes.toString(ocode)}`); + } + + const buf = packet.encode({ + answers: [{ + data: "127.0.0.1", + name: "hello.examplename", + type: "A" + }], + flags: 0x8480, + id: 45632, + type: "response" + }); + + const val = packet.decode(buf); + + assertStrictEquals(val.type, "response", "decode type"); + assertStrictEquals(val.opcode, "QUERY", "decode opcode"); + assertStrictEquals(val.flag_qr, true, "decode flag_qr"); + assertStrictEquals(val.flag_aa, true, "decode flag_aa"); + assertStrictEquals(val.flag_tc, false, "decode flag_tc"); + assertStrictEquals(val.flag_rd, false, "decode flag_rd"); + assertStrictEquals(val.flag_ra, true, "decode flag_ra"); + assertStrictEquals(val.flag_z, false, "decode flag_z"); + assertStrictEquals(val.flag_ad, false, "decode flag_ad"); + assertStrictEquals(val.flag_cd, false, "decode flag_cd"); + assertStrictEquals(val.rcode, "NOERROR", "decode rcode"); +}); + +Deno.test("response", () => { + testEncoder(new Packet.DEFAULT(), { + answers: [{ + class: "IN", + data: "127.0.0.1", + flush: true, + name: "hello.a.examplename", + type: "A" + }], + type: "response" + }); + + testEncoder(new Packet.DEFAULT(), { + answers: [ + { + class: "IN", + data: "127.0.0.1", + name: "hello.a.examplename", + type: "A" + }, { + class: "IN", + data: { + port: 9090, + target: "hello.target.examplename" + }, + name: "hello.srv.examplename", + type: "SRV" + }, { + class: "IN", + data: "hello.other.domain.examplename", + name: "hello.cname.examplename", + type: "CNAME" + } + ], + flags: Packet.TRUNCATED_RESPONSE, + type: "response" + }); + + testEncoder(new Packet.DEFAULT(), { + additionals: [ + { + data: "fe80::1", + name: "hello.a.examplename", + type: "AAAA" + }, { + data: "hello.other.ptr.examplename", + name: "hello.ptr.examplename", + type: "PTR" + }, { + data: { + port: 9090, + target: "hello.target.examplename" + }, + name: "hello.srv.examplename", + ttl: 42, + type: "SRV" + } + ], + answers: [ + { + data: Buffer.from([1, 2, 3, 4, 5]), + name: "hello.null.examplename", + type: "NULL", + } + ], + flags: 0, + id: 100, + type: "response" + }); + + testEncoder(new Packet.DEFAULT(), { + answers: [{ + data: "", + name: "emptytxt.examplename", + type: "TXT" + }], + type: "response" + }); +}); + +Deno.test("rrp", () => { + const packet = new Packet.RP(); + + testEncoder(packet, { + mbox: "foo.bar.examplename", + txt: "baz.bar.examplename" + }); + + testEncoder(packet, { mbox: "foo.bar.examplename" }); + testEncoder(packet, { txt: "baz.bar.examplename" }); + testEncoder(packet, {}); +}); + +Deno.test("rrsig", () => { + const packet = new Packet.RRSIG(); + + const testRRSIG = { + algorithm: 1, + expiration: 1234, + inception: 1233, + keyTag: 2345, + labels: 2, + originalTTL: 3600, + signature: Buffer.from([0, 1, 2, 3, 4, 5]), + signersName: "foo.examplename", + typeCovered: "A" + }; + + testEncoder(packet, testRRSIG); + + // Check the signature length is correct with extra junk at the end + const buf = Buffer.allocUnsafe(packet.encodingLength(testRRSIG) + 4); + packet.encode(testRRSIG, buf); + + const val2 = packet.decode(buf); + assertStrictEquals(compare(testRRSIG, val2), true); +}); + +Deno.test("soa", () => { + testEncoder(new Packet.SOA(), { + expire: 604800, + minimum: 3600, + mname: "hello.world.examplename", + refresh: 14400, + retry: 3600, + rname: "root.hello.world.examplename", + serial: 2018010400 + }); +}); + +Deno.test("sshfp", () => { + testEncoder(new Packet.SSHFP(), { + algorithm: 1, + fingerprint: "A108C9F834354D5B37AF988141C9294822F5BC00", + hash: 1 + }); +}); + +Deno.test("srv", () => { + testEncoder(new Packet.SRV(), { port: 9999, target: "hello.world.examplename" }); + testEncoder(new Packet.SRV(), { port: 9999, priority: 42, target: "hello.world.examplename", weight: 10 }); +}); + +Deno.test("stream", () => { + const packet = new Packet.Stream(); + + const val1 = { + answers: [{ + data: "198.51.100.1", + name: "test2.example.net", + type: "A" + }], + flags: 0x8480, + id: 45632, + type: "query" + }; + + const buf = packet.encode(val1); + const val2 = packet.decode(buf); + + assertStrictEquals(buf.length, packet.encodeBytes, "streamEncode.bytes was set correctly"); + assertStrictEquals(compare(val2?.type, val1.type), true, "streamDecoded type match"); + assertStrictEquals(compare(val2?.id, val1.id), true, "streamDecoded id match"); + assertStrictEquals(parseInt(String(val2?.flags)), parseInt(String(val1.flags & 0x7FFF)), "streamDecoded flags match"); + + const answer1 = val1.answers[0]; + const answer2 = val2?.answers[0]; + + assertStrictEquals(compare(answer1.type, answer2.type), true, "streamDecoded RR type match"); + assertStrictEquals(compare(answer1.name, answer2.name), true, "streamDecoded RR name match"); + assertStrictEquals(compare(answer1.data, answer2.data), true, "streamDecoded RR rdata match"); +}); + +Deno.test("txt", () => { + testEncoder(new Packet.TXT(), []); + testEncoder(new Packet.TXT(), ["hello world"]); + testEncoder(new Packet.TXT(), ["hello", "world"]); + testEncoder(new Packet.TXT(), [Buffer.from([0, 1, 2, 3, 4, 5])]); + testEncoder(new Packet.TXT(), ["a", "b", Buffer.from([0, 1, 2, 3, 4, 5])]); + testEncoder(new Packet.TXT(), ["", Buffer.allocUnsafe(0)]); +}); + +Deno.test("txt-invalid-data", () => { + assertThrows(() => { new Packet.TXT().encode(null) }, "null"); + assertThrows(() => { new Packet.TXT().encode(undefined) }, "undefined"); + assertThrows(() => { new Packet.TXT().encode(10) }, "number"); +}); + +Deno.test("txt-scalar-buffer", () => { + const data = Buffer.from([0, 1, 2, 3, 4, 5]); + const buf = new Packet.TXT().encode(data); + const val = new Packet.TXT().decode(buf); + + assertEquals(val.length, 1, "array length"); + assertEquals(val[0], data, "data"); +}); + +Deno.test("txt-scalar-string", () => { + const buf = new Packet.TXT().encode("hi"); + const val = new Packet.TXT().decode(buf); + + assertStrictEquals(val.length, 1, "array length"); + assertStrictEquals(String(val[0]), "hi", "data"); +}); + +Deno.test("unknown", () => { + testEncoder(new Packet.UNKNOWN(), Buffer.from("hello world")); +}); + +Deno.test("unpack", () => { + const buf = Buffer.from([ + 0x00, 0x79, + 0xde, 0xad, 0x85, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x02, 0x00, 0x02, 0x02, 0x6f, 0x6a, 0x05, + 0x62, 0x61, 0x6e, 0x67, 0x6a, 0x03, 0x63, 0x6f, + 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10, + 0x00, 0x04, 0x81, 0xfa, 0x0b, 0xaa, 0xc0, 0x0f, + 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10, + 0x00, 0x05, 0x02, 0x63, 0x6a, 0xc0, 0x0f, 0xc0, + 0x0f, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x0e, + 0x10, 0x00, 0x02, 0xc0, 0x0c, 0xc0, 0x3a, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10, 0x00, + 0x04, 0x45, 0x4d, 0x9b, 0x9c, 0xc0, 0x0c, 0x00, + 0x1c, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10, 0x00, + 0x10, 0x20, 0x01, 0x04, 0x18, 0x00, 0x00, 0x50, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf9 + ]); + + const val = new Packet.Stream().decode(buf); + const answer = val!.answers[0]; + const authority = val!.authorities[1]; + + assertEquals(val!.rcode, "NOERROR", "decode rcode"); + assertStrictEquals(compare(answer.type, "A"), true, "streamDecoded RR type match"); + assertStrictEquals(compare(answer.name, "oj.bangj.com"), true, "streamDecoded RR name match"); + assertStrictEquals(compare(answer.data, "129.250.11.170"), true, "streamDecoded RR rdata match"); + assertStrictEquals(compare(authority.type, "NS"), true, "streamDecoded RR type match"); + assertStrictEquals(compare(authority.name, "bangj.com"), true, "streamDecoded RR name match"); + assertStrictEquals(compare(authority.data, "oj.bangj.com"), true, "streamDecoded RR rdata match"); +}); + +/// failing tests + +/* +Deno.test("nsec3", () => { + // RangeError: The value of "value" is out of range. It must be >= 0 and <= 65535. Received -2 + testEncoder(new Packet.NSEC3(), { + algorithm: 1, + flags: 0, + iterations: 257, + nextDomain: Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 1]), + rrtypes: ["A", "CAA", "DLV", "DNSKEY"], + salt: Buffer.from([42, 42, 42]) + }); +}); +*/ + +/* +Deno.test("opt", () => { + const packet = new Packet.DEFAULT(); + + const val = { + additionals: [{ + name: ".", + type: "OPT", + udpPayloadSize: 1024 + }], + questions: [{ + name: "hello.a.examplename", + type: "A" + }], + type: "query" + }; + + testEncoder(packet, val); + + let buf = packet.encode(val); + let val2 = packet.decode(buf); + const additional1 = val.additionals[0]; + let additional2 = val2.additionals[0]; + + assertEquals(compare(additional1.name, additional2.name), true, "name matches"); + assertEquals(compare(additional1.udpPayloadSize, additional2.udpPayloadSize), true, "udp payload size matches"); + assertEquals(compare(0, additional2.flags), true, "flags match"); + + additional1.extendedRcode = 0x80; + additional1.flags = Packet.DNSSEC_OK; + additional1.options = [ + { + code: "CLIENT_SUBNET", // edns-client-subnet, see RFC 7871 + ip: "fe80::", + sourcePrefixLength: 64 + }, { + code: 8, // still ECS + ip: "5.6.0.0", + scopePrefixLength: 16, + sourcePrefixLength: 16 + }, { + code: "padding", + length: 31 + }, { + code: "TCP_KEEPALIVE" + }, { + code: "tcp_keepalive", + timeout: 150 + }, { + code: "KEY_TAG", + tags: [1, 82, 987] + } + ]; + + buf = packet.encode(val); + val2 = packet.decode(buf); + additional2 = val2.additionals[0]; + + assertEquals(compare(1 << 15, additional2.flags), true, "DO bit set in flags"); + assertEquals(compare(true, additional2.flag_do), true, "DO bit set"); + assertEquals(compare(additional1.extendedRcode, additional2.extendedRcode), true, "extended rcode matches"); + assertEquals(compare(8, additional2.options[0].code), true); + assertEquals(compare("fe80::", additional2.options[0].ip), true); + assertEquals(compare(64, additional2.options[0].sourcePrefixLength), true); + assertEquals(compare("5.6.0.0", additional2.options[1].ip), true); + assertEquals(compare(16, additional2.options[1].sourcePrefixLength), true); + assertEquals(compare(16, additional2.options[1].scopePrefixLength), true); + assertEquals(compare(additional1.options[2].length, additional2.options[2].data.length), true); + assertEquals(compare(additional1.options[3].timeout, undefined), true); + assertEquals(compare(additional1.options[4].timeout, additional2.options[4].timeout), true); + assertEquals(compare(additional1.options[5].tags, additional2.options[5].tags), true); +}); +*/ + + + +/// helper + +function compare(a, b) { + if (Buffer.isBuffer(a)) + return a.toString("hex") === b.toString("hex"); + + if (typeof a === "object" && a && b) { + const keys = Object.keys(a); + + for (let i = 0; i < keys.length; i++) { + if (!compare(a[keys[i]], b[keys[i]])) + return false; + } + } else if (Array.isArray(b) && !Array.isArray(a)) { + return a.toString() === b[0].toString(); + } else { + return a === b; + } + + return true; +} + +function testEncoder(record, val1) { + const buf1 = record.encode(val1); + const val2 = record.decode(buf1); + + assertStrictEquals(buf1.length, record.encodeBytes, "encode.bytes was set correctly"); + assertStrictEquals(buf1.length, record.encodingLength(val1), "encoding length matches"); + assertStrictEquals(compare(val1, val2), true, "decoded object match"); + + const buf2 = record.encode(val2); + const val3 = record.decode(buf2); + + assertStrictEquals(buf2.length, record.encodeBytes, "encodeBytes was set correctly on re-encode"); + assertStrictEquals(buf2.length, record.encodingLength(val1), "encoding length matches on re-encode"); + assertStrictEquals(compare(val1, val3), true, "decoded object match on re-encode"); + assertStrictEquals(compare(val2, val3), true, "re-encoded decoded object match on re-encode"); + + const bigger = Buffer.allocUnsafe(buf2.length + 10); + const buf3 = record.encode(val1, bigger, 10); + const val4 = record.decode(buf3, 10); + + assertStrictEquals(buf3, bigger, "echoes buffer on external buffer"); + assertStrictEquals(record.encodeBytes, buf1.length, "encodeBytes is the same on external buffer"); + assertStrictEquals(compare(val1, val4), true, "decoded object match on external buffer"); +}