@nictool/dns-resource-record 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,10 +4,37 @@ Notable changes to this project are documented in this file.
4
4
 
5
5
  #### Unreleased
6
6
 
7
- ### [1.4.0] - 2026-03-21
7
+ ### [1.6.0] - 2026-03-26
8
8
 
9
9
  #### Changed
10
10
 
11
+ - feat(getCanonical): added to RRs where missing
12
+ - feat(toWire): added wire format export
13
+ - feat: add getTags() to all RR types
14
+ - values: common, obsolete, security, dnssec
15
+ - doc(README): categorize RR types.
16
+ - added RFCs to: AAAA 5952, NAPTR 4848, SIG 3755, TLSA 7671
17
+ - TXT: 4408, 6376, 7208
18
+
19
+ ### [1.5.0] - 2026-03-22
20
+
21
+ - feat(RRSIG, SIG, TSIG): added toBind, toTinydns, fromTinydns
22
+ - feat(TSIG): added missing setters
23
+ - feat(fromTinydns): added to APL, DHCID, HIP, KX, RRSIG, RP, WKS
24
+ - feat(CERT): validate mnemonic values
25
+ - fix: hardened polynomial regexes #56
26
+ - fix(NAPTR): replace polynomial regexp
27
+ - test: coverage is at 98% 🎉
28
+ - test(NSEC3PARAM): added test cases, better binary parser, better salt
29
+ - test(AAAA, APL, CERT, KEY, NAPTR, NSEC3, NSEC3PARAM, RRSIG, SIG, SSHFP, TSIG, URL): added test coverage
30
+ - doc(README): updated RR table completion status
31
+ - style(es6): use ?. and ?? operators (nullish coalescing) #57
32
+ - style(es6): use argument expansion / destructured parameters
33
+ - style(es6): substring -> slice #55
34
+ - style(es6): use more trim() #55
35
+
36
+ ### [1.4.0] - 2026-03-21
37
+
11
38
  - feat(toBind): added to nsec3param, rrsig
12
39
  - feat(toTinydns): added to key, nsec3param, nxt, openpgpkey
13
40
  - feat(fromTinydns): added to cert, https, key, nsec, nsec3param, nxt, openpgpkey, smimea, svcb
@@ -350,3 +377,5 @@ Notable changes to this project are documented in this file.
350
377
  [1.3.0]: https://github.com/NicTool/dns-resource-record/releases/tag/v1.3.0
351
378
  [1.3.1]: https://github.com/NicTool/dns-resource-record/releases/tag/v1.3.1
352
379
  [1.4.0]: https://github.com/NicTool/dns-resource-record/releases/tag/v1.4.0
380
+ [1.5.0]: https://github.com/NicTool/dns-resource-record/releases/tag/v1.5.0
381
+ [1.6.0]: https://github.com/NicTool/dns-resource-record/releases/tag/v1.6.0
package/README.md CHANGED
@@ -19,6 +19,7 @@ This module is used to:
19
19
  | **[Tinydns](https://cr.yp.to/djbdns/tinydns-data.html)** | :white_check_mark: | :white_check_mark: |
20
20
  | **MaraDNS** | | :white_check_mark: |
21
21
  | **JS** | :white_check_mark: | :white_check_mark: |
22
+ | **PowerDNS** | | |
22
23
 
23
24
  This package intends to import and export RFC compliant DNS resource records. Please [raise an issue](https://github.com/NicTool/dns-resource-record/issues) if a valid resource record fails to pass or an invalid resource record passes.
24
25
 
@@ -185,48 +186,54 @@ This module intends to support all current (ie, not officially deprecated) DNS R
185
186
 
186
187
  PRs are welcome, especially PRs with tests.
187
188
 
188
- | **RR** | **toBind** | **toTinydns** | **fromBind** | **fromTinydns** |
189
- | :------------: | :----------------: | :----------------: | :----------------: | :----------------: |
190
- | **A** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
191
- | **AAAA** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
192
- | **APL** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
193
- | **CAA** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
194
- | **CERT** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
195
- | **CNAME** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
196
- | **DHCID** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
197
- | **DNAME** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
198
- | **DNSKEY** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
199
- | **DS** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
200
- | **HINFO** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
201
- | **HIP** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
202
- | **HTTPS** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
203
- | **IPSECKEY** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
204
- | **KEY** | | | | |
205
- | **KX** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
206
- | **LOC** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
207
- | **MX** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
208
- | **NAPTR** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
209
- | **NS** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
210
- | **NSEC** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
211
- | **NSEC3** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
212
- | **NSEC3PARAM** | | | | |
213
- | **NXT** | | | | |
214
- | **OPENPGPKEY** | | | | |
215
- | **PTR** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
216
- | **RP** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
217
- | **RRSIG** | | | | |
218
- | **SIG** | | | | |
219
- | **SMIMEA** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
220
- | **SOA** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
221
- | **SPF** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
222
- | **SRV** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
223
- | **SSHFP** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
224
- | **SVCB** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
225
- | **TLSA** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
226
- | **TSIG** | | | | |
227
- | **TXT** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
228
- | **URI** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
229
- | **WKS** | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
189
+ | **RR** | **BIND / RFC 1035** | **Tinydns** |
190
+ | :------------: | :-----------------: | :----------------: |
191
+ | _Common_ |
192
+ | **A** | :white_check_mark: | :white_check_mark: |
193
+ | **AAAA** | :white_check_mark: | :white_check_mark: |
194
+ | **CNAME** | :white_check_mark: | :white_check_mark: |
195
+ | **HTTPS** | :white_check_mark: | :white_check_mark: |
196
+ | **MX** | :white_check_mark: | :white_check_mark: |
197
+ | **NS** | :white_check_mark: | :white_check_mark: |
198
+ | **PTR** | :white_check_mark: | :white_check_mark: |
199
+ | **SRV** | :white_check_mark: | :white_check_mark: |
200
+ | **TXT** | :white_check_mark: | :white_check_mark: |
201
+ | _Less Common_ |
202
+ | **APL** | :white_check_mark: | :white_check_mark: |
203
+ | **CERT** | :white_check_mark: | :white_check_mark: |
204
+ | **DHCID** | :white_check_mark: | :white_check_mark: |
205
+ | **DNAME** | :white_check_mark: | :white_check_mark: |
206
+ | **HIP** | :white_check_mark: | :white_check_mark: |
207
+ | **KEY** | :white_check_mark: | :white_check_mark: |
208
+ | **KX** | :white_check_mark: | :white_check_mark: |
209
+ | **LOC** | :white_check_mark: | :white_check_mark: |
210
+ | **NAPTR** | :white_check_mark: | :white_check_mark: |
211
+ | **SOA** | :white_check_mark: | :white_check_mark: |
212
+ | **SVCB** | :white_check_mark: | :white_check_mark: |
213
+ | **TSIG** | :white_check_mark: | :white_check_mark: |
214
+ | **URI** | :white_check_mark: | :white_check_mark: |
215
+ | _Security_ |
216
+ | **CAA** | :white_check_mark: | :white_check_mark: |
217
+ | **IPSECKEY** | :white_check_mark: | :white_check_mark: |
218
+ | **OPENPGPKEY** | :white_check_mark: | :white_check_mark: |
219
+ | **SMIMEA** | :white_check_mark: | :white_check_mark: |
220
+ | **SSHFP** | :white_check_mark: | :white_check_mark: |
221
+ | **TLSA** | :white_check_mark: | :white_check_mark: |
222
+ | _DNSSEC_ |
223
+ | **DNSKEY** | :white_check_mark: | :white_check_mark: |
224
+ | **DS** | :white_check_mark: | :white_check_mark: |
225
+ | **NSEC** | :white_check_mark: | :white_check_mark: |
226
+ | **NSEC3** | :white_check_mark: | :white_check_mark: |
227
+ | **NSEC3PARAM** | :white_check_mark: | :white_check_mark: |
228
+ | **RRSIG** | :white_check_mark: | :white_check_mark: |
229
+ | _Deprecated_ |
230
+ | **NXT** | :white_check_mark: | :white_check_mark: |
231
+ | **SPF** | :white_check_mark: | :white_check_mark: |
232
+ | _Obsolete_ |
233
+ | **HINFO** | :white_check_mark: | :white_check_mark: |
234
+ | **RP** | :white_check_mark: | :white_check_mark: |
235
+ | **SIG** | :white_check_mark: | :white_check_mark: |
236
+ | **WKS** | :white_check_mark: | :white_check_mark: |
230
237
 
231
238
  ## TIPS
232
239
 
package/lib/readme.js ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ // Regenerates the Supported Records table in README.md using getTags() from each RR class.
3
+
4
+ import { readFileSync, writeFileSync } from 'fs'
5
+ import { fileURLToPath } from 'url'
6
+ import { dirname, join } from 'path'
7
+
8
+ import * as RR from '../index.js'
9
+
10
+ const __dirname = dirname(fileURLToPath(import.meta.url))
11
+
12
+ const tagToGroup = {
13
+ common: 'Common',
14
+ security: 'Security',
15
+ dnssec: 'DNSSEC',
16
+ deprecated: 'Deprecated',
17
+ obsolete: 'Obsolete',
18
+ }
19
+
20
+ const groupOrder = ['Common', 'Less Common', 'Security', 'DNSSEC', 'Deprecated', 'Obsolete']
21
+
22
+ function getGroup(instance) {
23
+ const tags = instance.getTags()
24
+ for (const [tag, group] of Object.entries(tagToGroup)) {
25
+ if (tags.includes(tag)) return group
26
+ }
27
+ return 'Less Common'
28
+ }
29
+
30
+ const groups = {}
31
+ for (const name of groupOrder) groups[name] = []
32
+
33
+ for (const [name, cls] of Object.entries(RR)) {
34
+ if (name === 'typeMap' || name === 'default') continue
35
+ if (typeof cls !== 'function') continue
36
+ const instance = new cls(null)
37
+ const group = getGroup(instance)
38
+ groups[group].push(name)
39
+ }
40
+
41
+ const check = ':white_check_mark:'
42
+ const rows = []
43
+ for (const groupName of groupOrder) {
44
+ const names = groups[groupName]
45
+ if (!names.length) continue
46
+ rows.push(`| *${groupName}* |`)
47
+ for (const name of names.sort()) {
48
+ const cls = RR[name]
49
+ const hasToBind = typeof cls.prototype.toBind === 'function'
50
+ const hasFromBind = typeof cls.prototype.fromBind === 'function'
51
+ const hasToTinydns = typeof cls.prototype.toTinydns === 'function'
52
+ const hasFromTinydns = typeof cls.prototype.fromTinydns === 'function'
53
+
54
+ const bind = hasToBind && hasFromBind ? check : ''
55
+ const tinydns = hasToTinydns && hasFromTinydns ? check : ''
56
+
57
+ rows.push(`| **${name}** | ${bind} | ${tinydns} |`)
58
+ }
59
+ }
60
+
61
+ const table = [
62
+ `| **RR** | **BIND / RFC 1035** | **Tinydns** |`,
63
+ `| :------------: | :-------------------: | :-----------------: |`,
64
+ ...rows,
65
+ ].join('\n')
66
+
67
+ const readmePath = join(__dirname, '..', 'README.md')
68
+ const readme = readFileSync(readmePath, 'utf-8')
69
+
70
+ // Replace from the table header line up to (but not including) the ## TIPS section
71
+ const tableHeaderMarker = '\n| **RR** |'
72
+ const tipsSectionMarker = '\n\n## TIPS'
73
+
74
+ const tableStart = readme.indexOf(tableHeaderMarker)
75
+ const tipsStart = readme.indexOf(tipsSectionMarker)
76
+
77
+ if (tableStart === -1 || tipsStart === -1) {
78
+ console.error('Could not locate table boundaries in README.md')
79
+ process.exit(1)
80
+ }
81
+
82
+ const newReadme = readme.slice(0, tableStart + 1) + table + '\n' + readme.slice(tipsStart)
83
+ writeFileSync(readmePath, newReadme)
84
+ console.log('README.md updated successfully')
package/lib/tinydns.js CHANGED
@@ -33,7 +33,7 @@ export function unescapeOctal(str) {
33
33
 
34
34
  export function octalToChar(str) {
35
35
  // relace instances of \NNN with ASCII
36
- return str.replace(octalRe, (o) => String.fromCharCode(parseInt(o.substring(1), 8)))
36
+ return str.replace(octalRe, (o) => String.fromCharCode(parseInt(o.slice(1), 8)))
37
37
  }
38
38
 
39
39
  export function octalToHex(str) {
@@ -41,29 +41,29 @@ export function octalToHex(str) {
41
41
  return str.replace(octalRe, (o) => {
42
42
  // parseInt(n, 8) -> from octal to decimal
43
43
  // .toString(16) -> decimal to hex
44
- return parseInt(o.substring(1), 8).toString(16).padStart(2, 0)
44
+ return parseInt(o.slice(1), 8).toString(16).padStart(2, 0)
45
45
  })
46
46
  }
47
47
 
48
48
  export function octalToUInt8(str) {
49
49
  const b = Buffer.alloc(1)
50
- b.writeUInt8(parseInt(str.substring(1, 4), 8), 0)
50
+ b.writeUInt8(parseInt(str.slice(1, 4), 8), 0)
51
51
  return b.readUInt8()
52
52
  }
53
53
 
54
54
  export function octalToUInt16(str) {
55
55
  const b = Buffer.alloc(2)
56
- b.writeUInt8(parseInt(str.substring(1, 4), 8), 0)
57
- b.writeUInt8(parseInt(str.substring(5, 8), 8), 1)
56
+ b.writeUInt8(parseInt(str.slice(1, 4), 8), 0)
57
+ b.writeUInt8(parseInt(str.slice(5, 8), 8), 1)
58
58
  return b.readUInt16BE()
59
59
  }
60
60
 
61
61
  export function octalToUInt32(str) {
62
62
  const b = Buffer.alloc(4)
63
- b.writeUInt8(parseInt(str.substring(1, 4), 8), 0)
64
- b.writeUInt8(parseInt(str.substring(5, 8), 8), 1)
65
- b.writeUInt8(parseInt(str.substring(9, 12), 8), 2)
66
- b.writeUInt8(parseInt(str.substring(13, 16), 8), 3)
63
+ b.writeUInt8(parseInt(str.slice(1, 4), 8), 0)
64
+ b.writeUInt8(parseInt(str.slice(5, 8), 8), 1)
65
+ b.writeUInt8(parseInt(str.slice(9, 12), 8), 2)
66
+ b.writeUInt8(parseInt(str.slice(13, 16), 8), 3)
67
67
  return b.readUInt32BE()
68
68
  }
69
69
 
@@ -99,7 +99,7 @@ export function packDomainName(fqdn) {
99
99
  // RFC 1035, 3.3 Standard RRs
100
100
  // The standard wire format for DNS names. (1 octet length + octets)
101
101
  let packed = ''
102
- fqdn.split('.').map((label) => {
102
+ fqdn.split('.').forEach((label) => {
103
103
  if (label === undefined || !label.length) return
104
104
 
105
105
  const len = Buffer.alloc(1)
@@ -196,6 +196,16 @@ export function octalToIPv4(str) {
196
196
  return [24, 16, 8, 0].map((n) => (asInt >> n) & 0xff).join('.')
197
197
  }
198
198
 
199
+ export function ipv6toOctal(ip) {
200
+ return packHex(ip.replace(/:/g, ''))
201
+ }
202
+
203
+ export function octalToIPv6(str) {
204
+ return octalToHex(str)
205
+ .match(/(.{4})/g)
206
+ .join(':')
207
+ }
208
+
199
209
  export function base64toOctal(str) {
200
210
  const bytes = Buffer.from(str, 'base64')
201
211
  let escaped = ''
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nictool/dns-resource-record",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "DNS Resource Records",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -11,8 +11,9 @@
11
11
  ],
12
12
  "type": "module",
13
13
  "scripts": {
14
+ "build": "node lib/readme.js",
14
15
  "format:check": "npm run prettier; npm run lint",
15
- "format": "npm run prettier -- --write && npm run lint --fix",
16
+ "format": "npm run prettier -- --write && npm run lint:fix",
16
17
  "lint": "npx eslint **/*.js",
17
18
  "lint:fix": "npm run lint -- --fix",
18
19
  "prettier": "npx prettier --ignore-path .gitignore --check .",
package/rr/a.js CHANGED
@@ -16,6 +16,10 @@ export default class A extends RR {
16
16
  return 'Address'
17
17
  }
18
18
 
19
+ getTags() {
20
+ return ['common']
21
+ }
22
+
19
23
  getRdataFields(arg) {
20
24
  return ['address']
21
25
  }
@@ -39,9 +43,9 @@ export default class A extends RR {
39
43
  }
40
44
 
41
45
  /****** IMPORTERS *******/
42
- fromTinydns(opts) {
46
+ fromTinydns({ tinyline }) {
43
47
  // +fqdn:ip:ttl:timestamp:lo
44
- const [owner, ip, ttl, ts, loc] = opts.tinyline.substring(1).split(':')
48
+ const [owner, ip, ttl, ts, loc] = tinyline.slice(1).split(':')
45
49
 
46
50
  return new A({
47
51
  owner: this.fullyQualify(owner),
@@ -49,13 +53,13 @@ export default class A extends RR {
49
53
  address: ip,
50
54
  ttl: parseInt(ttl, 10),
51
55
  timestamp: ts,
52
- location: loc !== '' && loc !== '\n' ? loc : '',
56
+ location: loc?.trim() ?? '',
53
57
  })
54
58
  }
55
59
 
56
- fromBind(opts) {
60
+ fromBind({ bindline }) {
57
61
  // test.example.com 3600 IN A 192.0.2.127
58
- const [owner, ttl, c, type, address] = opts.bindline.split(/\s+/)
62
+ const [owner, ttl, c, type, address] = bindline.split(/\s+/)
59
63
  return new A({
60
64
  owner,
61
65
  ttl: parseInt(ttl, 10),
@@ -66,6 +70,10 @@ export default class A extends RR {
66
70
  }
67
71
 
68
72
  /****** EXPORTERS *******/
73
+ getWireRdata() {
74
+ return Buffer.from(this.get('address').split('.').map(Number))
75
+ }
76
+
69
77
  toTinydns() {
70
78
  return `+${this.getTinyFQDN('owner')}:${this.get('address')}:${this.getTinydnsPostamble()}\n`
71
79
  }
package/rr/aaaa.js CHANGED
@@ -15,19 +15,23 @@ export default class AAAA extends RR {
15
15
  }
16
16
 
17
17
  getCompressed(val) {
18
- return this.compress(val || this.get('address'))
18
+ return this.compress(val ?? this.get('address'))
19
19
  }
20
20
 
21
21
  getDescription() {
22
22
  return 'Address IPv6'
23
23
  }
24
24
 
25
+ getTags() {
26
+ return ['common']
27
+ }
28
+
25
29
  getRdataFields(arg) {
26
30
  return ['address']
27
31
  }
28
32
 
29
33
  getRFCs() {
30
- return [3596]
34
+ return [3596, 5952]
31
35
  }
32
36
 
33
37
  getTypeId() {
@@ -40,19 +44,19 @@ export default class AAAA extends RR {
40
44
  address: '2001:0db8:0020:000a:0000:0000:0000:0004',
41
45
  class: 'IN',
42
46
  ttl: 3600,
43
- type: 'A',
47
+ type: 'AAAA',
44
48
  }
45
49
  }
46
50
 
47
51
  /****** IMPORTERS *******/
48
- fromTinydns(opts) {
49
- const str = opts.tinyline
52
+ fromTinydns({ tinyline }) {
53
+ const str = tinyline
50
54
  let fqdn, ip, n, rdata, ttl, ts, loc
51
55
 
52
56
  switch (str[0]) {
53
57
  case ':':
54
58
  // GENERIC => :fqdn:28:rdata:ttl:timestamp:lo
55
- ;[fqdn, n, rdata, ttl, ts, loc] = str.substring(1).split(':')
59
+ ;[fqdn, n, rdata, ttl, ts, loc] = str.slice(1).split(':')
56
60
  if (n != 28) this.throwHelp('AAAA fromTinydns, invalid n')
57
61
  ip = TINYDNS.octalToHex(rdata)
58
62
  .match(/([0-9a-fA-F]{4})/g)
@@ -62,7 +66,7 @@ export default class AAAA extends RR {
62
66
  case '6':
63
67
  // AAAA => 3fqdn:ip:x:ttl:timestamp:lo
64
68
  // AAAA,PTR => 6fqdn:ip:x:ttl:timestamp:lo
65
- ;[fqdn, rdata, ttl, ts, loc] = str.substring(1).split(':')
69
+ ;[fqdn, rdata, ttl, ts, loc] = str.slice(1).split(':')
66
70
  ip = rdata.match(/(.{4})/g).join(':')
67
71
  break
68
72
  }
@@ -73,13 +77,13 @@ export default class AAAA extends RR {
73
77
  type: 'AAAA',
74
78
  address: ip,
75
79
  timestamp: ts,
76
- location: loc !== '' && loc !== '\n' ? loc : '',
80
+ location: loc?.trim() ?? '',
77
81
  })
78
82
  }
79
83
 
80
- fromBind(opts) {
84
+ fromBind({ bindline }) {
81
85
  // test.example.com 3600 IN AAAA ...
82
- const [owner, ttl, c, type, ip] = opts.bindline.split(/\s+/)
86
+ const [owner, ttl, c, type, ip] = bindline.split(/\s+/)
83
87
  return new AAAA({
84
88
  owner,
85
89
  ttl: parseInt(ttl, 10),
@@ -124,7 +128,7 @@ export default class AAAA extends RR {
124
128
  if (delimiter === undefined) delimiter = ':'
125
129
 
126
130
  const colons = val.match(/:/g)
127
- if (colons && colons.length < 7) {
131
+ if (colons?.length < 7) {
128
132
  // console.log(`AAAA: restoring compressed colons`)
129
133
  val = val.replace(/::/, ':'.repeat(9 - colons.length))
130
134
  }
@@ -138,6 +142,10 @@ export default class AAAA extends RR {
138
142
  }
139
143
 
140
144
  /****** EXPORTERS *******/
145
+ getWireRdata() {
146
+ return Buffer.from(this.expand(this.get('address'), ''), 'hex')
147
+ }
148
+
141
149
  toBind(zone_opts) {
142
150
  return `${this.getPrefix(zone_opts)}\t${this.getCompressed()}\n`
143
151
  }
package/rr/apl.js CHANGED
@@ -42,9 +42,56 @@ export default class APL extends RR {
42
42
  }
43
43
 
44
44
  /****** IMPORTERS *******/
45
- fromBind(opts) {
45
+ fromTinydns({ tinyline }) {
46
+ // APL via generic, :fqdn:42:rdata:ttl:timestamp:lo
47
+ const [fqdn, n, rdata, ttl, ts, loc] = tinyline.slice(1).split(':')
48
+ if (n != 42) this.throwHelp('APL fromTinydns, invalid n')
49
+
50
+ const bytes = Buffer.from(TINYDNS.octalToChar(rdata), 'binary')
51
+ const items = []
52
+ let pos = 0
53
+
54
+ while (pos < bytes.length) {
55
+ const afi = bytes.readUInt16BE(pos)
56
+ pos += 2
57
+ const prefix = bytes.readUInt8(pos)
58
+ pos++
59
+ const adfLenByte = bytes.readUInt8(pos)
60
+ pos++
61
+ const neg = (adfLenByte & 0x80) !== 0
62
+ const addrLen = adfLenByte & 0x7f
63
+ const addrBytes = bytes.slice(pos, pos + addrLen)
64
+ pos += addrLen
65
+
66
+ let addr
67
+ if (afi === 1) {
68
+ const padded = Buffer.alloc(4)
69
+ addrBytes.copy(padded)
70
+ addr = [...padded].join('.')
71
+ } else {
72
+ const padded = Buffer.alloc(16)
73
+ addrBytes.copy(padded)
74
+ const groups = []
75
+ for (let i = 0; i < 16; i += 2) groups.push(padded.readUInt16BE(i).toString(16).padStart(4, '0'))
76
+ addr = this.compressIPv6(groups.join(':'))
77
+ }
78
+
79
+ items.push(`${neg ? '!' : ''}${afi}:${addr}/${prefix}`)
80
+ }
81
+
82
+ return new APL({
83
+ owner: this.fullyQualify(fqdn),
84
+ ttl: parseInt(ttl, 10),
85
+ type: 'APL',
86
+ 'apl rdata': items.join(' '),
87
+ timestamp: ts,
88
+ location: loc?.trim() ?? '',
89
+ })
90
+ }
91
+
92
+ fromBind({ bindline }) {
46
93
  // test.example.com 3600 IN APL {[!]afi:address/prefix}*
47
- const parts = opts.bindline.split(/\s+/)
94
+ const parts = bindline.split(/\s+/)
48
95
  const [owner, ttl, c, type] = parts
49
96
  return new APL({
50
97
  owner,
package/rr/caa.js CHANGED
@@ -62,6 +62,10 @@ export default class CAA extends RR {
62
62
  return 'Certification Authority Authorization'
63
63
  }
64
64
 
65
+ getTags() {
66
+ return ['security']
67
+ }
68
+
65
69
  getQuotedFields() {
66
70
  return ['value']
67
71
  }
@@ -91,17 +95,17 @@ export default class CAA extends RR {
91
95
  }
92
96
 
93
97
  /****** IMPORTERS *******/
94
- fromTinydns(opts) {
98
+ fromTinydns({ tinyline }) {
95
99
  // CAA via generic, :fqdn:n:rdata:ttl:timestamp:lo
96
- const [fqdn, n, rdata, ttl, ts, loc] = opts.tinyline.substring(1).split(':')
100
+ const [fqdn, n, rdata, ttl, ts, loc] = tinyline.slice(1).split(':')
97
101
  if (n != 257) this.throwHelp('CAA fromTinydns, invalid n')
98
102
 
99
- const flags = TINYDNS.octalToUInt8(rdata.substring(0, 4))
100
- const taglen = TINYDNS.octalToUInt8(rdata.substring(4, 8))
103
+ const flags = TINYDNS.octalToUInt8(rdata.slice(0, 4))
104
+ const taglen = TINYDNS.octalToUInt8(rdata.slice(4, 8))
101
105
 
102
- const unescaped = TINYDNS.octalToChar(rdata.substring(8))
103
- const tag = unescaped.substring(0, taglen)
104
- const fingerprint = unescaped.substring(taglen)
106
+ const unescaped = TINYDNS.octalToChar(rdata.slice(8))
107
+ const tag = unescaped.slice(0, taglen)
108
+ const fingerprint = unescaped.slice(taglen)
105
109
 
106
110
  return new CAA({
107
111
  owner: this.fullyQualify(fqdn),
@@ -111,17 +115,23 @@ export default class CAA extends RR {
111
115
  tag,
112
116
  value: fingerprint,
113
117
  timestamp: ts,
114
- location: loc !== '' && loc !== '\n' ? loc : '',
118
+ location: loc?.trim() ?? '',
115
119
  })
116
120
  }
117
121
 
118
- fromBind(opts) {
122
+ fromBind({ bindline }) {
119
123
  // test.example.com 3600 IN CAA flags, tags, value
120
- const regex = /^([\S]+)\s+([0-9]{1,10})\s+(IN)\s+(CAA)\s+([0-9]+)\s+(\w+)\s+("[^"]+"|[\S]+?)$/i
121
- const fields = opts.bindline.trim().match(regex)
122
- if (!fields) this.throwHelp(`unable to parse: ${opts.bindline}`)
124
+ const regex =
125
+ /^(?<owner>\S+)\s+(?<ttl>\d{1,10})\s+(?<class>IN)\s+(?<type>CAA)\s+(?<flags>\d+)\s+(?<tag>\w+)\s+(?:"(?<quotedValue>[^"]+)"|(?<unquotedValue>\S+))$/i
126
+
127
+ const match = bindline.trim().match(regex)
128
+
129
+ if (!match) {
130
+ this.throwHelp(`unable to parse CAA: ${bindline}`)
131
+ }
132
+
133
+ const { owner, ttl, class: c, type, flags, tag, quotedValue, unquotedValue } = match.groups
123
134
 
124
- const [owner, ttl, c, type, flags, tag, value] = fields.slice(1)
125
135
  return new CAA({
126
136
  owner,
127
137
  ttl: parseInt(ttl, 10),
@@ -129,21 +139,18 @@ export default class CAA extends RR {
129
139
  type,
130
140
  flags: parseInt(flags, 10),
131
141
  tag,
132
- value,
142
+ value: quotedValue ?? unquotedValue,
133
143
  })
134
144
  }
135
145
 
136
146
  /****** EXPORTERS *******/
137
147
 
138
148
  toTinydns() {
139
- let rdata = ''
140
- rdata += TINYDNS.UInt8toOctal(this.get('flags'))
141
-
142
- rdata += TINYDNS.UInt8toOctal(this.get('tag').length)
143
- rdata += TINYDNS.escapeOctal(/[\r\n\t:\\/]/, this.get('tag'))
144
-
145
- rdata += TINYDNS.escapeOctal(/[\r\n\t:\\/]/, this.getQuoted('value'))
146
-
147
- return this.getTinydnsGeneric(rdata)
149
+ return this.getTinydnsGeneric(
150
+ TINYDNS.UInt8toOctal(this.get('flags')) +
151
+ TINYDNS.UInt8toOctal(this.get('tag').length) +
152
+ TINYDNS.escapeOctal(/[\r\n\t:\\/]/, this.get('tag')) +
153
+ TINYDNS.escapeOctal(/[\r\n\t:\\/]/, this.getQuoted('value')),
154
+ )
148
155
  }
149
156
  }