@nictool/dns-resource-record 1.6.1 → 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 +33 -0
  2. package/README.md +323 -199
  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 +23 -6
  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/nsec.js CHANGED
@@ -1,8 +1,15 @@
1
1
  import RR from '../rr.js'
2
2
 
3
3
  import * as TINYDNS from '../lib/tinydns.js'
4
+ import { DNS_TYPE_IDS } from '../lib/binary.js'
4
5
 
5
6
  export default class NSEC extends RR {
7
+ static typeName = 'NSEC'
8
+ static typeId = 47
9
+ static RFCs = [4034]
10
+ static rdataFields = ['next domain', 'type bit maps']
11
+ static tags = ['dnssec']
12
+
6
13
  constructor(opts) {
7
14
  super(opts)
8
15
  if (opts === null) return
@@ -29,22 +36,6 @@ export default class NSEC extends RR {
29
36
  return 'Next Secure'
30
37
  }
31
38
 
32
- getTags() {
33
- return ['dnssec']
34
- }
35
-
36
- getRdataFields(arg) {
37
- return ['next domain', 'type bit maps']
38
- }
39
-
40
- getRFCs() {
41
- return [4034]
42
- }
43
-
44
- getTypeId() {
45
- return 47
46
- }
47
-
48
39
  getCanonical() {
49
40
  return {
50
41
  owner: 'alfa.example.com.',
@@ -60,7 +51,7 @@ export default class NSEC extends RR {
60
51
 
61
52
  fromTinydns({ tinyline }) {
62
53
  const [owner, _typeId, rdata, ttl, ts, loc] = tinyline.slice(1).split(':')
63
- const binaryRdata = Buffer.from(TINYDNS.octalToChar(rdata), 'binary')
54
+ const binaryRdata = Uint8Array.from(TINYDNS.octalToChar(rdata), (c) => c.charCodeAt(0))
64
55
  const [nextDomain, _escapedLen, binaryLen] = TINYDNS.unpackDomainName(rdata)
65
56
 
66
57
  return new NSEC({
@@ -68,7 +59,7 @@ export default class NSEC extends RR {
68
59
  ttl: parseInt(ttl, 10),
69
60
  type: 'NSEC',
70
61
  'next domain': nextDomain,
71
- 'type bit maps': binaryRdata.slice(binaryLen).toString(),
62
+ 'type bit maps': new TextDecoder().decode(binaryRdata.subarray(binaryLen)),
72
63
  timestamp: ts,
73
64
  location: loc?.trim() ?? '',
74
65
  })
@@ -87,6 +78,19 @@ export default class NSEC extends RR {
87
78
  })
88
79
  }
89
80
 
81
+ fromWire({ owner, cls, ttl, rdata }) {
82
+ const { fqdn: nextDomain, end } = this.wireUnpackDomain(rdata, 0)
83
+ const typeBitMaps = nsecBitmapToTypes(rdata.subarray(end))
84
+ return new NSEC({
85
+ owner,
86
+ ttl,
87
+ class: cls,
88
+ type: 'NSEC',
89
+ 'next domain': nextDomain,
90
+ 'type bit maps': typeBitMaps,
91
+ })
92
+ }
93
+
90
94
  /****** EXPORTERS *******/
91
95
 
92
96
  toTinydns() {
@@ -97,6 +101,73 @@ export default class NSEC extends RR {
97
101
  TINYDNS.escapeOctal(dataRe, this.get('type bit maps')),
98
102
  )
99
103
  }
104
+
105
+ getWireRdata() {
106
+ const nameBytes = this.wirePackDomain(this.get('next domain'))
107
+ const bitmapBytes = typesToNsecBitmap(this.get('type bit maps'))
108
+ const result = new Uint8Array(nameBytes.length + bitmapBytes.length)
109
+ result.set(nameBytes)
110
+ result.set(bitmapBytes, nameBytes.length)
111
+ return result
112
+ }
100
113
  }
101
114
 
102
115
  const removeParens = (a) => !['(', ')'].includes(a)
116
+
117
+ function nsecBitmapToTypes(bitmap) {
118
+ const DNS_TYPE_NAMES = Object.fromEntries(Object.entries(DNS_TYPE_IDS).map(([k, v]) => [v, k]))
119
+ const types = []
120
+ let pos = 0
121
+ while (pos + 2 <= bitmap.length) {
122
+ const windowNum = bitmap[pos]
123
+ const bitmapLen = bitmap[pos + 1]
124
+ pos += 2
125
+ for (let i = 0; i < bitmapLen; i++) {
126
+ const byte = bitmap[pos + i]
127
+ for (let bit = 0; bit < 8; bit++) {
128
+ if (byte & (0x80 >> bit)) {
129
+ const typeId = windowNum * 256 + i * 8 + bit
130
+ types.push(DNS_TYPE_NAMES[typeId] ?? `TYPE${typeId}`)
131
+ }
132
+ }
133
+ }
134
+ pos += bitmapLen
135
+ }
136
+ return types.join(' ')
137
+ }
138
+
139
+ function typesToNsecBitmap(typeNamesStr) {
140
+ const typeIds = typeNamesStr
141
+ .trim()
142
+ .split(/\s+/)
143
+ .map((t) => {
144
+ if (/^TYPE\d+$/i.test(t)) return parseInt(t.slice(4), 10)
145
+ return DNS_TYPE_IDS[t.toUpperCase()]
146
+ })
147
+ .filter((id) => id !== undefined && id >= 0)
148
+
149
+ const windows = new Map()
150
+ for (const id of typeIds) {
151
+ const w = Math.floor(id / 256)
152
+ if (!windows.has(w)) windows.set(w, [])
153
+ windows.get(w).push(id % 256)
154
+ }
155
+
156
+ const blocks = []
157
+ for (const [wNum, bits] of [...windows.entries()].sort((a, b) => a[0] - b[0])) {
158
+ const maxBit = Math.max(...bits)
159
+ const bitmapLen = Math.floor(maxBit / 8) + 1
160
+ const bitmap = new Uint8Array(bitmapLen)
161
+ for (const b of bits) bitmap[Math.floor(b / 8)] |= 0x80 >> (b % 8)
162
+ blocks.push(new Uint8Array([wNum, bitmapLen, ...bitmap]))
163
+ }
164
+
165
+ const total = blocks.reduce((s, b) => s + b.length, 0)
166
+ const result = new Uint8Array(total)
167
+ let pos = 0
168
+ for (const b of blocks) {
169
+ result.set(b, pos)
170
+ pos += b.length
171
+ }
172
+ return result
173
+ }
package/rr/nsec3.js CHANGED
@@ -3,6 +3,19 @@ import RR from '../rr.js'
3
3
  import * as TINYDNS from '../lib/tinydns.js'
4
4
 
5
5
  export default class NSEC3 extends RR {
6
+ static typeName = 'NSEC3'
7
+ static typeId = 50
8
+ static RFCs = [5155, 9077]
9
+ static rdataFields = [
10
+ 'hash algorithm',
11
+ 'flags',
12
+ 'iterations',
13
+ 'salt',
14
+ 'next hashed owner name',
15
+ 'type bit maps',
16
+ ]
17
+ static tags = ['dnssec']
18
+
6
19
  constructor(opts) {
7
20
  super(opts)
8
21
  if (opts === null) return
@@ -64,22 +77,6 @@ export default class NSEC3 extends RR {
64
77
  return 'Next Secure'
65
78
  }
66
79
 
67
- getTags() {
68
- return ['dnssec']
69
- }
70
-
71
- getRdataFields(arg) {
72
- return ['hash algorithm', 'flags', 'iterations', 'salt', 'next hashed owner name', 'type bit maps']
73
- }
74
-
75
- getRFCs() {
76
- return [5155, 9077]
77
- }
78
-
79
- getTypeId() {
80
- return 50
81
- }
82
-
83
80
  getCanonical() {
84
81
  return {
85
82
  owner: 'test.example.com.',
@@ -100,7 +97,10 @@ export default class NSEC3 extends RR {
100
97
  fromBind({ bindline }) {
101
98
  // test.example.com. 3600 IN NSEC3 1 1 12 aabbccdd (2vptu5timamqttgl4luu9kg21e0aor3s A RRSIG)
102
99
  const [owner, ttl, c, type, ha, flags, iterations, salt] = bindline.split(/\s+/)
103
- const rdata = bindline.split(/\(|\)/)[1]
100
+ // rdata may be parenthesized or inline
101
+ const rdataStr = bindline.includes('(')
102
+ ? bindline.split(/\(|\)/)[1]
103
+ : bindline.split(/\s+/).slice(8).join(' ')
104
104
 
105
105
  return new NSEC3({
106
106
  owner,
@@ -111,8 +111,8 @@ export default class NSEC3 extends RR {
111
111
  flags: parseInt(flags, 10),
112
112
  iterations: parseInt(iterations, 10),
113
113
  salt,
114
- 'next hashed owner name': rdata.split(/\s+/)[0],
115
- 'type bit maps': rdata.split(/\s+/).slice(1).join(' '),
114
+ 'next hashed owner name': rdataStr.trim().split(/\s+/)[0],
115
+ 'type bit maps': rdataStr.trim().split(/\s+/).slice(1).join('\t'),
116
116
  })
117
117
  }
118
118
 
@@ -120,11 +120,12 @@ export default class NSEC3 extends RR {
120
120
  const [fqdn, n, rdata, ttl, ts, loc] = tinyline.slice(1).split(':')
121
121
  if (n != 50) this.throwHelp('NSEC3 fromTinydns, invalid n')
122
122
 
123
- const bytes = Buffer.from(TINYDNS.octalToChar(rdata), 'binary')
123
+ const bytes = Uint8Array.from(TINYDNS.octalToChar(rdata), (c) => c.charCodeAt(0))
124
+ const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
124
125
 
125
- const hashAlgorithm = bytes.readUInt8(0)
126
- const flags = bytes.readUInt8(1)
127
- const iterations = bytes.readUInt16BE(2)
126
+ const hashAlgorithm = bytes[0]
127
+ const flags = bytes[1]
128
+ const iterations = dv.getUint16(2)
128
129
 
129
130
  // The remaining bytes in the buffer contain:
130
131
  // Salt Length (1 octet)
@@ -148,6 +149,23 @@ export default class NSEC3 extends RR {
148
149
  })
149
150
  }
150
151
 
152
+ fromWire({ owner, cls, ttl, rdata }) {
153
+ const dv = new DataView(rdata.buffer, rdata.byteOffset)
154
+ const { salt, nextHashedOwnerName, typeBitMaps } = parseNSEC3Buffer(rdata)
155
+ return new NSEC3({
156
+ owner,
157
+ ttl,
158
+ class: cls,
159
+ type: 'NSEC3',
160
+ 'hash algorithm': rdata[0],
161
+ flags: rdata[1],
162
+ iterations: dv.getUint16(2),
163
+ salt,
164
+ 'next hashed owner name': nextHashedOwnerName,
165
+ 'type bit maps': typeBitMaps,
166
+ })
167
+ }
168
+
151
169
  /****** EXPORTERS *******/
152
170
 
153
171
  toBind(zone_opts) {
@@ -173,15 +191,33 @@ export default class NSEC3 extends RR {
173
191
  TINYDNS.escapeOctal(dataRe, this.get('type bit maps')),
174
192
  )
175
193
  }
194
+
195
+ getWireRdata() {
196
+ const tail = `${this.get('salt')}${this.get('next hashed owner name')}${this.get('type bit maps')}`
197
+ const tailBytes = new TextEncoder().encode(tail)
198
+
199
+ const totalLen = 4 + tailBytes.length
200
+ const bytes = new Uint8Array(totalLen)
201
+ const dv = new DataView(bytes.buffer, bytes.byteOffset)
202
+
203
+ let pos = 0
204
+ bytes[pos++] = this.get('hash algorithm')
205
+ bytes[pos++] = this.get('flags')
206
+ dv.setUint16(pos, this.get('iterations'))
207
+ pos += 2
208
+ bytes.set(tailBytes, pos)
209
+
210
+ return bytes
211
+ }
176
212
  }
177
213
 
178
214
  function parseNSEC3Buffer(bytes) {
179
- // bytes is a Buffer containing the full RDATA binary (hash alg, flags, iterations, then ASCII salt + next-hashed + type bit maps)
215
+ // bytes is a Uint8Array containing the full RDATA binary (hash alg, flags, iterations, then ASCII salt + next-hashed + type bit maps)
180
216
  // Start after the first 4 bytes (hash alg, flags, iterations)
181
- const rest = bytes.slice(4).toString('utf8')
217
+ const rest = new TextDecoder().decode(bytes.subarray(4))
182
218
 
183
219
  // determine expected next hashed owner name length from hash algorithm
184
- const hashAlgorithm = bytes.readUInt8(0)
220
+ const hashAlgorithm = bytes[0]
185
221
  // common mapping: algorithm 1 => SHA-1 => 20 bytes => base32 length 32
186
222
  const expectedLen = hashAlgorithm === 1 ? 32 : hashAlgorithm === 2 ? 52 : 32
187
223
 
package/rr/nsec3param.js CHANGED
@@ -1,7 +1,14 @@
1
1
  import RR from '../rr.js'
2
2
  import * as TINYDNS from '../lib/tinydns.js'
3
+ import * as BINARY from '../lib/binary.js'
3
4
 
4
5
  export default class NSEC3PARAM extends RR {
6
+ static typeName = 'NSEC3PARAM'
7
+ static typeId = 51
8
+ static RFCs = [5155]
9
+ static rdataFields = ['hash algorithm', 'flags', 'iterations', 'salt']
10
+ static tags = ['dnssec']
11
+
5
12
  constructor(opts) {
6
13
  super(opts)
7
14
  if (opts === null) return
@@ -57,22 +64,6 @@ export default class NSEC3PARAM extends RR {
57
64
  return 'Next Secure Parameters'
58
65
  }
59
66
 
60
- getTags() {
61
- return ['dnssec']
62
- }
63
-
64
- getRdataFields(arg) {
65
- return ['hash algorithm', 'flags', 'iterations', 'salt']
66
- }
67
-
68
- getRFCs() {
69
- return [5155]
70
- }
71
-
72
- getTypeId() {
73
- return 51
74
- }
75
-
76
67
  getCanonical() {
77
68
  return {
78
69
  owner: 'example.com.',
@@ -88,62 +79,74 @@ export default class NSEC3PARAM extends RR {
88
79
 
89
80
  /****** IMPORTERS *******/
90
81
 
91
- fromBind({ bindline }) {
92
- // test.example.com 3600 IN NSEC3PARAM <hash> <flags> <iterations> <salt>
93
- // Example: test.example.com. 3600 IN NSEC3PARAM 1 1 12 aabbccdd
94
- const [owner, ttl, c, type, ha, flags, iterations, salt] = bindline.split(/\s+/)
95
- return new NSEC3PARAM({
96
- owner,
97
- ttl: parseInt(ttl, 10),
98
- class: c,
99
- type: type,
100
- 'hash algorithm': parseInt(ha, 10),
101
- flags: parseInt(flags, 10),
102
- iterations: parseInt(iterations, 10),
103
- salt: salt,
104
- })
105
- }
106
-
107
82
  fromTinydns({ tinyline }) {
108
83
  // RDATA format: Hash Algorithm (3 octal chars) + Flags (3 octal chars) + Iterations (6 octal chars) + Salt (escaped hex string)
109
- const [owner, _typeId, rd, ttl, ts, loc] = tinyline.slice(1).split(':')
110
- if (rd.length < 4) {
111
- this.throwHelp(`NSEC3PARAM: RDATA too short: ${rd}`)
84
+ const { owner, typeId, rdata, ttl, timestamp, location } = this.parseTinydnsLine(tinyline)
85
+ if (typeId != this.getTypeId()) this.throwHelp('NSEC3PARAM fromTinydns, invalid n')
86
+ if (rdata.length < 4) {
87
+ this.throwHelp(`NSEC3PARAM: RDATA too short: ${rdata}`)
112
88
  }
113
89
 
114
90
  // rd may contain actual binary characters (from JS string '\\001' -> char 0x01),
115
- // so convert via octalToChar and read bytes from a Buffer for robust parsing.
116
- const bytes = Buffer.from(TINYDNS.octalToChar(rd), 'binary')
91
+ // so convert via octalToChar and read bytes from a Uint8Array for robust parsing.
92
+ const bytes = TINYDNS.octalRdataToBytes(rdata)
117
93
 
118
94
  return new NSEC3PARAM({
119
- owner: this.fullyQualify(owner),
120
- ttl: parseInt(ttl, 10),
95
+ owner,
96
+ ttl,
121
97
  type: 'NSEC3PARAM',
122
- 'hash algorithm': bytes.readUInt8(0),
123
- flags: bytes.readUInt8(1),
124
- iterations: bytes.readUInt16BE(2),
125
- salt: bytes.slice(4).toString('utf8'),
126
- timestamp: ts,
127
- location: loc?.trim() ?? '',
98
+ 'hash algorithm': bytes[0],
99
+ flags: bytes[1],
100
+ iterations: (bytes[2] << 8) | bytes[3],
101
+ salt: bytes[4] === 0 ? '-' : BINARY.bytesToHex(bytes.subarray(5, 5 + bytes[4])),
102
+ timestamp,
103
+ location,
128
104
  })
129
105
  }
130
106
 
131
- /****** EXPORTERS *******/
132
-
133
- toBind(zone_opts) {
134
- // Example: test.example.com. 3600 IN NSEC3PARAM 1 1 12 aabbccdd
135
- return `${this.getFQDN('owner', zone_opts)} ${this.get('ttl')} ${this.get('class')} NSEC3PARAM ${this.get('hash algorithm')} ${this.get('flags')} ${this.get('iterations')} ${this.get('salt')}
136
- `
107
+ fromWire({ owner, cls, ttl, rdata }) {
108
+ const dv = new DataView(rdata.buffer, rdata.byteOffset)
109
+ const saltLen = rdata[4]
110
+ const salt = saltLen === 0 ? '-' : BINARY.bytesToHex(rdata.subarray(5, 5 + saltLen))
111
+ return new NSEC3PARAM({
112
+ owner,
113
+ ttl,
114
+ class: cls,
115
+ type: 'NSEC3PARAM',
116
+ 'hash algorithm': rdata[0],
117
+ flags: rdata[1],
118
+ iterations: dv.getUint16(2),
119
+ salt,
120
+ })
137
121
  }
138
122
 
123
+ /****** EXPORTERS *******/
124
+
139
125
  toTinydns() {
140
- const dataRe = new RegExp(/[\r\n\t:\\/]/, 'g')
126
+ const salt = this.get('salt')
127
+ const saltOctal =
128
+ salt === '-' ? TINYDNS.UInt8toOctal(0) : TINYDNS.UInt8toOctal(salt.length / 2) + TINYDNS.packHex(salt)
141
129
 
142
130
  return this.getTinydnsGeneric(
143
131
  TINYDNS.UInt8toOctal(this.get('hash algorithm')) +
144
132
  TINYDNS.UInt8toOctal(this.get('flags')) +
145
133
  TINYDNS.UInt16toOctal(this.get('iterations')) +
146
- TINYDNS.escapeOctal(dataRe, this.get('salt')),
134
+ saltOctal,
147
135
  )
148
136
  }
137
+
138
+ getWireRdata() {
139
+ const salt = this.get('salt')
140
+ const saltBytes = salt === '-' ? new Uint8Array(0) : BINARY.hexToBytes(salt)
141
+ const bytes = new Uint8Array(4 + 1 + saltBytes.length)
142
+ const dv = new DataView(bytes.buffer, bytes.byteOffset)
143
+
144
+ bytes[0] = this.get('hash algorithm')
145
+ bytes[1] = this.get('flags')
146
+ dv.setUint16(2, this.get('iterations'))
147
+ bytes[4] = saltBytes.length
148
+ bytes.set(saltBytes, 5)
149
+
150
+ return bytes
151
+ }
149
152
  }
package/rr/nxt.js CHANGED
@@ -1,8 +1,15 @@
1
1
  import RR from '../rr.js'
2
2
 
3
3
  import * as TINYDNS from '../lib/tinydns.js'
4
+ import { DNS_TYPE_IDS } from '../lib/binary.js'
4
5
 
5
6
  export default class NXT extends RR {
7
+ static typeName = 'NXT'
8
+ static typeId = 30
9
+ static RFCs = [2065]
10
+ static rdataFields = ['next domain', 'type bit map']
11
+ static tags = ['obsolete']
12
+
6
13
  constructor(opts) {
7
14
  super(opts)
8
15
  if (opts === null) return
@@ -29,22 +36,6 @@ export default class NXT extends RR {
29
36
  return 'Next Secure'
30
37
  }
31
38
 
32
- getTags() {
33
- return ['deprecated']
34
- }
35
-
36
- getRdataFields(arg) {
37
- return ['next domain', 'type bit map']
38
- }
39
-
40
- getRFCs() {
41
- return [2065]
42
- }
43
-
44
- getTypeId() {
45
- return 30
46
- }
47
-
48
39
  getCanonical() {
49
40
  return {
50
41
  owner: 'big.example.com.',
@@ -62,7 +53,7 @@ export default class NXT extends RR {
62
53
  const [owner, n, rdata, ttl, ts, loc] = tinyline.slice(1).split(':')
63
54
  if (parseInt(n, 10) !== this.getTypeId()) this.throwHelp('NXT fromTinydns, invalid n')
64
55
 
65
- const binaryRdata = Buffer.from(TINYDNS.octalToChar(rdata), 'binary')
56
+ const binaryRdata = Uint8Array.from(TINYDNS.octalToChar(rdata), (c) => c.charCodeAt(0))
66
57
  const [nextDomain, _escapedLen, binaryLen] = TINYDNS.unpackDomainName(rdata)
67
58
 
68
59
  return new NXT({
@@ -70,7 +61,7 @@ export default class NXT extends RR {
70
61
  ttl: parseInt(ttl, 10),
71
62
  type: 'NXT',
72
63
  'next domain': nextDomain,
73
- 'type bit map': binaryRdata.slice(binaryLen).toString(),
64
+ 'type bit map': new TextDecoder().decode(binaryRdata.subarray(binaryLen)),
74
65
  timestamp: ts,
75
66
  location: loc?.trim() ?? '',
76
67
  })
@@ -89,6 +80,19 @@ export default class NXT extends RR {
89
80
  })
90
81
  }
91
82
 
83
+ fromWire({ owner, cls, ttl, rdata }) {
84
+ const { fqdn: nextDomain, end } = this.wireUnpackDomain(rdata, 0)
85
+ const typeBitMap = nxtBitmapToTypes(rdata.subarray(end))
86
+ return new NXT({
87
+ owner,
88
+ ttl,
89
+ class: cls,
90
+ type: 'NXT',
91
+ 'next domain': nextDomain,
92
+ 'type bit map': typeBitMap,
93
+ })
94
+ }
95
+
92
96
  /****** EXPORTERS *******/
93
97
 
94
98
  toTinydns() {
@@ -98,6 +102,41 @@ export default class NXT extends RR {
98
102
  TINYDNS.packDomainName(this.get('next domain')) + TINYDNS.escapeOctal(dataRe, this.get('type bit map')),
99
103
  )
100
104
  }
105
+
106
+ getWireRdata() {
107
+ const nameBytes = this.wirePackDomain(this.get('next domain'))
108
+ const bitmapBytes = typesToNxtBitmap(this.get('type bit map'))
109
+ const result = new Uint8Array(nameBytes.length + bitmapBytes.length)
110
+ result.set(nameBytes)
111
+ result.set(bitmapBytes, nameBytes.length)
112
+ return result
113
+ }
101
114
  }
102
115
 
103
116
  const removeParens = (a) => !['(', ')'].includes(a)
117
+
118
+ function nxtBitmapToTypes(bitmap) {
119
+ const DNS_TYPE_NAMES = Object.fromEntries(Object.entries(DNS_TYPE_IDS).map(([k, v]) => [v, k]))
120
+ const types = []
121
+ for (let i = 0; i < bitmap.length; i++) {
122
+ const byte = bitmap[i]
123
+ for (let bit = 0; bit < 8; bit++) {
124
+ if (byte & (0x80 >> bit)) {
125
+ const typeId = i * 8 + bit
126
+ types.push(DNS_TYPE_NAMES[typeId] ?? `TYPE${typeId}`)
127
+ }
128
+ }
129
+ }
130
+ return types.join(' ')
131
+ }
132
+
133
+ function typesToNxtBitmap(typeNamesStr) {
134
+ const bitmap = new Uint8Array(16)
135
+ for (const name of typeNamesStr.trim().split(/\s+/)) {
136
+ const id = /^TYPE\d+$/i.test(name) ? parseInt(name.slice(4), 10) : DNS_TYPE_IDS[name.toUpperCase()]
137
+ if (id !== undefined && id < 128) bitmap[Math.floor(id / 8)] |= 0x80 >> (id % 8)
138
+ }
139
+ let len = bitmap.length
140
+ while (len > 0 && bitmap[len - 1] === 0) len--
141
+ return bitmap.slice(0, len)
142
+ }
package/rr/openpgpkey.js CHANGED
@@ -1,13 +1,21 @@
1
1
  import RR from '../rr.js'
2
2
  import * as TINYDNS from '../lib/tinydns.js'
3
+ import * as BINARY from '../lib/binary.js'
3
4
 
4
5
  export default class OPENPGPKEY extends RR {
6
+ static typeName = 'OPENPGPKEY'
7
+ static typeId = 61
8
+ static RFCs = [4880, 7929]
9
+ static rdataFields = [['public key', 'base64']]
10
+ static tags = ['security']
11
+
5
12
  constructor(opts) {
6
13
  super(opts)
7
14
  }
8
15
 
9
16
  /****** Resource record specific setters *******/
10
17
  setPublicKey(val) {
18
+ this.isBase64('OPENPGPKEY', 'public key', val)
11
19
  this.set('public key', val)
12
20
  }
13
21
 
@@ -15,29 +23,14 @@ export default class OPENPGPKEY extends RR {
15
23
  return 'OpenPGP Public Key'
16
24
  }
17
25
 
18
- getTags() {
19
- return ['security']
20
- }
21
-
22
- getRdataFields() {
23
- return ['public key']
24
- }
25
-
26
- getRFCs() {
27
- return [4880, 7929]
28
- }
29
-
30
- getTypeId() {
31
- return 61
32
- }
33
-
34
26
  getCanonical() {
35
27
  return {
36
28
  owner: 'matt.example.com.',
37
29
  ttl: 3600,
38
30
  class: 'IN',
39
31
  type: 'OPENPGPKEY',
40
- 'public key': 'mQINBFY...',
32
+ 'public key':
33
+ 'AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwIXAqcOTiW7iHnQt5hwVAAAAA==',
41
34
  }
42
35
  }
43
36
 
@@ -50,35 +43,36 @@ export default class OPENPGPKEY extends RR {
50
43
  if (!match) this.throwHelp(`unable to parse OPENPGPKEY: ${bindline}`)
51
44
 
52
45
  const { owner, ttl, class: c, type, publickey } = match.groups
46
+ const keyStr = publickey.trim().replace(/\s+/g, '')
53
47
 
54
48
  return new OPENPGPKEY({
55
49
  owner,
56
50
  ttl: parseInt(ttl, 10),
57
51
  class: c,
58
52
  type: type,
59
- 'public key': publickey.trim(),
53
+ 'public key': keyStr,
60
54
  })
61
55
  }
62
56
 
63
57
  fromTinydns({ tinyline }) {
64
- const [owner, _typeId, rd, ttl, ts, loc] = tinyline.slice(1).split(':')
58
+ const { owner, typeId, rdata, ttl, timestamp, location } = this.parseTinydnsLine(tinyline)
59
+ if (typeId != this.getTypeId()) this.throwHelp('OPENPGPKEY fromTinydns, invalid n')
65
60
  return new OPENPGPKEY({
66
- owner: this.fullyQualify(owner),
67
- ttl: parseInt(ttl, 10),
61
+ owner,
62
+ ttl,
68
63
  type: 'OPENPGPKEY',
69
- 'public key': Buffer.from(TINYDNS.unescapeOctal(rd), 'base64').toString('utf-8'),
70
- timestamp: ts,
71
- location: loc?.trim() ?? '',
64
+ 'public key': TINYDNS.octalToBase64(rdata),
65
+ timestamp,
66
+ location,
72
67
  })
73
68
  }
74
69
 
75
70
  /****** EXPORTERS *******/
76
71
  toTinydns() {
77
- const dataRe = new RegExp(/[\r\n\t:\\/]/, 'g')
78
- const escapedPublicKey = TINYDNS.escapeOctal(
79
- dataRe,
80
- Buffer.from(this.get('public key'), 'utf-8').toString('base64'),
81
- )
82
- return this.getTinydnsGeneric(escapedPublicKey)
72
+ return this.getTinydnsGeneric(TINYDNS.base64toOctal(this.get('public key')))
73
+ }
74
+
75
+ getWireRdata() {
76
+ return BINARY.base64ToBytes(this.get('public key'))
83
77
  }
84
78
  }