commit e2643b0312696e3453bcfff47ccd81978404d29e Author: Mathias Buus Date: Thu Feb 18 12:46:42 2016 -0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bae9da7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Mathias Buus + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..cf468ec --- /dev/null +++ b/README.md @@ -0,0 +1,170 @@ +# dns-packet + +An [abstract-encoding](https://github.com/mafintosh/abstract-encoding) compliant module for encoding / decoding DNS packets. +Lifted out of [multicast-dns](https://github.com/mafintosh/multicast-dns) as a separate module. + +``` +npm install dns-packet +``` + +## Usage + +``` js +var packet = require('dns-packet') +var dgram = require('dgram') + +var socket = dgram.createSocket('udp4') + +var buf = packet.encode({ + type: 'query', + id: 1, + questions: [{ + type: 'A', + name: 'google.com' + }] +}) + +socket.on('message', function (message) { + console.log(packet.decode(message)) // prints out a response from google dns +}) + +socket.send(buf, 0, buf.length, 53, '8.8.8.8') +``` + +## API + +#### `var buf = packets.encode(packet, [buf], [offset])` + +Encodes a DNS packet into a buffer. + +#### `var packet = packets.decode(buf, [offset])` + +Decode a DNS packet from a buffer + +#### `var len = packets.encodingLength(packet)` + +Returns how many bytes are needed to encode the DNS packet + +## Packets + +Packets look like this + +``` js +{ + type: 'query|response', + id: optionalIdNumber, + questions: [...], + answers: [...], + additionals: [...], + authorities: [...] +} +``` + +A question looks like this + +``` js +{ + type: 'A', // or SRV, AAAA, etc + name: 'google.com' // which record are you looking for +} +``` + +And an answers, additional, or authority looks like this + +``` js +{ + type: 'A', // or SRV, AAAA, etc + name: 'google.com', // which name is this record for + ttl: optionalTimeToLiveInSeconds, + (record specific data, see below) +} +``` + +Currently the different available records are + +#### `A` + +``` js +{ + data: 'IPv4 address' // fx 127.0.0.1 +} +``` + +#### `AAAA` + +``` js +{ + data: 'IPv6 address' // fx fe80::1 +} +``` + +#### `TXT` + +``` js +{ + data: Buffer('some text') +} +``` + +#### `NULL` + +``` js +{ + data: Buffer('any binary data') +} +``` + +#### `SRV` + +``` js +{ + data: { + port: servicePort, + target: serviceHostName, + priority: optionalServicePriority, + weight: optionalServiceWeight + } +} +``` + +#### `HINFO` + +``` js +{ + data: { + cpu: 'cpu info', + os: 'os info' + } +} +``` + +#### `PTR` + +``` js +{ + data: 'points.to.another.record' +} +``` + +#### `CNAME` + +``` js +{ + data: 'cname.to.another.record' +} +``` + +#### `DNAME` + +``` js +{ + data: 'dname.to.another.record' +} +``` + +If you need another one, open an issue and we'll try to add it. + +## License + +MIT + diff --git a/example.js b/example.js new file mode 100644 index 0000000..5ddb4ee --- /dev/null +++ b/example.js @@ -0,0 +1,20 @@ +var packet = require('./') +var dgram = require('dgram') + +var socket = dgram.createSocket('udp4') + +var buf = packet.encode({ + type: 'query', + id: 1, + questions: [{ + type: 'A', + name: 'google.com' + }] +}) + +socket.on('message', function (message, rinfo) { + console.log(rinfo) + console.log(packet.decode(message)) // prints out a response from google dns +}) + +socket.send(buf, 0, buf.length, 53, '8.8.8.8') diff --git a/index.js b/index.js new file mode 100644 index 0000000..1f8581d --- /dev/null +++ b/index.js @@ -0,0 +1,576 @@ +var types = require('./types') +var ip = require('ip') + +var QUERY_FLAG = 0 +var RESPONSE_FLAG = 1 << 15 +var FLUSH_MASK = 1 << 15 +var NOT_FLUSH_MASK = ~FLUSH_MASK +var QU_MASK = 1 << 15 +var NOT_QU_MASK = ~QU_MASK + +var name = {} + +name.encode = function (n, buf, offset) { + if (!buf) buf = Buffer(name.encodingLength(n)) + if (!offset) offset = 0 + + var list = n.split('.') + var oldOffset = offset + + for (var i = 0; i < list.length; i++) { + var len = buf.write(list[i], offset + 1) + buf[offset] = len + offset += len + 1 + } + + buf[offset++] = 0 + + name.encode.bytes = offset - oldOffset + return buf +} + +name.encode.bytes = 0 + +name.decode = function (buf, offset) { + if (!offset) offset = 0 + + var list = [] + var oldOffset = offset + var len = buf[offset++] + + if (len >= 0xc0) { + var res = name.decode(buf, buf.readUInt16BE(offset - 1) - 0xc000) + name.decode.bytes = 2 + return res + } + + while (len) { + if (len >= 0xc0) { + list.push(name.decode(buf, buf.readUInt16BE(offset - 1) - 0xc000)) + offset++ + break + } + + list.push(buf.toString('utf-8', offset, offset + len)) + offset += len + len = buf[offset++] + } + + name.decode.bytes = offset - oldOffset + return list.join('.') +} + +name.decode.bytes = 0 + +name.encodingLength = function (n) { + return Buffer.byteLength(n) + 2 +} + +var string = {} + +string.encode = function (s, buf, offset) { + if (!buf) buf = Buffer(string.encodingLength(s)) + if (!offset) offset = 0 + + var len = buf.write(s, offset + 1) + buf[offset] = len + string.encode.bytes = len + 1 + return buf +} + +string.encode.bytes = 0 + +string.decode = function (buf, offset) { + if (!offset) offset = 0 + + var len = buf[offset] + var s = buf.toString('utf-8', offset + 1, offset + 1 + len) + string.decode.bytes = len + 1 + return s +} + +string.decode.bytes = 0 + +string.encodingLength = function (s) { + return Buffer.byteLength(s) + 1 +} + +var header = {} + +header.encode = function (h, buf, offset) { + if (!buf) buf = header.encodingLength(h) + if (!offset) offset = 0 + + buf.writeUInt16BE(h.id || 0, offset) + buf.writeUInt16BE(h.type === 'response' ? RESPONSE_FLAG : QUERY_FLAG, offset + 2) + buf.writeUInt16BE(h.questions.length, offset + 4) + buf.writeUInt16BE(h.answers.length, offset + 6) + buf.writeUInt16BE(h.authorities.length, offset + 8) + buf.writeUInt16BE(h.additionals.length, offset + 10) + + return buf +} + +header.encode.bytes = 12 + +header.decode = function (buf, offset) { + if (!offset) offset = 0 + if (buf.length < 12) throw new Error('Header must be 12 bytes') + + return { + id: buf.readUInt16BE(offset), + type: buf.readUInt16BE(offset + 2) & RESPONSE_FLAG ? 'response' : 'query', + questions: new Array(buf.readUInt16BE(offset + 4)), + answers: new Array(buf.readUInt16BE(offset + 6)), + authorities: new Array(buf.readUInt16BE(offset + 8)), + additionals: new Array(buf.readUInt16BE(offset + 10)) + } +} + +header.decode.bytes = 12 + +header.encodingLength = function (h) { + return 12 +} + +var runknown = exports.unknown = {} + +runknown.encode = function (data, buf, offset) { + if (!buf) buf = Buffer(runknown.encodingLength(data)) + if (!offset) offset = 0 + + buf.writeUInt16BE(data.length, offset) + data.copy(buf, offset + 2) + + runknown.encode.bytes = data.length + 2 + return buf +} + +runknown.encode.bytes = 0 + +runknown.decode = function (buf, offset) { + if (!offset) offset = 0 + + var len = buf.readUInt16BE(offset) + var data = buf.slice(offset + 2, offset + 2 + len) + runknown.decode.bytes = len + 2 + return data +} + +runknown.decode.bytes = 0 + +runknown.encodingLength = function (data) { + return data.length + 2 +} + +var rtxt = exports.txt = exports.null = {} +var rnull = rtxt + +rtxt.encode = function (data, buf, offset) { + if (!buf) buf = Buffer(rtxt.encodingLength(data)) + if (!offset) offset = 0 + + if (typeof data === 'string') data = Buffer(data) + var oldOffset = offset + offset += 2 + + var len = data.length + data.copy(buf, offset, 0, len) + offset += len + + buf.writeUInt16BE(offset - oldOffset - 2, oldOffset) + rtxt.encode.bytes = offset - oldOffset + return buf +} + +rtxt.encode.bytes = 0 + +rtxt.decode = function (buf, offset) { + if (!offset) offset = 0 + var oldOffset = offset + var len = buf.readUInt16BE(offset) + + offset += 2 + + var data = buf.slice(offset, offset + len) + offset += len + + rtxt.decode.bytes = offset - oldOffset + return data +} + +rtxt.decode.bytes = 0 + +rtxt.encodingLength = function (data) { + return (Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data)) + 2 +} + +var rhinfo = exports.hinfo = {} + +rhinfo.encode = function (data, buf, offset) { + if (!buf) buf = Buffer(rhinfo.encodingLength(data)) + if (!offset) offset = 0 + + var oldOffset = offset + offset += 2 + string.encode(data.cpu, buf, offset) + offset += string.encode.bytes + string.encode(data.os, buf, offset) + offset += string.encode.bytes + buf.writeUInt16BE(offset - oldOffset - 2, oldOffset) + rhinfo.encode.bytes = offset - oldOffset + return buf +} + +rhinfo.encode.bytes = 0 + +rhinfo.decode = function (buf, offset) { + if (!offset) offset = 0 + + var oldOffset = offset + + var data = {} + offset += 2 + data.cpu = string.decode(buf, offset) + offset += string.decode.bytes + data.os = string.decode(buf, offset) + offset += string.decode.bytes + rhinfo.decode.bytes = offset - oldOffset + return data +} + +rhinfo.decode.bytes = 0 + +rhinfo.encodingLength = function (data) { + return string.encodingLength(data.cpu) + string.encodingLength(data.os) + 2 +} + +var rptr = exports.ptr = {} +var rcname = exports.cname = rptr +var rdname = exports.dname = rptr + +rptr.encode = function (data, buf, offset) { + if (!buf) buf = Buffer(rptr.encodingLength(data)) + if (!offset) offset = 0 + + name.encode(data, buf, offset + 2) + buf.writeUInt16BE(name.encode.bytes, offset) + rptr.encode.bytes = name.encode.bytes + 2 + return buf +} + +rptr.encode.bytes = 0 + +rptr.decode = function (buf, offset) { + if (!offset) offset = 0 + + var data = name.decode(buf, offset + 2) + rptr.decode.bytes = name.decode.bytes + 2 + return data +} + +rptr.decode.bytes = 0 + +rptr.encodingLength = function (data) { + return name.encodingLength(data) + 2 +} + +var rsrv = exports.srv = {} + +rsrv.encode = function (data, buf, offset) { + if (!buf) buf = Buffer(rsrv.encodingLength(data)) + if (!offset) offset = 0 + + buf.writeUInt16BE(data.priority || 0, offset + 2) + buf.writeUInt16BE(data.weight || 0, offset + 4) + buf.writeUInt16BE(data.port || 0, offset + 6) + name.encode(data.target, buf, offset + 8) + + var len = name.encode.bytes + 6 + buf.writeUInt16BE(len, offset) + + rsrv.encode.bytes = len + 2 + return buf +} + +rsrv.encode.bytes = 0 + +rsrv.decode = function (buf, offset) { + if (!offset) offset = 0 + + var len = buf.readUInt16BE(offset) + + var data = {} + data.priority = buf.readUInt16BE(offset + 2) + data.weight = buf.readUInt16BE(offset + 4) + data.port = buf.readUInt16BE(offset + 6) + data.target = name.decode(buf, offset + 8) + + rsrv.decode.bytes = len + 2 + return data +} + +rsrv.decode.bytes = 0 + +rsrv.encodingLength = function (data) { + return 8 + name.encodingLength(data.target) +} + +var ra = exports.a = {} + +ra.encode = function (host, buf, offset) { + if (!buf) buf = Buffer(ra.encodingLength(host)) + if (!offset) offset = 0 + + buf.writeUInt16BE(4, offset) + offset += 2 + ip.toBuffer(host, buf, offset) + ra.encode.bytes = 6 + return buf +} + +ra.encode.bytes = 0 + +ra.decode = function (buf, offset) { + if (!offset) offset = 0 + + offset += 2 + var host = ip.toString(buf, offset, 4) + ra.decode.bytes = 6 + return host +} + +ra.decode.bytes = 0 + +ra.encodingLength = function (host) { + return 6 +} + +var raaaa = exports.aaaa = {} + +raaaa.encode = function (host, buf, offset) { + if (!buf) buf = Buffer(raaaa.encodingLength(host)) + if (!offset) offset = 0 + + buf.writeUInt16BE(16, offset) + offset += 2 + ip.toBuffer(host, buf, offset) + raaaa.encode.bytes = 18 + return buf +} + +raaaa.encode.bytes = 0 + +raaaa.decode = function (buf, offset) { + if (!offset) offset = 0 + + offset += 2 + var host = ip.toString(buf, offset, 16) + raaaa.decode.bytes = 18 + return host +} + +raaaa.decode.bytes = 0 + +raaaa.encodingLength = function (host) { + return 18 +} + +var renc = exports.record = function (type) { + switch (type.toUpperCase()) { + case 'A': return ra + case 'PTR': return rptr + case 'CNAME': return rcname + case 'DNAME': return rdname + case 'TXT': return rtxt + case 'NULL': return rnull + case 'AAAA': return raaaa + case 'SRV': return rsrv + case 'HINFO': return rhinfo + } + return runknown +} + +var answer = exports.answer = {} + +answer.encode = function (a, buf, offset) { + if (!buf) buf = Buffer(answer.encodingLength(a)) + if (!offset) offset = 0 + + var oldOffset = offset + + name.encode(a.name, buf, offset) + offset += name.encode.bytes + + buf.writeUInt16BE(types.toType(a.type), offset) + + var klass = a.class === undefined ? 1 : a.class + if (a.flush) klass |= FLUSH_MASK // the 1st bit of the class is the flush bit + buf.writeUInt16BE(klass, offset + 2) + + buf.writeUInt32BE(a.ttl || 0, offset + 4) + + var enc = renc(a.type) + enc.encode(a.data, buf, offset + 8) + offset += 8 + enc.encode.bytes + + answer.encode.bytes = offset - oldOffset + return buf +} + +answer.encode.bytes = 0 + +answer.decode = function (buf, offset) { + if (!offset) offset = 0 + + var a = {} + var oldOffset = offset + + a.name = name.decode(buf, offset) + offset += name.decode.bytes + a.type = types.toString(buf.readUInt16BE(offset)) + a.class = buf.readUInt16BE(offset + 2) + a.ttl = buf.readUInt32BE(offset + 4) + + a.flush = !!(a.class & FLUSH_MASK) + if (a.flush) a.class &= NOT_FLUSH_MASK + + var enc = renc(a.type) + a.data = enc.decode(buf, offset + 8) + offset += 8 + enc.decode.bytes + + answer.decode.bytes = offset - oldOffset + return a +} + +answer.decode.bytes = 0 + +answer.encodingLength = function (a) { + return name.encodingLength(a.name) + 8 + renc(a.type).encodingLength(a.data) +} + +var question = exports.question = {} + +question.encode = function (q, buf, offset) { + if (!buf) buf = Buffer(question.encodingLength(q)) + if (!offset) offset = 0 + + var oldOffset = offset + + name.encode(q.name, buf, offset) + offset += name.encode.bytes + + buf.writeUInt16BE(types.toType(q.type), offset) + offset += 2 + + buf.writeUInt16BE(q.class === undefined ? 1 : q.class, offset) + offset += 2 + + question.encode.bytes = offset - oldOffset + return q +} + +question.encode.bytes = 0 + +question.decode = function (buf, offset) { + if (!offset) offset = 0 + + var oldOffset = offset + var q = {} + + q.name = name.decode(buf, offset) + offset += name.decode.bytes + + q.type = types.toString(buf.readUInt16BE(offset)) + offset += 2 + + q.class = buf.readUInt16BE(offset) + offset += 2 + + var qu = !!(q.class & QU_MASK) + if (qu) q.class &= NOT_QU_MASK + + question.decode.bytes = offset - oldOffset + return q +} + +question.decode.bytes = 0 + +question.encodingLength = function (q) { + return name.encodingLength(q.name) + 4 +} + +exports.encode = function (result, buf, offset) { + if (!buf) buf = Buffer(exports.encodingLength(result)) + if (!offset) offset = 0 + + var oldOffset = offset + + if (!result.questions) result.questions = [] + if (!result.answers) result.answers = [] + if (!result.authorities) result.authorities = [] + if (!result.additionals) result.additionals = [] + + header.encode(result, buf, offset) + offset += header.encode.bytes + + offset = encodeList(result.questions, question, buf, offset) + offset = encodeList(result.answers, answer, buf, offset) + offset = encodeList(result.authorities, answer, buf, offset) + offset = encodeList(result.additionals, answer, buf, offset) + + exports.encode.bytes = offset - oldOffset + + return buf +} + +exports.encode.bytes = 0 + +exports.decode = function (buf, offset) { + if (!offset) offset = 0 + + var oldOffset = offset + var result = header.decode(buf, offset) + offset += header.decode.bytes + + offset = decodeList(result.questions, question, buf, offset) + offset = decodeList(result.answers, answer, buf, offset) + offset = decodeList(result.authorities, answer, buf, offset) + offset = decodeList(result.additionals, answer, buf, offset) + + exports.decode.bytes = offset - oldOffset + + return result +} + +exports.decode.bytes = 0 + +exports.encodingLength = function (result) { + return header.encodingLength(result) + + encodingLengthList(result.questions || [], question) + + encodingLengthList(result.answers || [], answer) + + encodingLengthList(result.authorities || [], answer) + + encodingLengthList(result.additionals || [], answer) +} + +function encodingLengthList (list, enc) { + var len = 0 + for (var i = 0; i < list.length; i++) len += enc.encodingLength(list[i]) + return len +} + +function encodeList (list, enc, buf, offset) { + for (var i = 0; i < list.length; i++) { + enc.encode(list[i], buf, offset) + offset += enc.encode.bytes + } + return offset +} + +function decodeList (list, enc, buf, offset) { + for (var i = 0; i < list.length; i++) { + list[i] = enc.decode(buf, offset) + offset += enc.decode.bytes + } + return offset +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..04e2bee --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "dns-packet", + "version": "0.0.0", + "description": "An abstract-encoding compliant module for encoding / decoding DNS packets", + "repository": { + "type": "git", + "url": "https://github.com/mafintosh/dns-packet" + }, + "dependencies": { + "ip": "^1.1.0" + }, + "devDependencies": { + "standard": "^6.0.5", + "tape": "^4.4.0" + }, + "scripts": { + "test": "standard && tape test.js" + }, + "bugs": { + "url": "https://github.com/mafintosh/dns-packet/issues" + }, + "homepage": "https://github.com/mafintosh/dns-packet", + "main": "index.js", + "keywords": [ + "dns", + "packet", + "encodings", + "encoding", + "encoder", + "abstract-encoding" + ], + "author": "Mathias Buus", + "license": "MIT" +} diff --git a/test.js b/test.js new file mode 100644 index 0000000..4176482 --- /dev/null +++ b/test.js @@ -0,0 +1,186 @@ +var tape = require('tape') +var packet = require('./') + +tape('unknown', function (t) { + testEncoder(t, packet.unknown, Buffer('hello world')) + t.end() +}) + +tape('txt', function (t) { + testEncoder(t, packet.txt, Buffer(0)) + testEncoder(t, packet.txt, Buffer('hello world')) + testEncoder(t, packet.txt, Buffer([0, 1, 2, 3, 4, 5])) + t.end() +}) + +tape('null', function (t) { + testEncoder(t, packet.null, Buffer([0, 1, 2, 3, 4, 5])) + t.end() +}) + +tape('hinfo', function (t) { + testEncoder(t, packet.hinfo, {cpu: 'intel', os: 'best one'}) + t.end() +}) + +tape('ptr', function (t) { + testEncoder(t, packet.ptr, 'hello.world.com') + t.end() +}) + +tape('cname', function (t) { + testEncoder(t, packet.cname, 'hello.cname.world.com') + t.end() +}) + +tape('dname', function (t) { + testEncoder(t, packet.dname, 'hello.dname.world.com') + t.end() +}) + +tape('srv', function (t) { + testEncoder(t, packet.srv, {port: 9999, target: 'hello.world.com'}) + testEncoder(t, packet.srv, {port: 9999, target: 'hello.world.com', priority: 42, weight: 10}) + t.end() +}) + +tape('a', function (t) { + testEncoder(t, packet.a, '127.0.0.1') + t.end() +}) + +tape('aaaa', function (t) { + testEncoder(t, packet.aaaa, 'fe80::1') + t.end() +}) + +tape('query', function (t) { + testEncoder(t, packet, { + type: 'query', + questions: [{ + type: 'A', + name: 'hello.a.com' + }, { + type: 'SRV', + name: 'hello.srv.com' + }] + }) + + testEncoder(t, packet, { + type: 'query', + id: 42, + questions: [{ + type: 'A', + name: 'hello.a.com' + }, { + type: 'SRV', + name: 'hello.srv.com' + }] + }) + + testEncoder(t, packet, { + type: 'query', + id: 42, + questions: [{ + type: 'A', + class: 100, + name: 'hello.a.com' + }, { + type: 'SRV', + name: 'hello.srv.com' + }] + }) + + t.end() +}) + +tape('response', function (t) { + testEncoder(t, packet, { + type: 'response', + answers: [{ + type: 'A', + name: 'hello.a.com', + data: '127.0.0.1' + }, { + type: 'SRV', + name: 'hello.srv.com', + data: { + port: 9090, + target: 'hello.target.com' + } + }, { + type: 'CNAME', + name: 'hello.cname.com', + data: 'hello.other.domain.com' + }] + }) + + testEncoder(t, packet, { + type: 'response', + id: 100, + additionals: [{ + type: 'AAAA', + name: 'hello.a.com', + data: 'fe80::1' + }, { + type: 'PTR', + name: 'hello.ptr.com', + data: 'hello.other.ptr.com' + }, { + type: 'SRV', + name: 'hello.srv.com', + ttl: 42, + data: { + port: 9090, + target: 'hello.target.com' + } + }], + answers: [{ + type: 'NULL', + name: 'hello.null.com', + data: Buffer([1, 2, 3, 4, 5]) + }] + }) + + t.end() +}) + +function testEncoder (t, packet, val) { + var buf = packet.encode(val) + var val2 = packet.decode(buf) + + t.same(buf.length, packet.encode.bytes, 'encode.bytes was set correctly') + t.same(buf.length, packet.encodingLength(val), 'encoding length matches') + t.ok(compare(t, val, val2), 'decoded object match') + + var buf2 = packet.encode(val2) + var val3 = packet.decode(buf2) + + t.same(buf2.length, packet.encode.bytes, 'encode.bytes was set correctly on re-encode') + t.same(buf2.length, packet.encodingLength(val), 'encoding length matches on re-encode') + + t.ok(compare(t, val, val3), 'decoded object match on re-encode') + t.ok(compare(t, val2, val3), 're-encoded decoded object match on re-encode') + + var bigger = Buffer(buf2.length + 10) + + var buf3 = packet.encode(val, bigger, 10) + var val4 = packet.decode(buf3, 10) + + t.ok(buf3 === bigger, 'echoes buffer on external buffer') + t.same(packet.encode.bytes, buf.length, 'encode.bytes is the same on external buffer') + t.ok(compare(t, val, val4), 'decoded object match on external buffer') +} + +function compare (t, a, b) { + if (Buffer.isBuffer(a)) return a.toString('hex') === b.toString('hex') + if (typeof a === 'object' && a && b) { + var keys = Object.keys(a) + for (var i = 0; i < keys.length; i++) { + if (!compare(t, a[keys[i]], b[keys[i]])) return false + } + } else { + return a === b + } + return true +} diff --git a/types.js b/types.js new file mode 100644 index 0000000..7a29d00 --- /dev/null +++ b/types.js @@ -0,0 +1,100 @@ +exports.toString = function (type) { + switch (type) { + case 1: return 'A' + case 10: return 'NULL' + case 28: return 'AAAA' + case 18: return 'AFSDB' + case 42: return 'APL' + case 257: return 'CAA' + case 60: return 'CDNSKEY' + case 59: return 'CDS' + case 37: return 'CERT' + case 5: return 'CNAME' + case 49: return 'DHCID' + case 32769: return 'DLV' + case 39: return 'DNAME' + case 48: return 'DNSKEY' + case 43: return 'DS' + case 55: return 'HIP' + case 13: return 'HINFO' + case 45: return 'IPSECKEY' + case 25: return 'KEY' + case 36: return 'KX' + case 29: return 'LOC' + case 15: return 'MX' + case 35: return 'NAPTR' + case 2: return 'NS' + case 47: return 'NSEC' + case 50: return 'NSEC3' + case 51: return 'NSEC3PARAM' + case 12: return 'PTR' + case 46: return 'RRSIG' + case 17: return 'RP' + case 24: return 'SIG' + case 6: return 'SOA' + case 99: return 'SPF' + case 33: return 'SRV' + case 44: return 'SSHFP' + case 32768: return 'TA' + case 249: return 'TKEY' + case 52: return 'TLSA' + case 250: return 'TSIG' + case 16: return 'TXT' + case 252: return 'AXFR' + case 251: return 'IXFR' + case 41: return 'OPT' + case 255: return 'ANY' + } + return 'UNKNOWN_' + type +} + +exports.toType = function (name) { + switch (name.toUpperCase()) { + case 'A': return 1 + case 'NULL': return 10 + case 'AAAA': return 28 + case 'AFSDB': return 18 + case 'APL': return 42 + case 'CAA': return 257 + case 'CDNSKEY': return 60 + case 'CDS': return 59 + case 'CERT': return 37 + case 'CNAME': return 5 + case 'DHCID': return 49 + case 'DLV': return 32769 + case 'DNAME': return 39 + case 'DNSKEY': return 48 + case 'DS': return 43 + case 'HIP': return 55 + case 'HINFO': return 13 + case 'IPSECKEY': return 45 + case 'KEY': return 25 + case 'KX': return 36 + case 'LOC': return 29 + case 'MX': return 15 + case 'NAPTR': return 35 + case 'NS': return 2 + case 'NSEC': return 47 + case 'NSEC3': return 50 + case 'NSEC3PARAM': return 51 + case 'PTR': return 12 + case 'RRSIG': return 46 + case 'RP': return 17 + case 'SIG': return 24 + case 'SOA': return 6 + case 'SPF': return 99 + case 'SRV': return 33 + case 'SSHFP': return 44 + case 'TA': return 32768 + case 'TKEY': return 249 + case 'TLSA': return 52 + case 'TSIG': return 250 + case 'TXT': return 16 + case 'AXFR': return 252 + case 'IXFR': return 251 + case 'OPT': return 41 + case 'ANY': return 255 + case '*': return 255 + } + return 0 +}