@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/tsig.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'
4
+ import * as WIRE from '../lib/wire.js'
3
5
 
4
6
  export default class TSIG extends RR {
7
+ static typeName = 'TSIG'
8
+ static typeId = 250
9
+ static RFCs = [2845, 8945]
10
+ static rdataFields = ['algorithm name', 'time signed', 'fudge', 'mac', 'original id', 'error', 'other']
11
+
5
12
  constructor(opts) {
6
13
  super(opts)
7
14
  if (opts === null) return
@@ -13,18 +20,6 @@ export default class TSIG extends RR {
13
20
  return 'Transaction Signature'
14
21
  }
15
22
 
16
- getRdataFields(arg) {
17
- return ['algorithm name', 'time signed', 'fudge', 'mac', 'original id', 'error', 'other']
18
- }
19
-
20
- getRFCs() {
21
- return [2845, 8945]
22
- }
23
-
24
- getTypeId() {
25
- return 250
26
- }
27
-
28
23
  getCanonical() {
29
24
  return {
30
25
  owner: 'test.example.',
@@ -114,22 +109,23 @@ export default class TSIG extends RR {
114
109
  const algUnpacked = TINYDNS.unpackDomainName(rdata)
115
110
  const algBinaryLen = algUnpacked[2]
116
111
 
117
- const bytes = Buffer.from(TINYDNS.octalToChar(rdata), 'binary')
112
+ const bytes = Uint8Array.from(TINYDNS.octalToChar(rdata), (c) => c.charCodeAt(0))
113
+ const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
118
114
  let bpos = algBinaryLen
119
115
 
120
- const timeSigned = bytes.readUInt32BE(bpos)
116
+ const timeSigned = dv.getUint32(bpos)
121
117
  bpos += 4
122
- const fudge = bytes.readUInt16BE(bpos)
118
+ const fudge = dv.getUint16(bpos)
123
119
  bpos += 2
124
- const macSize = bytes.readUInt16BE(bpos)
120
+ const macSize = dv.getUint16(bpos)
125
121
  bpos += 2
126
- const mac = macSize > 0 ? bytes.slice(bpos, bpos + macSize).toString('hex') : ''
122
+ const mac = macSize > 0 ? BINARY.bytesToHex(bytes.subarray(bpos, bpos + macSize)) : ''
127
123
  bpos += macSize
128
- const originalId = bytes.readUInt16BE(bpos)
124
+ const originalId = dv.getUint16(bpos)
129
125
  bpos += 2
130
- const error = bytes.readUInt16BE(bpos)
126
+ const error = dv.getUint16(bpos)
131
127
  bpos += 2
132
- const other = bpos < bytes.length ? bytes.slice(bpos).toString() : ''
128
+ const other = bpos < bytes.length ? new TextDecoder().decode(bytes.subarray(bpos)) : ''
133
129
 
134
130
  return new TSIG({
135
131
  owner: this.fullyQualify(owner),
@@ -148,6 +144,38 @@ export default class TSIG extends RR {
148
144
  })
149
145
  }
150
146
 
147
+ fromWire({ owner, cls, ttl, rdata }) {
148
+ const { fqdn: algorithmName, end } = this.wireUnpackDomain(rdata, 0)
149
+ const dv = new DataView(rdata.buffer, rdata.byteOffset)
150
+ let pos = end
151
+ const timeSigned = dv.getUint32(pos)
152
+ pos += 4
153
+ const fudge = dv.getUint16(pos)
154
+ pos += 2
155
+ const macSize = dv.getUint16(pos)
156
+ pos += 2
157
+ const mac = macSize > 0 ? BINARY.bytesToHex(rdata.subarray(pos, pos + macSize)) : ''
158
+ pos += macSize
159
+ const originalId = dv.getUint16(pos)
160
+ pos += 2
161
+ const error = dv.getUint16(pos)
162
+ pos += 2
163
+ const other = pos < rdata.length ? new TextDecoder().decode(rdata.subarray(pos)) : ''
164
+ return new TSIG({
165
+ owner,
166
+ ttl: 0,
167
+ class: 'ANY',
168
+ type: 'TSIG',
169
+ 'algorithm name': algorithmName,
170
+ 'time signed': timeSigned,
171
+ fudge,
172
+ mac,
173
+ 'original id': originalId,
174
+ error,
175
+ other,
176
+ })
177
+ }
178
+
151
179
  /****** EXPORTERS *******/
152
180
  toBind(zone_opts) {
153
181
  const mac = this.get('mac') ?? ''
@@ -172,19 +200,54 @@ export default class TSIG extends RR {
172
200
  )
173
201
  }
174
202
 
203
+ getWireRdata() {
204
+ const algWire = WIRE.wirePackDomain(this.get('algorithm name') || '')
205
+ const mac = this.get('mac') ?? ''
206
+ const macBytes = mac.length > 0 ? BINARY.hexToBytes(mac) : new Uint8Array()
207
+ const other = this.get('other') ?? ''
208
+ const otherBytes = other.length > 0 ? new TextEncoder().encode(other) : new Uint8Array()
209
+
210
+ const bytes = new Uint8Array(algWire.length + 4 + 2 + 2 + macBytes.length + 2 + 2 + otherBytes.length)
211
+ const dv = new DataView(bytes.buffer, bytes.byteOffset)
212
+ let pos = 0
213
+
214
+ bytes.set(algWire, pos)
215
+ pos += algWire.length
216
+ dv.setUint32(pos, this.get('time signed') ?? 0)
217
+ pos += 4
218
+ dv.setUint16(pos, this.get('fudge') ?? 0)
219
+ pos += 2
220
+ dv.setUint16(pos, macBytes.length)
221
+ pos += 2
222
+ if (macBytes.length > 0) {
223
+ bytes.set(macBytes, pos)
224
+ pos += macBytes.length
225
+ }
226
+ dv.setUint16(pos, this.get('original id') ?? 0)
227
+ pos += 2
228
+ dv.setUint16(pos, this.get('error') ?? 0)
229
+ pos += 2
230
+ if (otherBytes.length > 0) bytes.set(otherBytes, pos)
231
+
232
+ return bytes
233
+ }
234
+
175
235
  toTinydns() {
176
- const dataRe = new RegExp(/[\r\n\t:\\/]/, 'g')
177
236
  const alg = this.get('algorithm name') || ''
237
+ const mac = this.get('mac') ?? ''
238
+ const macByteLen = mac.length > 0 ? mac.length / 2 : 0
178
239
 
179
240
  return this.getTinydnsGeneric(
180
241
  TINYDNS.packDomainName(alg) +
181
242
  TINYDNS.UInt32toOctal(this.get('time signed') ?? 0) +
182
243
  TINYDNS.UInt16toOctal(this.get('fudge')) +
183
- TINYDNS.UInt16toOctal(this.get('mac size') ?? 0) +
184
- (this.get('mac size') > 0 ? TINYDNS.escapeOctal(dataRe, this.get('mac')) : '') +
244
+ TINYDNS.UInt16toOctal(macByteLen) +
245
+ (macByteLen > 0 ? TINYDNS.packHex(mac) : '') +
185
246
  TINYDNS.UInt16toOctal(this.get('original id') ?? 0) +
186
247
  TINYDNS.UInt16toOctal(this.get('error') ?? 0) +
187
- (this.get('other').length > 0 ? TINYDNS.escapeOctal(dataRe, this.get('other')) : ''),
248
+ (this.get('other').length > 0
249
+ ? TINYDNS.escapeOctal(new RegExp(/[\r\n\t:\\/]/, 'g'), this.get('other'))
250
+ : ''),
188
251
  )
189
252
  }
190
253
  }
package/rr/txt.js CHANGED
@@ -3,6 +3,13 @@ import RR from '../rr.js'
3
3
  import * as TINYDNS from '../lib/tinydns.js'
4
4
 
5
5
  export default class TXT extends RR {
6
+ static typeName = 'TXT'
7
+ static typeId = 16
8
+ static RFCs = [1035, 4408, 7208, 6376]
9
+ static tinydnsType = "'"
10
+ static rdataFields = [['data', 'charstrs']]
11
+ static tags = ['common']
12
+
6
13
  constructor(opts) {
7
14
  super(opts)
8
15
  }
@@ -16,22 +23,6 @@ export default class TXT extends RR {
16
23
  return 'Text'
17
24
  }
18
25
 
19
- getTags() {
20
- return ['common']
21
- }
22
-
23
- getRdataFields(arg) {
24
- return ['data']
25
- }
26
-
27
- getRFCs() {
28
- return [1035, 4408, 7208, 6376]
29
- }
30
-
31
- getTypeId() {
32
- return 16
33
- }
34
-
35
26
  getCanonical() {
36
27
  return {
37
28
  owner: 'example.com.',
@@ -82,26 +73,6 @@ export default class TXT extends RR {
82
73
  return [fqdn, s, ttl, ts, loc]
83
74
  }
84
75
 
85
- fromBind({ bindline }) {
86
- // test.example.com 3600 IN TXT "..."
87
- const regex = /^(?<owner>\S{1,255})\s+(?<ttl>\d{1,10})\s+(?<cls>IN)\s+(?<type>\w{3})\s+(?<rdata>\S.*)$/i
88
- const match = bindline.trim().match(regex)
89
- if (!match) this.throwHelp(`unable to parse TXT: ${bindline}`)
90
-
91
- const { owner, ttl, cls, type, rdata } = match.groups
92
-
93
- return new this.constructor({
94
- owner,
95
- ttl: parseInt(ttl, 10),
96
- class: cls,
97
- type: type.toUpperCase(),
98
- data: rdata
99
- .match(/"([^"]+?)"/g)
100
- .map((s) => s.replace(/^"|"$/g, ''))
101
- .join(''),
102
- })
103
- }
104
-
105
76
  /****** EXPORTERS *******/
106
77
  toBind(zone_opts) {
107
78
  return `${this.getPrefix(zone_opts)}\t"${asQuotedStrings(this.get('data'))}"\n`
@@ -127,38 +98,48 @@ export default class TXT extends RR {
127
98
  }
128
99
 
129
100
  function asQuotedStrings(data) {
130
- // BIND croaks when any string in the TXT RR data is longer than 255
101
+ // RFC 1035 character-strings are 255 bytes max; chunk by UTF-8 bytes,
102
+ // not JS chars, so non-ASCII TXT data doesn't overflow the 255-byte limit.
103
+ const enc = new TextEncoder()
104
+
131
105
  if (Array.isArray(data)) {
132
- let hasTooLong = false
133
- for (const str of data) {
134
- if (str.length > 255) hasTooLong = true
135
- }
136
- return hasTooLong
137
- ? data
138
- .join('')
139
- .match(/(.{1,255})/g)
140
- .join('" "')
141
- : data.join('" "')
106
+ const anyTooLong = data.some((s) => enc.encode(s).length > 255)
107
+ if (!anyTooLong) return data.join('" "')
108
+ return chunkByBytes(data.join(''), 255).join('" "')
142
109
  }
143
110
 
144
- if (data.length > 255) {
145
- return data.match(/(.{1,255})/g).join('" "')
146
- }
111
+ if (enc.encode(data).length <= 255) return data
112
+ return chunkByBytes(data, 255).join('" "')
113
+ }
147
114
 
148
- return data
115
+ function chunkByBytes(str, maxBytes) {
116
+ const bytes = new TextEncoder().encode(str)
117
+ const dec = new TextDecoder()
118
+ const chunks = []
119
+ let start = 0
120
+ while (start < bytes.length) {
121
+ let end = Math.min(start + maxBytes, bytes.length)
122
+ // back up to a UTF-8 codepoint boundary so decode() returns whole chars
123
+ while (end < bytes.length && (bytes[end] & 0xc0) === 0x80) end--
124
+ chunks.push(dec.decode(bytes.subarray(start, end)))
125
+ start = end
126
+ }
127
+ return chunks
149
128
  }
150
129
 
151
130
  function packStringWire(str) {
152
- const parts = str.match(/(.{1,255})/g)
153
- let len = 0
154
- for (const part of parts) len += part.length + 1
131
+ const encoded = new TextEncoder().encode(str)
132
+ if (encoded.length === 0) return new Uint8Array([0])
133
+
134
+ const chunks = []
135
+ for (let i = 0; i < encoded.length; i += 255) chunks.push(encoded.subarray(i, i + 255))
155
136
 
156
- const buf = Buffer.allocUnsafe(len)
137
+ const buf = new Uint8Array(encoded.length + chunks.length)
157
138
  let offset = 0
158
- for (const part of parts) {
159
- buf.writeUInt8(part.length, offset++)
160
- buf.write(part, offset, 'ascii')
161
- offset += part.length
139
+ for (const chunk of chunks) {
140
+ buf[offset++] = chunk.length
141
+ buf.set(chunk, offset)
142
+ offset += chunk.length
162
143
  }
163
144
  return buf
164
145
  }
package/rr/uri.js CHANGED
@@ -3,6 +3,15 @@ import RR from '../rr.js'
3
3
  import * as TINYDNS from '../lib/tinydns.js'
4
4
 
5
5
  export default class URI extends RR {
6
+ static typeName = 'URI'
7
+ static typeId = 256
8
+ static RFCs = [7553]
9
+ static rdataFields = [
10
+ ['priority', 'u16'],
11
+ ['weight', 'u16'],
12
+ ['target', 'qstr'],
13
+ ]
14
+
6
15
  constructor(opts) {
7
16
  super(opts)
8
17
  }
@@ -44,37 +53,11 @@ export default class URI extends RR {
44
53
  })
45
54
  }
46
55
 
47
- fromBind({ bindline }) {
48
- // test.example.com 3600 IN URI priority, weight, target
49
- const [owner, ttl, c, type, priority, weight, target] = bindline.split(/\s+/)
50
- return new URI({
51
- class: c,
52
- type: type,
53
- owner,
54
- priority: parseInt(priority, 10),
55
- weight: parseInt(weight, 10),
56
- target: target.replace(/^"|"$/g, ''),
57
- ttl: parseInt(ttl, 10),
58
- })
59
- }
60
-
61
56
  /****** MISC *******/
62
57
  getDescription() {
63
58
  return 'URI'
64
59
  }
65
60
 
66
- getRdataFields(arg) {
67
- return ['priority', 'weight', 'target']
68
- }
69
-
70
- getRFCs() {
71
- return [7553]
72
- }
73
-
74
- getTypeId() {
75
- return 256
76
- }
77
-
78
61
  getCanonical() {
79
62
  return {
80
63
  owner: 'www.example.com.',
@@ -87,11 +70,18 @@ export default class URI extends RR {
87
70
  }
88
71
  }
89
72
 
90
- getQuotedFields() {
91
- return ['target']
73
+ /****** EXPORTERS *******/
74
+
75
+ getWireRdata() {
76
+ const target = new TextEncoder().encode(this.get('target'))
77
+ const result = new Uint8Array(4 + target.length)
78
+ const dv = new DataView(result.buffer)
79
+ dv.setUint16(0, this.get('priority'))
80
+ dv.setUint16(2, this.get('weight'))
81
+ result.set(target, 4)
82
+ return result
92
83
  }
93
84
 
94
- /****** EXPORTERS *******/
95
85
  toTinydns() {
96
86
  const dataRe = new RegExp(/[\r\n\t:\\/]/, 'g')
97
87
  let rdata = ''
package/rr/wks.js CHANGED
@@ -2,7 +2,102 @@ import RR from '../rr.js'
2
2
 
3
3
  import * as TINYDNS from '../lib/tinydns.js'
4
4
 
5
+ const WELL_KNOWN_PORTS = {
6
+ echo: 7,
7
+ discard: 9,
8
+ systat: 11,
9
+ daytime: 13,
10
+ netstat: 15,
11
+ ftp_data: 20,
12
+ ftp: 21,
13
+ ssh: 22,
14
+ telnet: 23,
15
+ smtp: 25,
16
+ time: 37,
17
+ rlp: 39,
18
+ nameserver: 42,
19
+ nicname: 43,
20
+ domain: 53,
21
+ mtp: 57,
22
+ bootps: 67,
23
+ bootpc: 68,
24
+ tftp: 69,
25
+ gopher: 70,
26
+ rje: 77,
27
+ finger: 79,
28
+ http: 80,
29
+ link: 87,
30
+ supdup: 95,
31
+ hostnames: 101,
32
+ iso_tsap: 102,
33
+ csnet_ns: 105,
34
+ pop_2: 109,
35
+ pop3: 110,
36
+ sunrpc: 111,
37
+ auth: 113,
38
+ sftp: 115,
39
+ uucp_path: 117,
40
+ nntp: 119,
41
+ ntp: 123,
42
+ netbios_ns: 137,
43
+ netbios_dgm: 138,
44
+ netbios_ssn: 139,
45
+ imap: 143,
46
+ sql_net: 150,
47
+ snmp: 161,
48
+ snmp_trap: 162,
49
+ cmip_man: 163,
50
+ cmip_agent: 164,
51
+ xdmcp: 177,
52
+ nextstep: 178,
53
+ bgp: 179,
54
+ prospero: 191,
55
+ irc: 194,
56
+ smux: 199,
57
+ at_rtmp: 201,
58
+ at_nbp: 202,
59
+ at_echo: 204,
60
+ at_zis: 206,
61
+ qmtp: 209,
62
+ z3950: 210,
63
+ ipx: 213,
64
+ imap3: 220,
65
+ ulistproc: 372,
66
+ https: 443,
67
+ snpp: 444,
68
+ microsoft_ds: 445,
69
+ kpasswd: 464,
70
+ urd: 465,
71
+ saft: 487,
72
+ isakmp: 500,
73
+ exec: 512,
74
+ biff: 512,
75
+ login: 513,
76
+ who: 513,
77
+ cmd: 514,
78
+ syslog: 514,
79
+ printer: 515,
80
+ talk: 517,
81
+ ntalk: 518,
82
+ route: 520,
83
+ timed: 525,
84
+ tempo: 526,
85
+ courier: 530,
86
+ netnews: 532,
87
+ netwall: 533,
88
+ uucp: 540,
89
+ remotefs: 556,
90
+ nntps: 563,
91
+ ldap: 389,
92
+ }
93
+
5
94
  export default class WKS extends RR {
95
+ static typeName = 'WKS'
96
+ static typeId = 11
97
+ static RFCs = [883, 1035]
98
+ static rdataFields = ['address', 'protocol', 'bit map']
99
+ static tags = ['obsolete']
100
+
6
101
  constructor(opts) {
7
102
  super(opts)
8
103
  }
@@ -29,22 +124,6 @@ export default class WKS extends RR {
29
124
  return 'Well Known Service'
30
125
  }
31
126
 
32
- getTags() {
33
- return ['obsolete']
34
- }
35
-
36
- getRdataFields(arg) {
37
- return ['address', 'protocol', 'bit map']
38
- }
39
-
40
- getRFCs() {
41
- return [883, 1035]
42
- }
43
-
44
- getTypeId() {
45
- return 11
46
- }
47
-
48
127
  getCanonical() {
49
128
  return {
50
129
  owner: 'host.example.com.',
@@ -77,14 +156,12 @@ export default class WKS extends RR {
77
156
  fromTinydns({ tinyline }) {
78
157
  const [owner, _typeId, rdata, ttl, ts, loc] = tinyline.slice(1).split(':')
79
158
 
80
- const binary = Buffer.from(TINYDNS.octalToChar(rdata), 'binary')
81
- const address = [binary.readUInt8(0), binary.readUInt8(1), binary.readUInt8(2), binary.readUInt8(3)].join(
82
- '.',
83
- )
84
- const protoNum = binary.readUInt8(4)
159
+ const binary = Uint8Array.from(TINYDNS.octalToChar(rdata), (c) => c.charCodeAt(0))
160
+ const address = [binary[0], binary[1], binary[2], binary[3]].join('.')
161
+ const protoNum = binary[4]
85
162
  const protoMap = { 6: 'TCP', 17: 'UDP' }
86
163
  const protocol = protoMap[protoNum] ?? protoNum
87
- const bitmap = binary.slice(5).toString()
164
+ const bitmap = new TextDecoder().decode(binary.subarray(5))
88
165
 
89
166
  return new WKS({
90
167
  owner: this.fullyQualify(owner),
@@ -98,6 +175,33 @@ export default class WKS extends RR {
98
175
  })
99
176
  }
100
177
 
178
+ fromWire({ owner, cls, ttl, rdata }) {
179
+ const address = [...rdata.subarray(0, 4)].join('.')
180
+ const protoNum = rdata[4]
181
+ const protoMap = { 6: 'TCP', 17: 'UDP' }
182
+ const protocol = protoMap[protoNum] ?? String(protoNum)
183
+ const PORT_NAMES = Object.fromEntries(Object.entries(WELL_KNOWN_PORTS).map(([k, v]) => [v, k]))
184
+ const bitmap = rdata.subarray(5)
185
+ const ports = []
186
+ for (let i = 0; i < bitmap.length; i++) {
187
+ for (let bit = 0; bit < 8; bit++) {
188
+ if (bitmap[i] & (0x80 >> bit)) {
189
+ const port = i * 8 + bit
190
+ ports.push(PORT_NAMES[port] ?? String(port))
191
+ }
192
+ }
193
+ }
194
+ return new WKS({
195
+ owner,
196
+ ttl,
197
+ class: cls,
198
+ type: 'WKS',
199
+ address,
200
+ protocol,
201
+ 'bit map': ports.join(' '),
202
+ })
203
+ }
204
+
101
205
  /****** EXPORTERS *******/
102
206
 
103
207
  toTinydns() {
@@ -111,4 +215,32 @@ export default class WKS extends RR {
111
215
  TINYDNS.escapeOctal(dataRe, this.get('bit map')),
112
216
  )
113
217
  }
218
+
219
+ getWireRdata() {
220
+ const protoMap = { TCP: 6, UDP: 17, 6: 6, 17: 17 }
221
+ const addrBytes = this.get('address').split('.').map(Number)
222
+ const protoNum = protoMap[this.get('protocol')]
223
+
224
+ const portNums = this.get('bit map')
225
+ .trim()
226
+ .split(/\s+/)
227
+ .map((s) => {
228
+ if (/^\d+$/.test(s)) return parseInt(s, 10)
229
+ return WELL_KNOWN_PORTS[s.toLowerCase()]
230
+ })
231
+ .filter((p) => p !== undefined)
232
+
233
+ if (portNums.length === 0) return new Uint8Array([...addrBytes, protoNum])
234
+
235
+ const maxPort = Math.max(...portNums)
236
+ const bitmapLen = Math.floor(maxPort / 8) + 1
237
+ const bitmap = new Uint8Array(bitmapLen)
238
+ for (const port of portNums) bitmap[Math.floor(port / 8)] |= 0x80 >> (port % 8)
239
+
240
+ const result = new Uint8Array(5 + bitmapLen)
241
+ result.set(addrBytes)
242
+ result[4] = protoNum
243
+ result.set(bitmap, 5)
244
+ return result
245
+ }
114
246
  }