@nictool/dns-resource-record 1.6.0 → 1.7.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +45 -6
  2. package/README.md +328 -198
  3. package/dist/dns-rr.min.js +26 -0
  4. package/dist/dns-rr.min.js.map +7 -0
  5. package/index.js +12 -11
  6. package/lib/binary.js +108 -0
  7. package/lib/bind.js +94 -0
  8. package/lib/dns-query.js +67 -0
  9. package/lib/tinydns.js +128 -44
  10. package/lib/wire.js +519 -0
  11. package/package.json +29 -10
  12. package/rr/TEMPLATE.js +124 -0
  13. package/rr/a.js +8 -48
  14. package/rr/aaaa.js +13 -80
  15. package/rr/apl.js +123 -27
  16. package/rr/caa.js +27 -50
  17. package/rr/cert.js +58 -87
  18. package/rr/cname.js +7 -63
  19. package/rr/dhcid.js +20 -30
  20. package/rr/dname.js +9 -36
  21. package/rr/dnskey.js +38 -32
  22. package/rr/ds.js +43 -57
  23. package/rr/hinfo.js +21 -40
  24. package/rr/hip.js +88 -26
  25. package/rr/https.js +28 -40
  26. package/rr/ipseckey.js +97 -18
  27. package/rr/key.js +39 -29
  28. package/rr/kx.js +16 -27
  29. package/rr/loc.js +42 -12
  30. package/rr/mx.js +16 -51
  31. package/rr/naptr.js +58 -33
  32. package/rr/ns.js +8 -36
  33. package/rr/nsec.js +89 -18
  34. package/rr/nsec3.js +62 -26
  35. package/rr/nsec3param.js +56 -53
  36. package/rr/nxt.js +57 -18
  37. package/rr/openpgpkey.js +24 -30
  38. package/rr/ptr.js +7 -47
  39. package/rr/rp.js +17 -32
  40. package/rr/rrsig.js +80 -85
  41. package/rr/sig.js +70 -45
  42. package/rr/smimea.js +33 -33
  43. package/rr/soa.js +31 -49
  44. package/rr/spf.js +10 -17
  45. package/rr/srv.js +22 -62
  46. package/rr/sshfp.js +25 -53
  47. package/rr/svcb.js +27 -40
  48. package/rr/tlsa.js +34 -34
  49. package/rr/tsig.js +87 -24
  50. package/rr/txt.js +40 -59
  51. package/rr/uri.js +19 -29
  52. package/rr/wks.js +154 -22
  53. package/rr.js +290 -109
  54. package/lib/readme.js +0 -84
package/rr/TEMPLATE.js ADDED
@@ -0,0 +1,124 @@
1
+ import RR from '../rr.js'
2
+
3
+ /**
4
+ * DNS Resource Record Template
5
+ *
6
+ * Use this as a starting point for creating a new Resource Record (RR) class.
7
+ * Replace 'TEMPLATE' with the actual record type (e.g., 'MX', 'SRV').
8
+ *
9
+ * ## Mandates
10
+ * - Every new RR MUST extend the `RR` class.
11
+ * - 100% test coverage is required for all new PRs.
12
+ * - Follow established patterns for naming and implementation.
13
+ * - **Registration**: You MUST update `index.js` to import and export the new class and add it to the `classes` array.
14
+ * - **Tests**: You MUST create a corresponding test file in `test/rr/<type>.js` (mirroring an existing test file like `test/rr/a.js`).
15
+ *
16
+ * ## `rdataFields` and Avoiding Boilerplate
17
+ * The `rdataFields` static property defines the fields in the RDATA section.
18
+ * It is an array where each element can be:
19
+ * - A string: The field name.
20
+ * - An array: `[fieldName, type]`
21
+ *
22
+ * Supported types for `setTypedValue`:
23
+ * - `u8`, `u16`, `u32`: Unsigned integers of 8, 16, or 32 bits.
24
+ * - `fqdn`: A fully qualified domain name.
25
+ * - `qstr`: Quoted string field (BIND quoting behavior).
26
+ * - `charstr`: DNS length-prefixed character-string (max 255 bytes).
27
+ * - `qcharstr`: Quoted DNS length-prefixed character-string.
28
+ * - `charstrs`: Concatenated DNS character-strings.
29
+ * - `svcparams`: SVCB/HTTPS params string.
30
+ * - `base64`: Base64 encoded data.
31
+ * - `hex`: Hexadecimal encoded data.
32
+ * - `str`: A simple string.
33
+ * - `ipv4`: An IPv4 address.
34
+ * - `ipv6`: An IPv6 address.
35
+ *
36
+ * If you provide a type in `rdataFields`, the base `RR` class automatically
37
+ * handles validation and setting of that field. You only need to implement
38
+ * a custom `set<FieldName>` method if you need more complex validation
39
+ * logic than what the default types provide.
40
+ */
41
+
42
+ export default class TEMPLATE extends RR {
43
+ // The DNS record type name (e.g., 'A', 'MX', 'TXT')
44
+ static typeName = 'TEMPLATE'
45
+ static typeId = 0
46
+ static RFCs = []
47
+ static tags = []
48
+
49
+ // The character used in tinydns format (optional)
50
+ static tinydnsType = 'T'
51
+
52
+ // Define the RDATA fields.
53
+ // Using types here avoids the need for manual setters in many cases.
54
+ static rdataFields = [
55
+ ['field1', 'u16'],
56
+ ['field2', 'fqdn'],
57
+ ]
58
+
59
+ constructor(opts) {
60
+ super(opts)
61
+ }
62
+
63
+ /****** Resource record specific setters *******/
64
+
65
+ /*
66
+ // Example of a custom setter. Only implement if default validation
67
+ // from `rdataFields` is insufficient.
68
+ setField1(val) {
69
+ if (val === undefined) val = this?.default?.field1
70
+ if (val === undefined) this.throwHelp('TEMPLATE: field1 is required')
71
+ this.is16bitInt('TEMPLATE', 'field1', val)
72
+ this.set('field1', parseInt(val, 10))
73
+ }
74
+ */
75
+
76
+ /****** Metadata (Required) *******/
77
+
78
+ getDescription() {
79
+ return 'Short description of the TEMPLATE record'
80
+ }
81
+
82
+ getCanonical() {
83
+ // Returns a sample object representing a valid record.
84
+ // This is used for documentation and testing.
85
+ return {
86
+ owner: 'example.com.',
87
+ ttl: 3600,
88
+ class: 'IN',
89
+ type: 'TEMPLATE',
90
+ field1: 10,
91
+ field2: 'target.example.com.',
92
+ }
93
+ }
94
+
95
+ /****** IMPORTERS / EXPORTERS (Recommended) *******/
96
+
97
+ /*
98
+ // Optional: Override if generic implementation in RR.js is insufficient
99
+ fromTinydns({ tinyline }) {
100
+ // Implementation...
101
+ }
102
+ */
103
+
104
+ /*
105
+ // Optional: Override if generic implementation in RR.js is insufficient
106
+ toTinydns() {
107
+ // Implementation...
108
+ }
109
+ */
110
+
111
+ getWireRdata() {
112
+ // MUST be implemented for binary/wire format support.
113
+ // Returns a Uint8Array.
114
+ // Example:
115
+ /*
116
+ const domain = this.wirePackDomain(this.get('field2'))
117
+ const result = new Uint8Array(2 + domain.length)
118
+ new DataView(result.buffer).setUint16(0, this.get('field1'))
119
+ result.set(domain, 2)
120
+ return result
121
+ */
122
+ throw new Error('getWireRdata not implemented')
123
+ }
124
+ }
package/rr/a.js CHANGED
@@ -1,6 +1,13 @@
1
1
  import RR from '../rr.js'
2
2
 
3
3
  export default class A extends RR {
4
+ static typeName = 'A'
5
+ static typeId = 1
6
+ static RFCs = [1035]
7
+ static tinydnsType = '+'
8
+ static rdataFields = [['address', 'ipv4']]
9
+ static tags = ['common']
10
+
4
11
  constructor(opts) {
5
12
  super(opts)
6
13
  }
@@ -16,22 +23,6 @@ export default class A extends RR {
16
23
  return 'Address'
17
24
  }
18
25
 
19
- getTags() {
20
- return ['common']
21
- }
22
-
23
- getRdataFields(arg) {
24
- return ['address']
25
- }
26
-
27
- getRFCs() {
28
- return [1035]
29
- }
30
-
31
- getTypeId() {
32
- return 1
33
- }
34
-
35
26
  getCanonical() {
36
27
  return {
37
28
  owner: 'host.example.com.',
@@ -42,39 +33,8 @@ export default class A extends RR {
42
33
  }
43
34
  }
44
35
 
45
- /****** IMPORTERS *******/
46
- fromTinydns({ tinyline }) {
47
- // +fqdn:ip:ttl:timestamp:lo
48
- const [owner, ip, ttl, ts, loc] = tinyline.slice(1).split(':')
49
-
50
- return new A({
51
- owner: this.fullyQualify(owner),
52
- type: 'A',
53
- address: ip,
54
- ttl: parseInt(ttl, 10),
55
- timestamp: ts,
56
- location: loc?.trim() ?? '',
57
- })
58
- }
59
-
60
- fromBind({ bindline }) {
61
- // test.example.com 3600 IN A 192.0.2.127
62
- const [owner, ttl, c, type, address] = bindline.split(/\s+/)
63
- return new A({
64
- owner,
65
- ttl: parseInt(ttl, 10),
66
- class: c,
67
- type,
68
- address,
69
- })
70
- }
71
-
72
36
  /****** EXPORTERS *******/
73
37
  getWireRdata() {
74
- return Buffer.from(this.get('address').split('.').map(Number))
75
- }
76
-
77
- toTinydns() {
78
- return `+${this.getTinyFQDN('owner')}:${this.get('address')}:${this.getTinydnsPostamble()}\n`
38
+ return new Uint8Array(this.get('address').split('.').map(Number))
79
39
  }
80
40
  }
package/rr/aaaa.js CHANGED
@@ -2,6 +2,12 @@ import RR from '../rr.js'
2
2
  import * as TINYDNS from '../lib/tinydns.js'
3
3
 
4
4
  export default class AAAA extends RR {
5
+ static typeName = 'AAAA'
6
+ static typeId = 28
7
+ static RFCs = [3596, 5952]
8
+ static rdataFields = [['address', 'ipv6']]
9
+ static tags = ['common']
10
+
5
11
  constructor(opts) {
6
12
  super(opts)
7
13
  }
@@ -11,33 +17,17 @@ export default class AAAA extends RR {
11
17
  if (!val) this.throwHelp('AAAA: address is required')
12
18
  if (!this.isIPv6(val)) this.throwHelp(`AAAA: address must be IPv6 (${val})`)
13
19
 
14
- this.set('address', this.expand(val.toLowerCase())) // lower case: RFC 5952
20
+ this.set('address', this.expandIPv6(val.toLowerCase())) // lower case: RFC 5952
15
21
  }
16
22
 
17
23
  getCompressed(val) {
18
- return this.compress(val ?? this.get('address'))
24
+ return this.compressIPv6(val ?? this.get('address'))
19
25
  }
20
26
 
21
27
  getDescription() {
22
28
  return 'Address IPv6'
23
29
  }
24
30
 
25
- getTags() {
26
- return ['common']
27
- }
28
-
29
- getRdataFields(arg) {
30
- return ['address']
31
- }
32
-
33
- getRFCs() {
34
- return [3596, 5952]
35
- }
36
-
37
- getTypeId() {
38
- return 28
39
- }
40
-
41
31
  getCanonical() {
42
32
  return {
43
33
  owner: 'host.example.com.',
@@ -81,69 +71,12 @@ export default class AAAA extends RR {
81
71
  })
82
72
  }
83
73
 
84
- fromBind({ bindline }) {
85
- // test.example.com 3600 IN AAAA ...
86
- const [owner, ttl, c, type, ip] = bindline.split(/\s+/)
87
- return new AAAA({
88
- owner,
89
- ttl: parseInt(ttl, 10),
90
- class: c,
91
- type,
92
- address: this.expand(ip),
93
- })
94
- }
95
-
96
- compress(val) {
97
- /*
98
- * RFC 5952
99
- * 4.1. Leading zeros MUST be suppressed...A single 16-bit 0000 field MUST be represented as 0.
100
- * 4.2.1 The use of the symbol "::" MUST be used to its maximum capability.
101
- * 4.2.2 The symbol "::" MUST NOT be used to shorten just one 16-bit 0 field.
102
- * 4.2.3 When choosing placement of a "::", the longest run...MUST be shortened
103
- * 4.3 The characters a-f in an IPv6 address MUST be represented in lowercase.
104
- */
105
- let r = val
106
- .replace(/0000/g, '0') // 4.1 0000 -> 0
107
- .replace(/:0+([1-9a-fA-F])/g, ':$1') // 4.1 remove leading zeros
108
-
109
- const mostConsecutiveZeros = [
110
- new RegExp(/0?(?::0){6,}:0?/),
111
- new RegExp(/0?(?::0){5,}:0?/),
112
- new RegExp(/0?(?::0){4,}:0?/),
113
- new RegExp(/0?(?::0){3,}:0?/),
114
- new RegExp(/0?(?::0){2,}:0?/),
115
- ]
116
-
117
- for (const re of mostConsecutiveZeros) {
118
- if (re.test(r)) {
119
- r = r.replace(re, '::') // 4.2
120
- break
121
- }
122
- }
123
-
124
- return r.toLowerCase() // 4.3
125
- }
126
-
127
- expand(val, delimiter) {
128
- if (delimiter === undefined) delimiter = ':'
129
-
130
- const colons = val.match(/:/g)
131
- if (colons?.length < 7) {
132
- // console.log(`AAAA: restoring compressed colons`)
133
- val = val.replace(/::/, ':'.repeat(9 - colons.length))
134
- }
135
-
136
- // restore compressed leading zeros
137
- return val
138
- .split(':')
139
- .map((s) => s.padStart(4, 0))
140
- .join(delimiter)
141
- .toLowerCase()
142
- }
143
-
144
74
  /****** EXPORTERS *******/
145
75
  getWireRdata() {
146
- return Buffer.from(this.expand(this.get('address'), ''), 'hex')
76
+ const hex = this.expandIPv6(this.get('address'), '')
77
+ const arr = new Uint8Array(hex.length / 2)
78
+ for (let i = 0; i < arr.length; i++) arr[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16)
79
+ return arr
147
80
  }
148
81
 
149
82
  toBind(zone_opts) {
@@ -152,7 +85,7 @@ export default class AAAA extends RR {
152
85
 
153
86
  toTinydns() {
154
87
  // from AAAA notation (8 groups of 4 hex digits) to 16 escaped octals
155
- const rdata = TINYDNS.packHex(this.expand(this.get('address'), ''))
88
+ const rdata = TINYDNS.packHex(this.expandIPv6(this.get('address'), ''))
156
89
  return this.getTinydnsGeneric(rdata)
157
90
  }
158
91
  }
package/rr/apl.js CHANGED
@@ -3,6 +3,11 @@ import RR from '../rr.js'
3
3
  import * as TINYDNS from '../lib/tinydns.js'
4
4
 
5
5
  export default class APL extends RR {
6
+ static typeName = 'APL'
7
+ static typeId = 42
8
+ static RFCs = [3123]
9
+ static rdataFields = ['apl rdata']
10
+
6
11
  constructor(opts) {
7
12
  super(opts)
8
13
  }
@@ -10,8 +15,6 @@ export default class APL extends RR {
10
15
  /****** Resource record specific setters *******/
11
16
  setAplRdata(val) {
12
17
  if (!val) this.throwHelp('APL: apl rdata is required')
13
- // apl rdata is a list of address prefix list items, e.g.:
14
- // 1:192.0.2.0/24 !1:192.0.2.64/28 2:2001:db8::/32
15
18
  this.set('apl rdata', val)
16
19
  }
17
20
 
@@ -19,25 +22,13 @@ export default class APL extends RR {
19
22
  return 'Address Prefix List'
20
23
  }
21
24
 
22
- getRdataFields(arg) {
23
- return ['apl rdata']
24
- }
25
-
26
- getRFCs() {
27
- return [3123]
28
- }
29
-
30
- getTypeId() {
31
- return 42
32
- }
33
-
34
25
  getCanonical() {
35
26
  return {
36
27
  owner: 'example.com.',
37
28
  ttl: 3600,
38
29
  class: 'IN',
39
30
  type: 'APL',
40
- 'apl rdata': '1:192.0.2.0/24 !1:192.0.2.64/28 2:2001:db8::/32',
31
+ 'apl rdata': '1:192.0.2.1/24 !1:192.0.2.64/28 2:2001:db8::1/128',
41
32
  }
42
33
  }
43
34
 
@@ -47,32 +38,33 @@ export default class APL extends RR {
47
38
  const [fqdn, n, rdata, ttl, ts, loc] = tinyline.slice(1).split(':')
48
39
  if (n != 42) this.throwHelp('APL fromTinydns, invalid n')
49
40
 
50
- const bytes = Buffer.from(TINYDNS.octalToChar(rdata), 'binary')
41
+ const bytes = Uint8Array.from(TINYDNS.octalToChar(rdata), (c) => c.charCodeAt(0))
51
42
  const items = []
52
43
  let pos = 0
53
44
 
54
45
  while (pos < bytes.length) {
55
- const afi = bytes.readUInt16BE(pos)
46
+ const afi = (bytes[pos] << 8) | bytes[pos + 1]
56
47
  pos += 2
57
- const prefix = bytes.readUInt8(pos)
48
+ const prefix = bytes[pos]
58
49
  pos++
59
- const adfLenByte = bytes.readUInt8(pos)
50
+ const adfLenByte = bytes[pos]
60
51
  pos++
61
52
  const neg = (adfLenByte & 0x80) !== 0
62
53
  const addrLen = adfLenByte & 0x7f
63
- const addrBytes = bytes.slice(pos, pos + addrLen)
54
+ const addrBytes = bytes.subarray(pos, pos + addrLen)
64
55
  pos += addrLen
65
56
 
66
57
  let addr
67
58
  if (afi === 1) {
68
- const padded = Buffer.alloc(4)
69
- addrBytes.copy(padded)
59
+ const padded = new Uint8Array(4)
60
+ padded.set(addrBytes)
70
61
  addr = [...padded].join('.')
71
62
  } else {
72
- const padded = Buffer.alloc(16)
73
- addrBytes.copy(padded)
63
+ const padded = new Uint8Array(16)
64
+ padded.set(addrBytes)
65
+ const paddedDv = new DataView(padded.buffer)
74
66
  const groups = []
75
- for (let i = 0; i < 16; i += 2) groups.push(padded.readUInt16BE(i).toString(16).padStart(4, '0'))
67
+ for (let i = 0; i < 16; i += 2) groups.push(paddedDv.getUint16(i).toString(16).padStart(4, '0'))
76
68
  addr = this.compressIPv6(groups.join(':'))
77
69
  }
78
70
 
@@ -102,6 +94,43 @@ export default class APL extends RR {
102
94
  })
103
95
  }
104
96
 
97
+ fromWire({ owner, cls, ttl, rdata }) {
98
+ const items = []
99
+ let pos = 0
100
+ while (pos < rdata.length) {
101
+ const afi = (rdata[pos] << 8) | rdata[pos + 1]
102
+ pos += 2
103
+ const prefix = rdata[pos++]
104
+ const adfLenByte = rdata[pos++]
105
+ const neg = (adfLenByte & 0x80) !== 0
106
+ const addrLen = adfLenByte & 0x7f
107
+ const addrBytes = rdata.subarray(pos, pos + addrLen)
108
+ pos += addrLen
109
+
110
+ let addr
111
+ if (afi === 1) {
112
+ const padded = new Uint8Array(4)
113
+ padded.set(addrBytes)
114
+ addr = [...padded].join('.')
115
+ } else {
116
+ const padded = new Uint8Array(16)
117
+ padded.set(addrBytes)
118
+ const dv = new DataView(padded.buffer)
119
+ const groups = []
120
+ for (let i = 0; i < 16; i += 2) groups.push(dv.getUint16(i).toString(16).padStart(4, '0'))
121
+ addr = this.compressIPv6(groups.join(':'))
122
+ }
123
+ items.push(`${neg ? '!' : ''}${afi}:${addr}/${prefix}`)
124
+ }
125
+ return new APL({
126
+ owner,
127
+ ttl,
128
+ class: cls,
129
+ type: 'APL',
130
+ 'apl rdata': items.join(' '),
131
+ })
132
+ }
133
+
105
134
  /****** EXPORTERS *******/
106
135
  toTinydns() {
107
136
  return this.getTinydnsGeneric(
@@ -119,7 +148,7 @@ export default class APL extends RR {
119
148
 
120
149
  let addrBytes
121
150
  if (afi === 1) {
122
- addrBytes = Buffer.from(addr.split('.').map((n) => parseInt(n, 10)))
151
+ addrBytes = new Uint8Array(addr.split('.').map((n) => parseInt(n, 10)))
123
152
  } else {
124
153
  const dblIdx = addr.indexOf('::')
125
154
  let groups
@@ -136,7 +165,10 @@ export default class APL extends RR {
136
165
  } else {
137
166
  groups = addr.split(':')
138
167
  }
139
- addrBytes = Buffer.from(groups.map((g) => g.padStart(4, '0')).join(''), 'hex')
168
+ const hexStr = groups.map((g) => g.padStart(4, '0')).join('')
169
+ addrBytes = Uint8Array.from({ length: hexStr.length / 2 }, (_, i) =>
170
+ parseInt(hexStr.slice(i * 2, i * 2 + 2), 16),
171
+ )
140
172
  }
141
173
 
142
174
  let len = addrBytes.length
@@ -152,4 +184,68 @@ export default class APL extends RR {
152
184
  .join(''),
153
185
  )
154
186
  }
187
+
188
+ getWireRdata() {
189
+ const items = this.get('apl rdata').split(/\s+/)
190
+ const rdata = []
191
+
192
+ for (const item of items) {
193
+ const neg = item.startsWith('!')
194
+ const bare = neg ? item.slice(1) : item
195
+ const colonIdx = bare.indexOf(':')
196
+ const afi = parseInt(bare.slice(0, colonIdx), 10)
197
+ const rest = bare.slice(colonIdx + 1)
198
+ const slashIdx = rest.lastIndexOf('/')
199
+ const addr = rest.slice(0, slashIdx)
200
+ const prefix = parseInt(rest.slice(slashIdx + 1), 10)
201
+
202
+ let addrBytes
203
+ if (afi === 1) {
204
+ addrBytes = new Uint8Array(addr.split('.').map((n) => parseInt(n, 10)))
205
+ } else {
206
+ const dblIdx = addr.indexOf('::')
207
+ let groups
208
+ if (dblIdx !== -1) {
209
+ const left = addr
210
+ .slice(0, dblIdx)
211
+ .split(':')
212
+ .filter((s) => s !== '')
213
+ const right = addr
214
+ .slice(dblIdx + 2)
215
+ .split(':')
216
+ .filter((s) => s !== '')
217
+ groups = [...left, ...Array(8 - left.length - right.length).fill('0000'), ...right]
218
+ } else {
219
+ groups = addr.split(':')
220
+ }
221
+ const hexStr = groups.map((g) => g.padStart(4, '0')).join('')
222
+ addrBytes = Uint8Array.from({ length: hexStr.length / 2 }, (_, i) =>
223
+ parseInt(hexStr.slice(i * 2, i * 2 + 2), 16),
224
+ )
225
+ }
226
+
227
+ let len = addrBytes.length
228
+ while (len > 0 && addrBytes[len - 1] === 0) len--
229
+ const afdPart = addrBytes.slice(0, len)
230
+
231
+ const itemBytes = new Uint8Array(4 + afdPart.length)
232
+ const dv = new DataView(itemBytes.buffer, itemBytes.byteOffset)
233
+ dv.setUint16(0, afi)
234
+ itemBytes[2] = prefix
235
+ itemBytes[3] = (neg ? 0x80 : 0) | afdPart.length
236
+ itemBytes.set(afdPart, 4)
237
+
238
+ rdata.push(itemBytes)
239
+ }
240
+
241
+ const totalLen = rdata.reduce((sum, r) => sum + r.length, 0)
242
+ const bytes = new Uint8Array(totalLen)
243
+ let pos = 0
244
+ for (const r of rdata) {
245
+ bytes.set(r, pos)
246
+ pos += r.length
247
+ }
248
+
249
+ return bytes
250
+ }
155
251
  }
package/rr/caa.js CHANGED
@@ -2,6 +2,16 @@ import RR from '../rr.js'
2
2
  import * as TINYDNS from '../lib/tinydns.js'
3
3
 
4
4
  export default class CAA extends RR {
5
+ static typeName = 'CAA'
6
+ static typeId = 257
7
+ static RFCs = [6844, 8659, 9619]
8
+ static rdataFields = [
9
+ ['flags', 'u8'],
10
+ ['tag', 'charstr'],
11
+ ['value', 'qstr'],
12
+ ]
13
+ static tags = ['security']
14
+
5
15
  constructor(opts) {
6
16
  super(opts)
7
17
  }
@@ -62,26 +72,6 @@ export default class CAA extends RR {
62
72
  return 'Certification Authority Authorization'
63
73
  }
64
74
 
65
- getTags() {
66
- return ['security']
67
- }
68
-
69
- getQuotedFields() {
70
- return ['value']
71
- }
72
-
73
- getRdataFields(arg) {
74
- return ['flags', 'tag', 'value']
75
- }
76
-
77
- getRFCs() {
78
- return [6844, 8659]
79
- }
80
-
81
- getTypeId() {
82
- return 257
83
- }
84
-
85
75
  getCanonical() {
86
76
  return {
87
77
  owner: 'example.com.',
@@ -97,8 +87,8 @@ export default class CAA extends RR {
97
87
  /****** IMPORTERS *******/
98
88
  fromTinydns({ tinyline }) {
99
89
  // CAA via generic, :fqdn:n:rdata:ttl:timestamp:lo
100
- const [fqdn, n, rdata, ttl, ts, loc] = tinyline.slice(1).split(':')
101
- if (n != 257) this.throwHelp('CAA fromTinydns, invalid n')
90
+ const { owner, typeId, rdata, ttl, timestamp, location } = this.parseTinydnsLine(tinyline)
91
+ if (typeId != this.getTypeId()) this.throwHelp('CAA fromTinydns, invalid typeId')
102
92
 
103
93
  const flags = TINYDNS.octalToUInt8(rdata.slice(0, 4))
104
94
  const taglen = TINYDNS.octalToUInt8(rdata.slice(4, 8))
@@ -108,49 +98,36 @@ export default class CAA extends RR {
108
98
  const fingerprint = unescaped.slice(taglen)
109
99
 
110
100
  return new CAA({
111
- owner: this.fullyQualify(fqdn),
112
- ttl: parseInt(ttl, 10),
101
+ owner,
102
+ ttl,
113
103
  type: 'CAA',
114
104
  flags,
115
105
  tag,
116
106
  value: fingerprint,
117
- timestamp: ts,
118
- location: loc?.trim() ?? '',
107
+ timestamp,
108
+ location,
119
109
  })
120
110
  }
121
111
 
122
- fromBind({ bindline }) {
123
- // test.example.com 3600 IN CAA flags, tags, value
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
112
+ /****** EXPORTERS *******/
134
113
 
135
- return new CAA({
136
- owner,
137
- ttl: parseInt(ttl, 10),
138
- class: c,
139
- type,
140
- flags: parseInt(flags, 10),
141
- tag,
142
- value: quotedValue ?? unquotedValue,
143
- })
114
+ getWireRdata() {
115
+ const tag = new TextEncoder().encode(this.get('tag'))
116
+ const value = new TextEncoder().encode(this.get('value'))
117
+ const result = new Uint8Array(2 + tag.length + value.length)
118
+ result[0] = this.get('flags')
119
+ result[1] = tag.length
120
+ result.set(tag, 2)
121
+ result.set(value, 2 + tag.length)
122
+ return result
144
123
  }
145
124
 
146
- /****** EXPORTERS *******/
147
-
148
125
  toTinydns() {
149
126
  return this.getTinydnsGeneric(
150
127
  TINYDNS.UInt8toOctal(this.get('flags')) +
151
128
  TINYDNS.UInt8toOctal(this.get('tag').length) +
152
129
  TINYDNS.escapeOctal(/[\r\n\t:\\/]/, this.get('tag')) +
153
- TINYDNS.escapeOctal(/[\r\n\t:\\/]/, this.getQuoted('value')),
130
+ TINYDNS.escapeOctal(/[\r\n\t:\\/]/, this.get('value')),
154
131
  )
155
132
  }
156
133
  }