tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test.js (17353B)


      1 'use strict'
      2 
      3 const tape = require('tape')
      4 const packet = require('./')
      5 const rcodes = require('./rcodes')
      6 const opcodes = require('./opcodes')
      7 const optioncodes = require('./optioncodes')
      8 
      9 tape('unknown', function (t) {
     10  testEncoder(t, packet.unknown, Buffer.from('hello world'))
     11  t.end()
     12 })
     13 
     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()
     22 })
     23 
     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()
     30 })
     31 
     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()
     39 })
     40 
     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()
     46 })
     47 
     48 tape('null', function (t) {
     49  testEncoder(t, packet.null, Buffer.from([0, 1, 2, 3, 4, 5]))
     50  t.end()
     51 })
     52 
     53 tape('hinfo', function (t) {
     54  testEncoder(t, packet.hinfo, { cpu: 'intel', os: 'best one' })
     55  t.end()
     56 })
     57 
     58 tape('ptr', function (t) {
     59  testEncoder(t, packet.ptr, 'hello.world.com')
     60  t.end()
     61 })
     62 
     63 tape('cname', function (t) {
     64  testEncoder(t, packet.cname, 'hello.cname.world.com')
     65  t.end()
     66 })
     67 
     68 tape('dname', function (t) {
     69  testEncoder(t, packet.dname, 'hello.dname.world.com')
     70  t.end()
     71 })
     72 
     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()
     77 })
     78 
     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()
     84 })
     85 
     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()
     90 })
     91 
     92 tape('ns', function (t) {
     93  testEncoder(t, packet.ns, 'ns.world.com')
     94  t.end()
     95 })
     96 
     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()
    108 })
    109 
    110 tape('a', function (t) {
    111  testEncoder(t, packet.a, '127.0.0.1')
    112  t.end()
    113 })
    114 
    115 tape('aaaa', function (t) {
    116  testEncoder(t, packet.aaaa, 'fe80::1')
    117  t.end()
    118 })
    119 
    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  })
    131 
    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  })
    144 
    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  })
    157 
    158  t.end()
    159 })
    160 
    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  })
    172 
    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  })
    196 
    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  })
    224 
    225  testEncoder(t, packet, {
    226    type: 'response',
    227    answers: [{
    228      type: 'TXT',
    229      name: 'emptytxt.com',
    230      data: ''
    231    }]
    232  })
    233 
    234  t.end()
    235 })
    236 
    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  }
    243 
    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  }
    249 
    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()
    273 })
    274 
    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
    284 
    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
    291 
    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
    298 
    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()
    305 })
    306 
    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)
    320 
    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()
    331 })
    332 
    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()
    394 })
    395 
    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()
    403 })
    404 
    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)
    418 
    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))
    424 
    425  t.end()
    426 })
    427 
    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()
    441 })
    442 
    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  })
    468 
    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()
    482 })
    483 
    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()
    494 })
    495 
    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()
    504 })
    505 
    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()
    536 })
    537 
    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()
    568 })
    569 
    570 function testEncoder (t, rpacket, val) {
    571  const buf = rpacket.encode(val)
    572  const val2 = rpacket.decode(buf)
    573 
    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')
    577 
    578  const buf2 = rpacket.encode(val2)
    579  const val3 = rpacket.decode(buf2)
    580 
    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')
    583 
    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')
    586 
    587  const bigger = Buffer.allocUnsafe(buf2.length + 10)
    588 
    589  const buf3 = rpacket.encode(val, bigger, 10)
    590  const val4 = rpacket.decode(buf3, 10)
    591 
    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')
    595 }
    596 
    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
    613 }