Bug 1687263: part 2) Add `NodeOffsetRange::operator==(const nsRange& aRange)`. r...
[gecko.git] / testing / xpcshell / dns-packet / test.js
blobadf4757daebf70317159aeab07c264a3a8454b9c
1 'use strict'
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'))
11   t.end()
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)])
21   t.end()
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')
29   t.end()
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')
38   t.end()
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')
45   t.end()
48 tape('null', function (t) {
49   testEncoder(t, packet.null, Buffer.from([0, 1, 2, 3, 4, 5]))
50   t.end()
53 tape('hinfo', function (t) {
54   testEncoder(t, packet.hinfo, { cpu: 'intel', os: 'best one' })
55   t.end()
58 tape('ptr', function (t) {
59   testEncoder(t, packet.ptr, 'hello.world.com')
60   t.end()
63 tape('cname', function (t) {
64   testEncoder(t, packet.cname, 'hello.cname.world.com')
65   t.end()
68 tape('dname', function (t) {
69   testEncoder(t, packet.dname, 'hello.dname.world.com')
70   t.end()
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 })
76   t.end()
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' })
83   t.end()
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' })
89   t.end()
92 tape('ns', function (t) {
93   testEncoder(t, packet.ns, 'ns.world.com')
94   t.end()
97 tape('soa', function (t) {
98   testEncoder(t, packet.soa, {
99     mname: 'hello.world.com',
100     rname: 'root.hello.world.com',
101     serial: 2018010400,
102     refresh: 14400,
103     retry: 3600,
104     expire: 604800,
105     minimum: 3600
106   })
107   t.end()
110 tape('a', function (t) {
111   testEncoder(t, packet.a, '127.0.0.1')
112   t.end()
115 tape('aaaa', function (t) {
116   testEncoder(t, packet.aaaa, 'fe80::1')
117   t.end()
120 tape('query', function (t) {
121   testEncoder(t, packet, {
122     type: 'query',
123     questions: [{
124       type: 'A',
125       name: 'hello.a.com'
126     }, {
127       type: 'SRV',
128       name: 'hello.srv.com'
129     }]
130   })
132   testEncoder(t, packet, {
133     type: 'query',
134     id: 42,
135     questions: [{
136       type: 'A',
137       class: 'IN',
138       name: 'hello.a.com'
139     }, {
140       type: 'SRV',
141       name: 'hello.srv.com'
142     }]
143   })
145   testEncoder(t, packet, {
146     type: 'query',
147     id: 42,
148     questions: [{
149       type: 'A',
150       class: 'CH',
151       name: 'hello.a.com'
152     }, {
153       type: 'SRV',
154       name: 'hello.srv.com'
155     }]
156   })
158   t.end()
161 tape('response', function (t) {
162   testEncoder(t, packet, {
163     type: 'response',
164     answers: [{
165       type: 'A',
166       class: 'IN',
167       flush: true,
168       name: 'hello.a.com',
169       data: '127.0.0.1'
170     }]
171   })
173   testEncoder(t, packet, {
174     type: 'response',
175     flags: packet.TRUNCATED_RESPONSE,
176     answers: [{
177       type: 'A',
178       class: 'IN',
179       name: 'hello.a.com',
180       data: '127.0.0.1'
181     }, {
182       type: 'SRV',
183       class: 'IN',
184       name: 'hello.srv.com',
185       data: {
186         port: 9090,
187         target: 'hello.target.com'
188       }
189     }, {
190       type: 'CNAME',
191       class: 'IN',
192       name: 'hello.cname.com',
193       data: 'hello.other.domain.com'
194     }]
195   })
197   testEncoder(t, packet, {
198     type: 'response',
199     id: 100,
200     flags: 0,
201     additionals: [{
202       type: 'AAAA',
203       name: 'hello.a.com',
204       data: 'fe80::1'
205     }, {
206       type: 'PTR',
207       name: 'hello.ptr.com',
208       data: 'hello.other.ptr.com'
209     }, {
210       type: 'SRV',
211       name: 'hello.srv.com',
212       ttl: 42,
213       data: {
214         port: 9090,
215         target: 'hello.target.com'
216       }
217     }],
218     answers: [{
219       type: 'NULL',
220       name: 'hello.null.com',
221       data: Buffer.from([1, 2, 3, 4, 5])
222     }]
223   })
225   testEncoder(t, packet, {
226     type: 'response',
227     answers: [{
228       type: 'TXT',
229       name: 'emptytxt.com',
230       data: ''
231     }]
232   })
234   t.end()
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))
242   }
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))
248   }
250   const buf = packet.encode({
251     type: 'response',
252     id: 45632,
253     flags: 0x8480,
254     answers: [{
255       type: 'A',
256       name: 'hello.example.net',
257       data: '127.0.0.1'
258     }]
259   })
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')
272   t.end()
275 tape('name_encoding', function (t) {
276   let data = 'foo.example.com'
277   const buf = Buffer.allocUnsafe(255)
278   let offset = 0
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
285   data = 'com'
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
299   data = '.'
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')
304   t.end()
307 tape('stream', function (t) {
308   const val = {
309     type: 'query',
310     id: 45632,
311     flags: 0x8480,
312     answers: [{
313       type: 'A',
314       name: 'test2.example.net',
315       data: '198.51.100.1'
316     }]
317   }
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')
330   t.end()
333 tape('opt', function (t) {
334   const val = {
335     type: 'query',
336     questions: [{
337       type: 'A',
338       name: 'hello.a.com'
339     }],
340     additionals: [{
341       type: 'OPT',
342       name: '.',
343       udpPayloadSize: 1024
344     }]
345   }
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
358     ip: 'fe80::',
359     sourcePrefixLength: 64
360   }, {
361     code: 8, // still ECS
362     ip: '5.6.0.0',
363     sourcePrefixLength: 16,
364     scopePrefixLength: 16
365   }, {
366     code: 'padding',
367     length: 31
368   }, {
369     code: 'TCP_KEEPALIVE'
370   }, {
371     code: 'tcp_keepalive',
372     timeout: 150
373   }, {
374     code: 'KEY_TAG',
375     tags: [1, 82, 987]
376   }]
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))
393   t.end()
396 tape('dnskey', function (t) {
397   testEncoder(t, packet.dnskey, {
398     flags: packet.dnskey.SECURE_ENTRYPOINT | packet.dnskey.ZONE_KEY,
399     algorithm: 1,
400     key: Buffer.from([0, 1, 2, 3, 4, 5])
401   })
402   t.end()
405 tape('rrsig', function (t) {
406   const testRRSIG = {
407     typeCovered: 'A',
408     algorithm: 1,
409     labels: 2,
410     originalTTL: 3600,
411     expiration: 1234,
412     inception: 1233,
413     keyTag: 2345,
414     signersName: 'foo.com',
415     signature: Buffer.from([0, 1, 2, 3, 4, 5])
416   }
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))
425   t.end()
428 tape('rrp', function (t) {
429   testEncoder(t, packet.rp, {
430     mbox: 'foo.bar.com',
431     txt: 'baz.bar.com'
432   })
433   testEncoder(t, packet.rp, {
434     mbox: 'foo.bar.com'
435   })
436   testEncoder(t, packet.rp, {
437     txt: 'baz.bar.com'
438   })
439   testEncoder(t, packet.rp, {})
440   t.end()
443 tape('nsec', function (t) {
444   testEncoder(t, packet.nsec, {
445     nextDomain: 'foo.com',
446     rrtypes: ['A', 'DNSKEY', 'CAA', 'DLV']
447   })
448   testEncoder(t, packet.nsec, {
449     nextDomain: 'foo.com',
450     rrtypes: ['TXT'] // 16
451   })
452   testEncoder(t, packet.nsec, {
453     nextDomain: 'foo.com',
454     rrtypes: ['TKEY'] // 249
455   })
456   testEncoder(t, packet.nsec, {
457     nextDomain: 'foo.com',
458     rrtypes: ['RRSIG', 'NSEC']
459   })
460   testEncoder(t, packet.nsec, {
461     nextDomain: 'foo.com',
462     rrtypes: ['TXT', 'RRSIG']
463   })
464   testEncoder(t, packet.nsec, {
465     nextDomain: 'foo.com',
466     rrtypes: ['TXT', 'NSEC']
467   })
469   // Test with the sample NSEC from https://tools.ietf.org/html/rfc4034#section-4.3
470   var sampleNSEC = Buffer.from('003704686f7374076578616d706c6503636f6d00' +
471       '0006400100000003041b000000000000000000000000000000000000000000000' +
472       '000000020', 'hex')
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']
477   }))
478   var reencoded = packet.nsec.encode(decoded)
479   t.same(sampleNSEC.length, reencoded.length)
480   t.same(sampleNSEC, reencoded)
481   t.end()
484 tape('nsec3', function (t) {
485   testEncoder(t, packet.nsec3, {
486     algorithm: 1,
487     flags: 0,
488     iterations: 257,
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']
492   })
493   t.end()
496 tape('ds', function (t) {
497   testEncoder(t, packet.ds, {
498     keyTag: 1234,
499     algorithm: 1,
500     digestType: 1,
501     digest: Buffer.from([0, 1, 2, 3, 4, 5])
502   })
503   t.end()
506 tape('unpack', function (t) {
507   const buf = Buffer.from([
508     0x00, 0x79,
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
524   ])
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')
535   t.end()
538 tape('optioncodes', function (t) {
539   const opts = [
540     [0, 'OPTION_0'],
541     [1, 'LLQ'],
542     [2, 'UL'],
543     [3, 'NSID'],
544     [4, 'OPTION_4'],
545     [5, 'DAU'],
546     [6, 'DHU'],
547     [7, 'N3U'],
548     [8, 'CLIENT_SUBNET'],
549     [9, 'EXPIRE'],
550     [10, 'COOKIE'],
551     [11, 'TCP_KEEPALIVE'],
552     [12, 'PADDING'],
553     [13, 'CHAIN'],
554     [14, 'KEY_TAG'],
555     [26946, 'DEVICEID'],
556     [65535, 'OPTION_65535'],
557     [64000, 'OPTION_64000'],
558     [65002, 'OPTION_65002'],
559     [-1, null]
560   ]
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}`)
565   }
566   t.ok(compare(t, optioncodes.toCode('INVALIDINVALID'), -1))
567   t.end()
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]])) {
603         return false
604       }
605     }
606   } else if (Array.isArray(b) && !Array.isArray(a)) {
607     // TXT always decode as array
608     return a.toString() === b[0].toString()
609   } else {
610     return a === b
611   }
612   return true