3 const tape = require('tape')
4 const packet = require('./')
5 const rcodes = require('./rcodes')
6 const opcodes = require('./opcodes')
7 const optioncodes = require('./optioncodes')
9 tape('unknown', function (t) {
10 testEncoder(t, packet.unknown, Buffer.from('hello world'))
14 tape('txt', function (t) {
15 testEncoder(t, packet.txt, [])
16 testEncoder(t, packet.txt, ['hello world'])
17 testEncoder(t, packet.txt, ['hello', 'world'])
18 testEncoder(t, packet.txt, [Buffer.from([0, 1, 2, 3, 4, 5])])
19 testEncoder(t, packet.txt, ['a', 'b', Buffer.from([0, 1, 2, 3, 4, 5])])
20 testEncoder(t, packet.txt, ['', Buffer.allocUnsafe(0)])
24 tape('txt-scalar-string', function (t) {
25 const buf = packet.txt.encode('hi')
26 const val = packet.txt.decode(buf)
27 t.ok(val.length === 1, 'array length')
28 t.ok(val[0].toString() === 'hi', 'data')
32 tape('txt-scalar-buffer', function (t) {
33 const data = Buffer.from([0, 1, 2, 3, 4, 5])
34 const buf = packet.txt.encode(data)
35 const val = packet.txt.decode(buf)
36 t.ok(val.length === 1, 'array length')
37 t.ok(val[0].equals(data), 'data')
41 tape('txt-invalid-data', function (t) {
42 t.throws(function () { packet.txt.encode(null) }, 'null')
43 t.throws(function () { packet.txt.encode(undefined) }, 'undefined')
44 t.throws(function () { packet.txt.encode(10) }, 'number')
48 tape('null', function (t) {
49 testEncoder(t, packet.null, Buffer.from([0, 1, 2, 3, 4, 5]))
53 tape('hinfo', function (t) {
54 testEncoder(t, packet.hinfo, { cpu: 'intel', os: 'best one' })
58 tape('ptr', function (t) {
59 testEncoder(t, packet.ptr, 'hello.world.com')
63 tape('cname', function (t) {
64 testEncoder(t, packet.cname, 'hello.cname.world.com')
68 tape('dname', function (t) {
69 testEncoder(t, packet.dname, 'hello.dname.world.com')
73 tape('srv', function (t) {
74 testEncoder(t, packet.srv, { port: 9999, target: 'hello.world.com' })
75 testEncoder(t, packet.srv, { port: 9999, target: 'hello.world.com', priority: 42, weight: 10 })
79 tape('caa', function (t) {
80 testEncoder(t, packet.caa, { flags: 128, tag: 'issue', value: 'letsencrypt.org', issuerCritical: true })
81 testEncoder(t, packet.caa, { tag: 'issue', value: 'letsencrypt.org', issuerCritical: true })
82 testEncoder(t, packet.caa, { tag: 'issue', value: 'letsencrypt.org' })
86 tape('mx', function (t) {
87 testEncoder(t, packet.mx, { preference: 10, exchange: 'mx.hello.world.com' })
88 testEncoder(t, packet.mx, { exchange: 'mx.hello.world.com' })
92 tape('ns', function (t) {
93 testEncoder(t, packet.ns, 'ns.world.com')
97 tape('soa', function (t) {
98 testEncoder(t, packet.soa, {
99 mname: 'hello.world.com',
100 rname: 'root.hello.world.com',
110 tape('a', function (t) {
111 testEncoder(t, packet.a, '127.0.0.1')
115 tape('aaaa', function (t) {
116 testEncoder(t, packet.aaaa, 'fe80::1')
120 tape('query', function (t) {
121 testEncoder(t, packet, {
128 name: 'hello.srv.com'
132 testEncoder(t, packet, {
141 name: 'hello.srv.com'
145 testEncoder(t, packet, {
154 name: 'hello.srv.com'
161 tape('response', function (t) {
162 testEncoder(t, packet, {
173 testEncoder(t, packet, {
175 flags: packet.TRUNCATED_RESPONSE,
184 name: 'hello.srv.com',
187 target: 'hello.target.com'
192 name: 'hello.cname.com',
193 data: 'hello.other.domain.com'
197 testEncoder(t, packet, {
207 name: 'hello.ptr.com',
208 data: 'hello.other.ptr.com'
211 name: 'hello.srv.com',
215 target: 'hello.target.com'
220 name: 'hello.null.com',
221 data: Buffer.from([1, 2, 3, 4, 5])
225 testEncoder(t, packet, {
229 name: 'emptytxt.com',
237 tape('rcode', function (t) {
238 const errors = ['NOERROR', 'FORMERR', 'SERVFAIL', 'NXDOMAIN', 'NOTIMP', 'REFUSED', 'YXDOMAIN', 'YXRRSET', 'NXRRSET', 'NOTAUTH', 'NOTZONE', 'RCODE_11', 'RCODE_12', 'RCODE_13', 'RCODE_14', 'RCODE_15']
239 for (const i in errors) {
240 const code = rcodes.toRcode(errors[i])
241 t.ok(errors[i] === rcodes.toString(code), 'rcode conversion from/to string matches: ' + rcodes.toString(code))
244 const ops = ['QUERY', 'IQUERY', 'STATUS', 'OPCODE_3', 'NOTIFY', 'UPDATE', 'OPCODE_6', 'OPCODE_7', 'OPCODE_8', 'OPCODE_9', 'OPCODE_10', 'OPCODE_11', 'OPCODE_12', 'OPCODE_13', 'OPCODE_14', 'OPCODE_15']
245 for (const j in ops) {
246 const ocode = opcodes.toOpcode(ops[j])
247 t.ok(ops[j] === opcodes.toString(ocode), 'opcode conversion from/to string matches: ' + opcodes.toString(ocode))
250 const buf = packet.encode({
256 name: 'hello.example.net',
260 const val = packet.decode(buf)
261 t.ok(val.type === 'response', 'decode type')
262 t.ok(val.opcode === 'QUERY', 'decode opcode')
263 t.ok(val.flag_qr === true, 'decode flag_qr')
264 t.ok(val.flag_aa === true, 'decode flag_aa')
265 t.ok(val.flag_tc === false, 'decode flag_tc')
266 t.ok(val.flag_rd === false, 'decode flag_rd')
267 t.ok(val.flag_ra === true, 'decode flag_ra')
268 t.ok(val.flag_z === false, 'decode flag_z')
269 t.ok(val.flag_ad === false, 'decode flag_ad')
270 t.ok(val.flag_cd === false, 'decode flag_cd')
271 t.ok(val.rcode === 'NOERROR', 'decode rcode')
275 tape('name_encoding', function (t) {
276 let data = 'foo.example.com'
277 const buf = Buffer.allocUnsafe(255)
279 packet.name.encode(data, buf, offset)
280 t.ok(packet.name.encode.bytes === 17, 'name encoding length matches')
281 let dd = packet.name.decode(buf, offset)
282 t.ok(data === dd, 'encode/decode matches')
283 offset += packet.name.encode.bytes
286 packet.name.encode(data, buf, offset)
287 t.ok(packet.name.encode.bytes === 5, 'name encoding length matches')
288 dd = packet.name.decode(buf, offset)
289 t.ok(data === dd, 'encode/decode matches')
290 offset += packet.name.encode.bytes
292 data = 'example.com.'
293 packet.name.encode(data, buf, offset)
294 t.ok(packet.name.encode.bytes === 13, 'name encoding length matches')
295 dd = packet.name.decode(buf, offset)
296 t.ok(data.slice(0, -1) === dd, 'encode/decode matches')
297 offset += packet.name.encode.bytes
300 packet.name.encode(data, buf, offset)
301 t.ok(packet.name.encode.bytes === 1, 'name encoding length matches')
302 dd = packet.name.decode(buf, offset)
303 t.ok(data === dd, 'encode/decode matches')
307 tape('stream', function (t) {
314 name: 'test2.example.net',
318 const buf = packet.streamEncode(val)
319 const val2 = packet.streamDecode(buf)
321 t.same(buf.length, packet.streamEncode.bytes, 'streamEncode.bytes was set correctly')
322 t.ok(compare(t, val2.type, val.type), 'streamDecoded type match')
323 t.ok(compare(t, val2.id, val.id), 'streamDecoded id match')
324 t.ok(parseInt(val2.flags) === parseInt(val.flags & 0x7FFF), 'streamDecoded flags match')
325 const answer = val.answers[0]
326 const answer2 = val2.answers[0]
327 t.ok(compare(t, answer.type, answer2.type), 'streamDecoded RR type match')
328 t.ok(compare(t, answer.name, answer2.name), 'streamDecoded RR name match')
329 t.ok(compare(t, answer.data, answer2.data), 'streamDecoded RR rdata match')
333 tape('opt', function (t) {
346 testEncoder(t, packet, val)
347 let buf = packet.encode(val)
348 let val2 = packet.decode(buf)
349 const additional1 = val.additionals[0]
350 let additional2 = val2.additionals[0]
351 t.ok(compare(t, additional1.name, additional2.name), 'name matches')
352 t.ok(compare(t, additional1.udpPayloadSize, additional2.udpPayloadSize), 'udp payload size matches')
353 t.ok(compare(t, 0, additional2.flags), 'flags match')
354 additional1.flags = packet.DNSSEC_OK
355 additional1.extendedRcode = 0x80
356 additional1.options = [ {
357 code: 'CLIENT_SUBNET', // edns-client-subnet, see RFC 7871
359 sourcePrefixLength: 64
361 code: 8, // still ECS
363 sourcePrefixLength: 16,
364 scopePrefixLength: 16
369 code: 'TCP_KEEPALIVE'
371 code: 'tcp_keepalive',
377 buf = packet.encode(val)
378 val2 = packet.decode(buf)
379 additional2 = val2.additionals[0]
380 t.ok(compare(t, 1 << 15, additional2.flags), 'DO bit set in flags')
381 t.ok(compare(t, true, additional2.flag_do), 'DO bit set')
382 t.ok(compare(t, additional1.extendedRcode, additional2.extendedRcode), 'extended rcode matches')
383 t.ok(compare(t, 8, additional2.options[0].code))
384 t.ok(compare(t, 'fe80::', additional2.options[0].ip))
385 t.ok(compare(t, 64, additional2.options[0].sourcePrefixLength))
386 t.ok(compare(t, '5.6.0.0', additional2.options[1].ip))
387 t.ok(compare(t, 16, additional2.options[1].sourcePrefixLength))
388 t.ok(compare(t, 16, additional2.options[1].scopePrefixLength))
389 t.ok(compare(t, additional1.options[2].length, additional2.options[2].data.length))
390 t.ok(compare(t, additional1.options[3].timeout, undefined))
391 t.ok(compare(t, additional1.options[4].timeout, additional2.options[4].timeout))
392 t.ok(compare(t, additional1.options[5].tags, additional2.options[5].tags))
396 tape('dnskey', function (t) {
397 testEncoder(t, packet.dnskey, {
398 flags: packet.dnskey.SECURE_ENTRYPOINT | packet.dnskey.ZONE_KEY,
400 key: Buffer.from([0, 1, 2, 3, 4, 5])
405 tape('rrsig', function (t) {
414 signersName: 'foo.com',
415 signature: Buffer.from([0, 1, 2, 3, 4, 5])
417 testEncoder(t, packet.rrsig, testRRSIG)
419 // Check the signature length is correct with extra junk at the end
420 const buf = Buffer.allocUnsafe(packet.rrsig.encodingLength(testRRSIG) + 4)
421 packet.rrsig.encode(testRRSIG, buf)
422 const val2 = packet.rrsig.decode(buf)
423 t.ok(compare(t, testRRSIG, val2))
428 tape('rrp', function (t) {
429 testEncoder(t, packet.rp, {
433 testEncoder(t, packet.rp, {
436 testEncoder(t, packet.rp, {
439 testEncoder(t, packet.rp, {})
443 tape('nsec', function (t) {
444 testEncoder(t, packet.nsec, {
445 nextDomain: 'foo.com',
446 rrtypes: ['A', 'DNSKEY', 'CAA', 'DLV']
448 testEncoder(t, packet.nsec, {
449 nextDomain: 'foo.com',
450 rrtypes: ['TXT'] // 16
452 testEncoder(t, packet.nsec, {
453 nextDomain: 'foo.com',
454 rrtypes: ['TKEY'] // 249
456 testEncoder(t, packet.nsec, {
457 nextDomain: 'foo.com',
458 rrtypes: ['RRSIG', 'NSEC']
460 testEncoder(t, packet.nsec, {
461 nextDomain: 'foo.com',
462 rrtypes: ['TXT', 'RRSIG']
464 testEncoder(t, packet.nsec, {
465 nextDomain: 'foo.com',
466 rrtypes: ['TXT', 'NSEC']
469 // Test with the sample NSEC from https://tools.ietf.org/html/rfc4034#section-4.3
470 var sampleNSEC = Buffer.from('003704686f7374076578616d706c6503636f6d00' +
471 '0006400100000003041b000000000000000000000000000000000000000000000' +
473 var decoded = packet.nsec.decode(sampleNSEC)
474 t.ok(compare(t, decoded, {
475 nextDomain: 'host.example.com',
476 rrtypes: ['A', 'MX', 'RRSIG', 'NSEC', 'UNKNOWN_1234']
478 var reencoded = packet.nsec.encode(decoded)
479 t.same(sampleNSEC.length, reencoded.length)
480 t.same(sampleNSEC, reencoded)
484 tape('nsec3', function (t) {
485 testEncoder(t, packet.nsec3, {
489 salt: Buffer.from([42, 42, 42]),
490 nextDomain: Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]),
491 rrtypes: ['A', 'DNSKEY', 'CAA', 'DLV']
496 tape('ds', function (t) {
497 testEncoder(t, packet.ds, {
501 digest: Buffer.from([0, 1, 2, 3, 4, 5])
506 tape('unpack', function (t) {
507 const buf = Buffer.from([
509 0xde, 0xad, 0x85, 0x00, 0x00, 0x01, 0x00, 0x01,
510 0x00, 0x02, 0x00, 0x02, 0x02, 0x6f, 0x6a, 0x05,
511 0x62, 0x61, 0x6e, 0x67, 0x6a, 0x03, 0x63, 0x6f,
512 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c,
513 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10,
514 0x00, 0x04, 0x81, 0xfa, 0x0b, 0xaa, 0xc0, 0x0f,
515 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10,
516 0x00, 0x05, 0x02, 0x63, 0x6a, 0xc0, 0x0f, 0xc0,
517 0x0f, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x0e,
518 0x10, 0x00, 0x02, 0xc0, 0x0c, 0xc0, 0x3a, 0x00,
519 0x01, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10, 0x00,
520 0x04, 0x45, 0x4d, 0x9b, 0x9c, 0xc0, 0x0c, 0x00,
521 0x1c, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10, 0x00,
522 0x10, 0x20, 0x01, 0x04, 0x18, 0x00, 0x00, 0x50,
523 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf9
525 const val = packet.streamDecode(buf)
526 const answer = val.answers[0]
527 const authority = val.authorities[1]
528 t.ok(val.rcode === 'NOERROR', 'decode rcode')
529 t.ok(compare(t, answer.type, 'A'), 'streamDecoded RR type match')
530 t.ok(compare(t, answer.name, 'oj.bangj.com'), 'streamDecoded RR name match')
531 t.ok(compare(t, answer.data, '129.250.11.170'), 'streamDecoded RR rdata match')
532 t.ok(compare(t, authority.type, 'NS'), 'streamDecoded RR type match')
533 t.ok(compare(t, authority.name, 'bangj.com'), 'streamDecoded RR name match')
534 t.ok(compare(t, authority.data, 'oj.bangj.com'), 'streamDecoded RR rdata match')
538 tape('optioncodes', function (t) {
548 [8, 'CLIENT_SUBNET'],
551 [11, 'TCP_KEEPALIVE'],
556 [65535, 'OPTION_65535'],
557 [64000, 'OPTION_64000'],
558 [65002, 'OPTION_65002'],
561 for (const [code, str] of opts) {
562 const s = optioncodes.toString(code)
563 t.ok(compare(t, s, str), `${code} => ${str}`)
564 t.ok(compare(t, optioncodes.toCode(s), code), `${str} => ${code}`)
566 t.ok(compare(t, optioncodes.toCode('INVALIDINVALID'), -1))
570 function testEncoder (t, rpacket, val) {
571 const buf = rpacket.encode(val)
572 const val2 = rpacket.decode(buf)
574 t.same(buf.length, rpacket.encode.bytes, 'encode.bytes was set correctly')
575 t.same(buf.length, rpacket.encodingLength(val), 'encoding length matches')
576 t.ok(compare(t, val, val2), 'decoded object match')
578 const buf2 = rpacket.encode(val2)
579 const val3 = rpacket.decode(buf2)
581 t.same(buf2.length, rpacket.encode.bytes, 'encode.bytes was set correctly on re-encode')
582 t.same(buf2.length, rpacket.encodingLength(val), 'encoding length matches on re-encode')
584 t.ok(compare(t, val, val3), 'decoded object match on re-encode')
585 t.ok(compare(t, val2, val3), 're-encoded decoded object match on re-encode')
587 const bigger = Buffer.allocUnsafe(buf2.length + 10)
589 const buf3 = rpacket.encode(val, bigger, 10)
590 const val4 = rpacket.decode(buf3, 10)
592 t.ok(buf3 === bigger, 'echoes buffer on external buffer')
593 t.same(rpacket.encode.bytes, buf.length, 'encode.bytes is the same on external buffer')
594 t.ok(compare(t, val, val4), 'decoded object match on external buffer')
597 function compare (t, a, b) {
598 if (Buffer.isBuffer(a)) return a.toString('hex') === b.toString('hex')
599 if (typeof a === 'object' && a && b) {
600 const keys = Object.keys(a)
601 for (let i = 0; i < keys.length; i++) {
602 if (!compare(t, a[keys[i]], b[keys[i]])) {
606 } else if (Array.isArray(b) && !Array.isArray(a)) {
607 // TXT always decode as array
608 return a.toString() === b[0].toString()