@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.js CHANGED
@@ -1,16 +1,31 @@
1
1
  import * as TINYDNS from './lib/tinydns.js'
2
2
 
3
- export default class RR extends Map {
4
- constructor(opts) {
5
- super()
3
+ const customInspect = Symbol.for('nodejs.util.inspect.custom')
4
+ import {
5
+ wireUnpackDomain,
6
+ wirePackDomain,
7
+ getWireRdata as wireGetRdata,
8
+ toWire as wireToWire,
9
+ fromWireBytes,
10
+ fromWireGeneric,
11
+ } from './lib/wire.js'
12
+ import {
13
+ parseBindLine as bindParseLine,
14
+ fromBind as bindFromGeneric,
15
+ toBind as bindToGeneric,
16
+ } from './lib/bind.js'
17
+
18
+ export default class RR {
19
+ static CLASSES = { IN: 1, CS: 2, CH: 3, HS: 4, NONE: 254, ANY: 255 }
20
+ static typeId
21
+ static RFCs = []
22
+ static tags = []
6
23
 
24
+ constructor(opts) {
7
25
  if (opts === null) return
8
26
 
9
27
  if (opts?.default) this.default = opts.default
10
28
 
11
- if (opts?.bindline) return this.fromBind(opts)
12
- if (opts?.tinyline) return this.fromTinydns(opts)
13
-
14
29
  // tinydns specific
15
30
  this.setLocation(opts?.location)
16
31
  this.setTimestamp(opts?.timestamp)
@@ -20,16 +35,86 @@ export default class RR extends Map {
20
35
  this.setTtl(opts?.ttl)
21
36
  this.setClass(opts?.class)
22
37
 
23
- for (const f of this.getFields('rdata')) {
38
+ for (const entry of this.constructor.rdataFields ?? []) {
39
+ const f = RR.fieldName(entry)
40
+ const fieldType = Array.isArray(entry) ? entry[1] : null
24
41
  const fnName = `set${this.ucFirst(f)}`
25
- if (this[fnName] === undefined) this.throwHelp(`Missing ${fnName} in class ${this.get('type')}`)
26
- this[fnName](opts?.[f])
42
+ if (typeof this[fnName] === 'function') {
43
+ this[fnName](opts?.[f])
44
+ } else if (fieldType) {
45
+ this.setTypedValue(fieldType, f, opts?.[f])
46
+ } else {
47
+ this.set(f, opts?.[f])
48
+ }
27
49
  }
28
50
 
29
51
  if (opts?.comment) this.set('comment', opts.comment)
30
52
  }
31
53
 
54
+ static fromBind(line, opts = {}) {
55
+ const instance = new this(null)
56
+ if (opts.default !== undefined) instance.default = opts.default
57
+ const parsed = this.parseBindLine(line)
58
+ if (!parsed) return null
59
+ return instance.fromBind({ ...opts, ...parsed, bindline: line })
60
+ }
61
+
62
+ fromBind(opts) {
63
+ return bindFromGeneric(this, opts)
64
+ }
65
+
66
+ static parseBindLine(line) {
67
+ return bindParseLine(line, RR.CLASSES)
68
+ }
69
+
70
+ static fromTinydns(line, opts = {}) {
71
+ const instance = new this(null)
72
+ if (opts.default !== undefined) instance.default = opts.default
73
+ return instance.fromTinydns({ ...opts, tinyline: line })
74
+ }
75
+
76
+ fromTinydns(opts) {
77
+ return TINYDNS.fromGeneric(this, opts)
78
+ }
79
+
80
+ static fromWire(wireBytes) {
81
+ return fromWireBytes(this, wireBytes, wireUnpackDomain)
82
+ }
83
+
84
+ fromWire(opts) {
85
+ return fromWireGeneric(this, opts)
86
+ }
87
+
88
+ static #reserved = ['__proto__', 'constructor', 'prototype']
89
+
90
+ get(key) {
91
+ if (RR.#reserved.includes(key)) throw new Error(`Invalid field name: ${key}`)
92
+ return this[key]
93
+ }
94
+
95
+ set(key, value) {
96
+ if (RR.#reserved.includes(key)) throw new Error(`Invalid field name: ${key}`)
97
+ this[key] = value
98
+ return this
99
+ }
100
+
101
+ toJSON() {
102
+ const fields = [...this.getFields(), 'location', 'timestamp', 'comment']
103
+ const obj = {}
104
+ for (const f of fields) {
105
+ const v = this.get(f)
106
+ if (v !== undefined) obj[f] = v
107
+ }
108
+ return obj
109
+ }
110
+
111
+ [customInspect](depth, options, nextInspect) {
112
+ // Returns a formatted string that looks like: A { ... }
113
+ return `${this.type} ${nextInspect(this.toJSON(), options)}`
114
+ }
115
+
32
116
  ucFirst(str) {
117
+ if (!str) return str
33
118
  return str
34
119
  .split(/\s/)
35
120
  .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
@@ -37,23 +122,15 @@ export default class RR extends Map {
37
122
  }
38
123
 
39
124
  setClass(c) {
40
- switch (c) {
41
- case 'IN': // 1
42
- case undefined:
43
- case null:
44
- case '':
45
- this.set('class', 'IN')
46
- break
47
- case 'CS': // 2
48
- case 'CH': // 3
49
- case 'HS': // 4
50
- case 'NONE': // 254
51
- case 'ANY': // 255
52
- this.set('class', c)
53
- break
54
- default:
55
- this.throwHelp(`invalid class ${c}`)
125
+ if ([undefined, null, ''].includes(c)) {
126
+ this.set('class', 'IN')
127
+ return
128
+ }
129
+ if (RR.CLASSES[c.toUpperCase()]) {
130
+ this.set('class', c.toUpperCase())
131
+ return
56
132
  }
133
+ this.throwHelp(`invalid class ${c}`)
57
134
  }
58
135
 
59
136
  setLocation(l) {
@@ -80,7 +157,7 @@ export default class RR extends Map {
80
157
  if (n.length < 1 || n.length > 255)
81
158
  this.throwHelp('Domain names must have 1-255 octets (characters): RFC 2181')
82
159
 
83
- this.isFullyQualified(this.constructor.name, 'owner', n)
160
+ this.isFullyQualified(this.constructor.typeName ?? this.constructor.name, 'owner', n)
84
161
  this.hasValidLabels(n)
85
162
 
86
163
  // wildcard records: RFC 1034, 4592
@@ -95,7 +172,7 @@ export default class RR extends Map {
95
172
  setTtl(t) {
96
173
  t = t ?? this.default?.ttl
97
174
  if (t === undefined) {
98
- if (['SOA', 'SSHPF'].includes(this.get('type'))) return
175
+ if (['SOA', 'SSHFP', 'RRSIG'].includes(this.get('type'))) return
99
176
  this.throwHelp('TTL is required, no default available')
100
177
  }
101
178
 
@@ -108,24 +185,23 @@ export default class RR extends Map {
108
185
  }
109
186
 
110
187
  setType(t) {
111
- switch (t) {
112
- case '':
113
- case undefined:
114
- this.throwHelp(`type is required`)
115
- }
188
+ if ([undefined, ''].includes(t)) t = this.constructor.typeName
189
+
190
+ if (t === undefined) this.throwHelp(`type is required`)
116
191
 
117
- if (t.toUpperCase() !== this.constructor.name)
118
- this.throwHelp(`type ${t} doesn't match ${this.constructor.name}`)
192
+ if (t.toUpperCase() !== this.constructor.typeName)
193
+ this.throwHelp(`type ${t} doesn't match ${this.constructor.typeName}`)
119
194
 
120
195
  this.set('type', t.toUpperCase())
121
196
  }
122
197
 
123
198
  throwHelp(e) {
124
- if (this.constructor.name === 'RR') throw new Error(e)
199
+ if (!this.constructor.typeName) throw new Error(e)
125
200
 
201
+ const typeName = this.constructor.typeName
126
202
  const example = this.getCanonical
127
- ? `Example ${this.constructor.name}:\n${JSON.stringify(this.getCanonical(), null, '\t')}\n\n`
128
- : `${this.constructor.name} records have the fields: ${this.getFields().join(', ')}\n\n`
203
+ ? `Example ${typeName}:\n${JSON.stringify(this.getCanonical(), null, '\t')}\n\n`
204
+ : `${typeName} records have the fields: ${this.getFields().join(', ')}\n\n`
129
205
 
130
206
  throw new Error(`${e}\n\n${example}${this.citeRFC()}\n`)
131
207
  }
@@ -169,8 +245,8 @@ export default class RR extends Map {
169
245
  }
170
246
 
171
247
  getQuoted(prop) {
172
- // if prop is not in quoted list, return bare
173
- if (!this.getQuotedFields().includes(prop)) return this.get(prop)
248
+ // if prop is not a quoted string field, return bare
249
+ if (!this.isQuotedField(prop)) return this.get(prop)
174
250
 
175
251
  // if it's already quoted, return as-is
176
252
  if (/['"]/.test(this.get(prop)[0])) return this.get(prop)
@@ -178,29 +254,49 @@ export default class RR extends Map {
178
254
  return `"${this.get(prop)}"` // add double quotes
179
255
  }
180
256
 
181
- getQuotedFields() {
182
- return []
257
+ static fieldName(entry) {
258
+ return Array.isArray(entry) ? entry[0] : entry
259
+ }
260
+
261
+ static fieldType(entry) {
262
+ return Array.isArray(entry) ? entry[1] : null
183
263
  }
184
264
 
185
265
  getRdataFields() {
186
- return []
266
+ return (this.constructor.rdataFields ?? []).map((e) => RR.fieldName(e))
187
267
  }
188
268
 
189
269
  getTags() {
190
- return []
270
+ return this.constructor.tags ?? []
271
+ }
272
+
273
+ getRFCs() {
274
+ return this.constructor.RFCs ?? []
275
+ }
276
+
277
+ getTypeId() {
278
+ const typeId = this.constructor.typeId
279
+ if (typeId === undefined) this.throwHelp(`${this.constructor.typeName}: missing static typeId`)
280
+ return typeId
281
+ }
282
+
283
+ static getTypeId() {
284
+ return this.typeId
191
285
  }
192
286
 
193
287
  getFields(arg) {
194
288
  const commonFields = ['owner', 'ttl', 'class', 'type']
195
289
  Object.freeze(commonFields)
196
290
 
291
+ const rdataFields = this.getRdataFields()
292
+
197
293
  switch (arg) {
198
294
  case 'common':
199
295
  return commonFields
200
296
  case 'rdata':
201
- return this.getRdataFields()
297
+ return rdataFields
202
298
  default:
203
- return commonFields.concat(this.getRdataFields())
299
+ return commonFields.concat(rdataFields)
204
300
  }
205
301
  }
206
302
 
@@ -249,45 +345,132 @@ export default class RR extends Map {
249
345
  }
250
346
 
251
347
  is8bitInt(type, field, value) {
252
- if (
253
- typeof value === 'number' &&
254
- parseInt(value, 10) === value && // assure integer
255
- value >= 0 &&
256
- value <= 255
257
- )
258
- return true
348
+ if (Number.isInteger(value) && value >= 0 && value <= 255) return true
259
349
 
260
350
  this.throwHelp(`${type} ${field} must be a 8-bit integer (in the range 0-255)`)
261
351
  }
262
352
 
263
353
  is16bitInt(type, field, value) {
264
- if (
265
- typeof value === 'number' &&
266
- parseInt(value, 10) === value && // assure integer
267
- value >= 0 &&
268
- value <= 65535
269
- )
270
- return true
354
+ if (Number.isInteger(value) && value >= 0 && value <= 65535) return true
271
355
 
272
356
  this.throwHelp(`${type} ${field} must be a 16-bit integer (in the range 0-65535)`)
273
357
  }
274
358
 
275
359
  is32bitInt(type, field, value) {
360
+ if (Number.isInteger(value) && value >= 0 && value <= 4294967295) return true
361
+
362
+ this.throwHelp(`${type} ${field} must be a 32-bit integer (in the range 0-4294967295)`)
363
+ }
364
+
365
+ isBase64(type, field, value) {
276
366
  if (
277
- typeof value === 'number' &&
278
- parseInt(value, 10) === value && // assure integer
279
- value >= 0 &&
280
- value <= 2147483647
367
+ typeof value === 'string' &&
368
+ value.length > 0 &&
369
+ value.length % 4 === 0 &&
370
+ /^[A-Za-z0-9+/]*={0,2}$/.test(value)
281
371
  )
282
372
  return true
283
373
 
284
- this.throwHelp(`${type} ${field} must be a 32-bit integer (in the range 0-2147483647)`)
374
+ this.throwHelp(`${type} ${field} must be a valid base64 string`)
285
375
  }
286
376
 
287
377
  isQuoted(val) {
288
378
  return /^["']/.test(val) && /["']$/.test(val)
289
379
  }
290
380
 
381
+ setFqdnValue(typeName, fieldName, val) {
382
+ if (!val) this.throwHelp(`${typeName}: ${fieldName} is required`)
383
+ if (this.isIPv4(val) || this.isIPv6(val))
384
+ this.throwHelp(`${typeName}: ${fieldName} must be a domain name`)
385
+ this.isFullyQualified(typeName, fieldName, val)
386
+ this.isValidHostname(typeName, fieldName, val)
387
+ this.set(fieldName, val.toLowerCase())
388
+ }
389
+
390
+ setTypedValue(type, fieldName, val) {
391
+ const typeName = this.constructor.typeName
392
+ switch (type) {
393
+ case 'u8':
394
+ this.is8bitInt(typeName, fieldName, val)
395
+ this.set(fieldName, parseInt(val, 10))
396
+ break
397
+ case 'u16':
398
+ this.is16bitInt(typeName, fieldName, val)
399
+ this.set(fieldName, parseInt(val, 10))
400
+ break
401
+ case 'certtype': {
402
+ if (val === undefined || val === null || val === '')
403
+ this.throwHelp(`${typeName}: ${fieldName} is required`)
404
+ if (typeof val === 'string' && !/^[0-9]+$/.test(val)) {
405
+ const certTypes = this.constructor.CERT_TYPES
406
+ if (!certTypes || !Object.hasOwn(certTypes, val)) {
407
+ this.throwHelp(`${typeName}: unknown cert type mnemonic: ${val}`)
408
+ }
409
+ this.set(fieldName, val)
410
+ break
411
+ }
412
+ this.is16bitInt(typeName, fieldName, val)
413
+ this.set(fieldName, parseInt(val, 10))
414
+ break
415
+ }
416
+ case 'u32':
417
+ this.is32bitInt(typeName, fieldName, val)
418
+ this.set(fieldName, parseInt(val, 10))
419
+ break
420
+ case 'fqdn':
421
+ this.setFqdnValue(typeName, fieldName, val)
422
+ break
423
+ case 'base64':
424
+ this.isBase64(typeName, fieldName, val)
425
+ this.set(fieldName, val)
426
+ break
427
+ case 'hex':
428
+ if (!/^[0-9a-fA-F]*$/.test(val)) this.throwHelp(`${typeName}: ${fieldName} must be hexadecimal`)
429
+ this.set(fieldName, val)
430
+ break
431
+ case 'str':
432
+ if (!val) this.throwHelp(`${typeName}: ${fieldName} is required`)
433
+ this.set(fieldName, val)
434
+ break
435
+ case 'qstr':
436
+ if (val === undefined || val === null) this.throwHelp(`${typeName}: ${fieldName} is required`)
437
+ this.set(fieldName, val)
438
+ break
439
+ case 'charstr': {
440
+ if (val === undefined || val === null) this.throwHelp(`${typeName}: ${fieldName} is required`)
441
+ const value = String(val)
442
+ const byteLen = new TextEncoder().encode(value).length
443
+ if (byteLen > 255) this.throwHelp(`${typeName}: ${fieldName} must be <=255 bytes`)
444
+ this.set(fieldName, value)
445
+ break
446
+ }
447
+ case 'qcharstr': {
448
+ if (val === undefined || val === null) this.throwHelp(`${typeName}: ${fieldName} is required`)
449
+ const value = String(val)
450
+ const byteLen = new TextEncoder().encode(value).length
451
+ if (byteLen > 255) this.throwHelp(`${typeName}: ${fieldName} must be <=255 bytes`)
452
+ this.set(fieldName, value)
453
+ break
454
+ }
455
+ case 'charstrs':
456
+ if (val === undefined || val === null) this.throwHelp(`${typeName}: ${fieldName} is required`)
457
+ this.set(fieldName, val)
458
+ break
459
+ case 'svcparams':
460
+ if (val === undefined || val === null) this.throwHelp(`${typeName}: ${fieldName} is required`)
461
+ this.set(fieldName, val)
462
+ break
463
+ case 'ipv4':
464
+ if (!this.isIPv4(val)) this.throwHelp(`${typeName}: ${fieldName} must be a valid IPv4 address`)
465
+ this.set(fieldName, val)
466
+ break
467
+ case 'ipv6':
468
+ if (!this.isIPv6(val)) this.throwHelp(`${typeName}: ${fieldName} must be a valid IPv6 address`)
469
+ this.set(fieldName, this.expandIPv6(val.toLowerCase())) // lower case: RFC 5952
470
+ break
471
+ }
472
+ }
473
+
291
474
  isFullyQualified(type, field, hostname) {
292
475
  if (hostname.endsWith('.')) return true
293
476
 
@@ -313,6 +496,10 @@ export default class RR extends Map {
313
496
  )
314
497
  }
315
498
 
499
+ expandIPv6(val, delimiter) {
500
+ return TINYDNS.expandIPv6(val, delimiter)
501
+ }
502
+
316
503
  compressIPv6(val) {
317
504
  // * RFC 5952
318
505
  // * 4.1. Leading zeros MUST be suppressed...A single 16-bit 0000 field MUST be represented as 0.
@@ -358,39 +545,55 @@ export default class RR extends Map {
358
545
  return `${head}::${tail}`
359
546
  }
360
547
 
361
- octalToBuffer(octalStr) {
362
- return Buffer.from(TINYDNS.octalToChar(octalStr), 'binary')
548
+ octalToUint8Array(octalStr) {
549
+ const str = TINYDNS.octalToChar(octalStr)
550
+ return Uint8Array.from(str, (c) => c.charCodeAt(0))
551
+ }
552
+
553
+ wireUnpackDomain(bytes, offset = 0) {
554
+ return wireUnpackDomain(bytes, offset)
363
555
  }
364
556
 
365
557
  wirePackDomain(fqdn) {
366
- return packDomainNameWire(fqdn)
558
+ return wirePackDomain(fqdn)
367
559
  }
368
560
 
369
561
  getWireRdata() {
370
- const line = this.toTinydns()
371
- if (!line.startsWith(':'))
372
- throw new Error(`${this.get('type')}: override getWireRdata() — non-generic tinydns format`)
373
- // line: :fqdn:typeId:rdata:ttl:ts:loc\n
374
- const rdata = line.split(':')[3]
375
- return this.octalToBuffer(rdata ?? '')
562
+ return wireGetRdata(this)
376
563
  }
377
564
 
378
565
  toWire() {
379
- const rdata = this.getWireRdata()
380
- const owner = this.wirePackDomain(this.get('owner'))
381
- const classMap = { IN: 1, CS: 2, CH: 3, HS: 4, NONE: 254, ANY: 255 }
382
- const meta = Buffer.alloc(10)
383
- meta.writeUInt16BE(this.getTypeId(), 0)
384
- meta.writeUInt16BE(classMap[this.get('class')] ?? 1, 2)
385
- meta.writeUInt32BE(this.get('ttl'), 4)
386
- meta.writeUInt16BE(rdata.length, 8)
387
- return Buffer.concat([owner, meta, rdata])
566
+ return wireToWire(this, RR.CLASSES)
388
567
  }
389
568
 
390
569
  toBind(zone_opts) {
391
- return `${this.getPrefix(zone_opts)}\t${this.getRdataFields()
392
- .map((f) => this.getQuoted(f))
393
- .join('\t')}\n`
570
+ return bindToGeneric(this, zone_opts)
571
+ }
572
+
573
+ parseTinydnsLine(tinyline) {
574
+ const parsed = TINYDNS.parseGenericLine(tinyline)
575
+ return { ...parsed, owner: this.fullyQualify(parsed.owner) }
576
+ }
577
+
578
+ toTinydns() {
579
+ return TINYDNS.to(this)
580
+ }
581
+
582
+ isFqdnField(field) {
583
+ return (
584
+ (this.constructor.rdataFields ?? []).some(
585
+ (entry) => RR.fieldName(entry) === field && RR.fieldType(entry) === 'fqdn',
586
+ ) || false
587
+ )
588
+ }
589
+
590
+ isQuotedField(field) {
591
+ const quotedTypes = new Set(['qstr', 'qcharstr', 'charstrs'])
592
+ return (
593
+ (this.constructor.rdataFields ?? []).some(
594
+ (entry) => RR.fieldName(entry) === field && quotedTypes.has(RR.fieldType(entry)),
595
+ ) || false
596
+ )
394
597
  }
395
598
 
396
599
  toMaraDNS() {
@@ -399,37 +602,15 @@ export default class RR extends Map {
399
602
  /\s+/g,
400
603
  )
401
604
  if (!supportedTypes.includes(type)) return this.toMaraGeneric()
402
- return `${this.get('owner')}\t+${this.get('ttl')}\t${type}\t${this.getRdataFields()
605
+ return `${this.get('owner')}\t+${this.get('ttl')}\t${type}\t${this.getFields('rdata')
403
606
  .map((f) => this.getQuoted(f))
404
607
  .join('\t')} ~\n`
405
608
  }
406
609
 
407
610
  toMaraGeneric() {
408
611
  // this.throwHelp(`\nMaraDNS does not support ${type} records yet and this package does not support MaraDNS generic records. Yet.\n`)
409
- return `${this.get('owner')}\t+${this.get('ttl')}\tRAW ${this.getTypeId()}\t'${this.getRdataFields()
612
+ return `${this.get('owner')}\t+${this.get('ttl')}\tRAW ${this.getTypeId()}\t'${this.getFields('rdata')
410
613
  .map((f) => this.getQuoted(f))
411
614
  .join(' ')}' ~\n`
412
615
  }
413
616
  }
414
-
415
- function packDomainNameWire(fqdn) {
416
- if (fqdn === '.') return Buffer.from([0])
417
- const parts = fqdn.split('.')
418
- let len = 0
419
- for (const part of parts) {
420
- if (part.length > 0) len += part.length + 1
421
- }
422
- len += 1 // for the final \0
423
-
424
- const buf = Buffer.allocUnsafe(len)
425
- let offset = 0
426
- for (const part of parts) {
427
- if (part.length > 0) {
428
- buf.writeUInt8(part.length, offset++)
429
- buf.write(part, offset, 'ascii')
430
- offset += part.length
431
- }
432
- }
433
- buf.writeUInt8(0, offset)
434
- return buf
435
- }
package/lib/readme.js DELETED
@@ -1,84 +0,0 @@
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')