780 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			780 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | // Copyright 2011 Timothy J Fontaine <tjfontaine@gmail.com>
 | ||
|  | //
 | ||
|  | // 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
 | ||
|  | 
 | ||
|  | // TODO: change the default UDP packet size that node-dns sends
 | ||
|  | //       from 4096 to conform to these:
 | ||
|  | //       - [requestor's payload size](https://tools.ietf.org/html/rfc6891#section-6.2.3)
 | ||
|  | //       - [responders's payload size](https://tools.ietf.org/html/rfc6891#section-6.2.4)
 | ||
|  | 
 | ||
|  | 'use strict'; | ||
|  | 
 | ||
|  | var consts = require('./consts'), | ||
|  |     BufferCursor = require('buffercursor'), | ||
|  |     BufferCursorOverflow = BufferCursor.BufferCursorOverflow, | ||
|  |     ipaddr = require('ipaddr.js'), | ||
|  |     assert = require('assert'), | ||
|  |     util = require('util'); | ||
|  | 
 | ||
|  | function assertUndefined(val, msg) { | ||
|  |   assert(typeof val != 'undefined', msg); | ||
|  | } | ||
|  | 
 | ||
|  | var Packet = module.exports = function() { | ||
|  |   this.header = { | ||
|  |     id: 0, | ||
|  |     qr: 0, | ||
|  |     opcode: 0, | ||
|  |     aa: 0, | ||
|  |     tc: 0, | ||
|  |     rd: 1, | ||
|  |     ra: 0, | ||
|  |     res1: 0, | ||
|  |     res2: 0, | ||
|  |     res3: 0, | ||
|  |     rcode: 0 | ||
|  |   }; | ||
|  |   this.question = []; | ||
|  |   this.answer = []; | ||
|  |   this.authority = []; | ||
|  |   this.additional = []; | ||
|  |   this.edns_options = [];   // TODO: DEPRECATED! Use `.edns.options` instead!
 | ||
|  |   this.payload = undefined; // TODO: DEPRECATED! Use `.edns.payload` instead!
 | ||
|  | }; | ||
|  | 
 | ||
|  | var LABEL_POINTER = 0xC0; | ||
|  | 
 | ||
|  | var isPointer = function(len) { | ||
|  |   return (len & LABEL_POINTER) === LABEL_POINTER; | ||
|  | }; | ||
|  | 
 | ||
|  | function nameUnpack(buff) { | ||
|  |   var len, comp, end, pos, part, combine = ''; | ||
|  | 
 | ||
|  |   len = buff.readUInt8(); | ||
|  |   comp = false; | ||
|  |   end = buff.tell(); | ||
|  | 
 | ||
|  |   while (len !== 0) { | ||
|  |     if (isPointer(len)) { | ||
|  |       len -= LABEL_POINTER; | ||
|  |       len = len << 8; | ||
|  |       pos = len + buff.readUInt8(); | ||
|  |       if (!comp) | ||
|  |         end = buff.tell(); | ||
|  |       buff.seek(pos); | ||
|  |       len = buff.readUInt8(); | ||
|  |       comp = true; | ||
|  |       continue; | ||
|  |     } | ||
|  | 
 | ||
|  |     part = buff.toString('ascii', len); | ||
|  | 
 | ||
|  |     if (combine.length) | ||
|  |       combine = combine + '.' + part; | ||
|  |     else | ||
|  |       combine = part; | ||
|  | 
 | ||
|  |     len = buff.readUInt8(); | ||
|  | 
 | ||
|  |     if (!comp) | ||
|  |       end = buff.tell(); | ||
|  |   } | ||
|  | 
 | ||
|  |   buff.seek(end); | ||
|  | 
 | ||
|  |   return combine; | ||
|  | } | ||
|  | 
 | ||
|  | function namePack(str, buff, index) { | ||
|  |   var offset, dot, part; | ||
|  | 
 | ||
|  |   while (str) { | ||
|  |     if (index[str]) { | ||
|  |       offset = (LABEL_POINTER << 8) + index[str]; | ||
|  |       buff.writeUInt16BE(offset); | ||
|  |       break; | ||
|  |     } else { | ||
|  |       index[str] = buff.tell(); | ||
|  |       dot = str.indexOf('.'); | ||
|  |       if (dot > -1) { | ||
|  |         part = str.slice(0, dot); | ||
|  |         str = str.slice(dot + 1); | ||
|  |       } else { | ||
|  |         part = str; | ||
|  |         str = undefined; | ||
|  |       } | ||
|  |       buff.writeUInt8(part.length); | ||
|  |       buff.write(part, part.length, 'ascii'); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   if (!str) { | ||
|  |     buff.writeUInt8(0); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | var | ||
|  |   WRITE_HEADER              = 100001, | ||
|  |   WRITE_TRUNCATE            = 100002, | ||
|  |   WRITE_QUESTION            = 100003, | ||
|  |   WRITE_RESOURCE_RECORD     = 100004, | ||
|  |   WRITE_RESOURCE_WRITE      = 100005, | ||
|  |   WRITE_RESOURCE_DONE       = 100006, | ||
|  |   WRITE_RESOURCE_END        = 100007, | ||
|  |   WRITE_EDNS                = 100008, | ||
|  |   WRITE_END                 = 100009, | ||
|  |   WRITE_A     = consts.NAME_TO_QTYPE.A, | ||
|  |   WRITE_AAAA  = consts.NAME_TO_QTYPE.AAAA, | ||
|  |   WRITE_NS    = consts.NAME_TO_QTYPE.NS, | ||
|  |   WRITE_CNAME = consts.NAME_TO_QTYPE.CNAME, | ||
|  |   WRITE_PTR   = consts.NAME_TO_QTYPE.PTR, | ||
|  |   WRITE_SPF   = consts.NAME_TO_QTYPE.SPF, | ||
|  |   WRITE_MX    = consts.NAME_TO_QTYPE.MX, | ||
|  |   WRITE_SRV   = consts.NAME_TO_QTYPE.SRV, | ||
|  |   WRITE_TXT   = consts.NAME_TO_QTYPE.TXT, | ||
|  |   WRITE_SOA   = consts.NAME_TO_QTYPE.SOA, | ||
|  |   WRITE_OPT   = consts.NAME_TO_QTYPE.OPT, | ||
|  |   WRITE_NAPTR = consts.NAME_TO_QTYPE.NAPTR, | ||
|  |   WRITE_TLSA  = consts.NAME_TO_QTYPE.TLSA; | ||
|  | 
 | ||
|  | function writeHeader(buff, packet) { | ||
|  |   assert(packet.header, 'Packet requires "header"'); | ||
|  |   buff.writeUInt16BE(packet.header.id & 0xFFFF); | ||
|  |   var val = 0; | ||
|  |   val += (packet.header.qr << 15) & 0x8000; | ||
|  |   val += (packet.header.opcode << 11) & 0x7800; | ||
|  |   val += (packet.header.aa << 10) & 0x400; | ||
|  |   val += (packet.header.tc << 9) & 0x200; | ||
|  |   val += (packet.header.rd << 8) & 0x100; | ||
|  |   val += (packet.header.ra << 7) & 0x80; | ||
|  |   val += (packet.header.res1 << 6) & 0x40; | ||
|  |   val += (packet.header.res2 << 5) & 0x20; | ||
|  |   val += (packet.header.res3 << 4) & 0x10; | ||
|  |   val += packet.header.rcode & 0xF; | ||
|  |   buff.writeUInt16BE(val & 0xFFFF); | ||
|  |   assert(packet.question.length == 1, 'DNS requires one question'); | ||
|  |   // aren't used
 | ||
|  |   buff.writeUInt16BE(1); | ||
|  |   // answer offset 6
 | ||
|  |   buff.writeUInt16BE(packet.answer.length & 0xFFFF); | ||
|  |   // authority offset 8
 | ||
|  |   buff.writeUInt16BE(packet.authority.length & 0xFFFF); | ||
|  |   // additional offset 10
 | ||
|  |   buff.writeUInt16BE(packet.additional.length & 0xFFFF); | ||
|  |   return WRITE_QUESTION; | ||
|  | } | ||
|  | 
 | ||
|  | function writeTruncate(buff, packet, section, val) { | ||
|  |   // XXX FIXME TODO truncation is currently done wrong.
 | ||
|  |   // Quote rfc2181 section 9
 | ||
|  |   // The TC bit should not be set merely because some extra information
 | ||
|  |   // could have been included, but there was insufficient room.  This
 | ||
|  |   // includes the results of additional section processing.  In such cases
 | ||
|  |   // the entire RRSet that will not fit in the response should be omitted,
 | ||
|  |   // and the reply sent as is, with the TC bit clear.  If the recipient of
 | ||
|  |   // the reply needs the omitted data, it can construct a query for that
 | ||
|  |   // data and send that separately.
 | ||
|  |   //
 | ||
|  |   // TODO IOW only set TC if we hit it in ANSWERS otherwise make sure an
 | ||
|  |   // entire RRSet is removed during a truncation.
 | ||
|  |   var pos; | ||
|  | 
 | ||
|  |   buff.seek(2); | ||
|  |   val = buff.readUInt16BE(); | ||
|  |   val |= (1 << 9) & 0x200; | ||
|  |   buff.seek(2); | ||
|  |   buff.writeUInt16BE(val); | ||
|  |   switch (section) { | ||
|  |     case 'answer': | ||
|  |       pos = 6; | ||
|  |       // seek to authority and clear it and additional out
 | ||
|  |       buff.seek(8); | ||
|  |       buff.writeUInt16BE(0); | ||
|  |       buff.writeUInt16BE(0); | ||
|  |       break; | ||
|  |     case 'authority': | ||
|  |       pos = 8; | ||
|  |       // seek to additional and clear it out
 | ||
|  |       buff.seek(10); | ||
|  |       buff.writeUInt16BE(0); | ||
|  |       break; | ||
|  |     case 'additional': | ||
|  |       pos = 10; | ||
|  |       break; | ||
|  |   } | ||
|  |   buff.seek(pos); | ||
|  |   buff.writeUInt16BE(count - 1); // TODO: count not defined!
 | ||
|  |   buff.seek(last_resource);      // TODO: last_resource not defined!
 | ||
|  |   return WRITE_END; | ||
|  | } | ||
|  | 
 | ||
|  | function writeQuestion(buff, val, label_index) { | ||
|  |   assert(val, 'Packet requires a question'); | ||
|  |   assertUndefined(val.name, 'Question requires a "name"'); | ||
|  |   assertUndefined(val.type, 'Question requires a "type"'); | ||
|  |   assertUndefined(val.class, 'Questionn requires a "class"'); | ||
|  |   namePack(val.name, buff, label_index); | ||
|  |   buff.writeUInt16BE(val.type & 0xFFFF); | ||
|  |   buff.writeUInt16BE(val.class & 0xFFFF); | ||
|  |   return WRITE_RESOURCE_RECORD; | ||
|  | } | ||
|  | 
 | ||
|  | function writeResource(buff, val, label_index, rdata) { | ||
|  |   assert(val, 'Resource must be defined'); | ||
|  |   assertUndefined(val.name, 'Resource record requires "name"'); | ||
|  |   assertUndefined(val.type, 'Resource record requires "type"'); | ||
|  |   assertUndefined(val.class, 'Resource record requires "class"'); | ||
|  |   assertUndefined(val.ttl, 'Resource record requires "ttl"'); | ||
|  |   namePack(val.name, buff, label_index); | ||
|  |   buff.writeUInt16BE(val.type & 0xFFFF); | ||
|  |   buff.writeUInt16BE(val.class & 0xFFFF); | ||
|  |   buff.writeUInt32BE(val.ttl & 0xFFFFFFFF); | ||
|  |   rdata.pos = buff.tell(); | ||
|  |   buff.writeUInt16BE(0); // if there is rdata, then this value will be updated
 | ||
|  |                          // to the correct value by 'writeResourceDone'
 | ||
|  |   return val.type; | ||
|  | } | ||
|  | 
 | ||
|  | function writeResourceDone(buff, rdata) { | ||
|  |   var pos = buff.tell(); | ||
|  |   buff.seek(rdata.pos); | ||
|  |   buff.writeUInt16BE(pos - rdata.pos - 2); | ||
|  |   buff.seek(pos); | ||
|  |   return WRITE_RESOURCE_RECORD; | ||
|  | } | ||
|  | 
 | ||
|  | function writeIp(buff, val) { | ||
|  |   //TODO XXX FIXME -- assert that address is of proper type
 | ||
|  |   assertUndefined(val.address, 'A/AAAA record requires "address"'); | ||
|  |   val = ipaddr.parse(val.address).toByteArray(); | ||
|  |   val.forEach(function(b) { | ||
|  |     buff.writeUInt8(b); | ||
|  |   }); | ||
|  |   return WRITE_RESOURCE_DONE; | ||
|  | } | ||
|  | 
 | ||
|  | function writeCname(buff, val, label_index) { | ||
|  |   assertUndefined(val.data, 'NS/CNAME/PTR record requires "data"'); | ||
|  |   namePack(val.data, buff, label_index); | ||
|  |   return WRITE_RESOURCE_DONE; | ||
|  | } | ||
|  | 
 | ||
|  | // For <character-string> see: http://tools.ietf.org/html/rfc1035#section-3.3
 | ||
|  | // For TXT: http://tools.ietf.org/html/rfc1035#section-3.3.14
 | ||
|  | function writeTxt(buff, val) { | ||
|  |   //TODO XXX FIXME -- split on max char string and loop
 | ||
|  |   assertUndefined(val.data, 'TXT record requires "data"'); | ||
|  |   for (var i=0,len=val.data.length; i<len; i++) { | ||
|  |     var dataLen = Buffer.byteLength(val.data[i], 'utf8'); | ||
|  |     buff.writeUInt8(dataLen); | ||
|  |     buff.write(val.data[i], dataLen, 'utf8'); | ||
|  |   } | ||
|  |   return WRITE_RESOURCE_DONE; | ||
|  | } | ||
|  | 
 | ||
|  | function writeMx(buff, val, label_index) { | ||
|  |   assertUndefined(val.priority, 'MX record requires "priority"'); | ||
|  |   assertUndefined(val.exchange, 'MX record requires "exchange"'); | ||
|  |   buff.writeUInt16BE(val.priority & 0xFFFF); | ||
|  |   namePack(val.exchange, buff, label_index); | ||
|  |   return WRITE_RESOURCE_DONE; | ||
|  | } | ||
|  | 
 | ||
|  | // SRV: https://tools.ietf.org/html/rfc2782
 | ||
|  | // TODO: SRV fixture failing for '_xmpp-server._tcp.gmail.com.srv.js'
 | ||
|  | function writeSrv(buff, val, label_index) { | ||
|  |   assertUndefined(val.priority, 'SRV record requires "priority"'); | ||
|  |   assertUndefined(val.weight, 'SRV record requires "weight"'); | ||
|  |   assertUndefined(val.port, 'SRV record requires "port"'); | ||
|  |   assertUndefined(val.target, 'SRV record requires "target"'); | ||
|  |   buff.writeUInt16BE(val.priority & 0xFFFF); | ||
|  |   buff.writeUInt16BE(val.weight & 0xFFFF); | ||
|  |   buff.writeUInt16BE(val.port & 0xFFFF); | ||
|  |   namePack(val.target, buff, label_index); | ||
|  |   return WRITE_RESOURCE_DONE; | ||
|  | } | ||
|  | 
 | ||
|  | function writeSoa(buff, val, label_index) { | ||
|  |   assertUndefined(val.primary, 'SOA record requires "primary"'); | ||
|  |   assertUndefined(val.admin, 'SOA record requires "admin"'); | ||
|  |   assertUndefined(val.serial, 'SOA record requires "serial"'); | ||
|  |   assertUndefined(val.refresh, 'SOA record requires "refresh"'); | ||
|  |   assertUndefined(val.retry, 'SOA record requires "retry"'); | ||
|  |   assertUndefined(val.expiration, 'SOA record requires "expiration"'); | ||
|  |   assertUndefined(val.minimum, 'SOA record requires "minimum"'); | ||
|  |   namePack(val.primary, buff, label_index); | ||
|  |   namePack(val.admin, buff, label_index); | ||
|  |   buff.writeUInt32BE(val.serial & 0xFFFFFFFF); | ||
|  |   buff.writeInt32BE(val.refresh & 0xFFFFFFFF); | ||
|  |   buff.writeInt32BE(val.retry & 0xFFFFFFFF); | ||
|  |   buff.writeInt32BE(val.expiration & 0xFFFFFFFF); | ||
|  |   buff.writeInt32BE(val.minimum & 0xFFFFFFFF); | ||
|  |   return WRITE_RESOURCE_DONE; | ||
|  | } | ||
|  | 
 | ||
|  | // http://tools.ietf.org/html/rfc3403#section-4.1
 | ||
|  | function writeNaptr(buff, val, label_index) { | ||
|  |   assertUndefined(val.order, 'NAPTR record requires "order"'); | ||
|  |   assertUndefined(val.preference, 'NAPTR record requires "preference"'); | ||
|  |   assertUndefined(val.flags, 'NAPTR record requires "flags"'); | ||
|  |   assertUndefined(val.service, 'NAPTR record requires "service"'); | ||
|  |   assertUndefined(val.regexp, 'NAPTR record requires "regexp"'); | ||
|  |   assertUndefined(val.replacement, 'NAPTR record requires "replacement"'); | ||
|  |   buff.writeUInt16BE(val.order & 0xFFFF); | ||
|  |   buff.writeUInt16BE(val.preference & 0xFFFF); | ||
|  |   buff.writeUInt8(val.flags.length); | ||
|  |   buff.write(val.flags, val.flags.length, 'ascii'); | ||
|  |   buff.writeUInt8(val.service.length); | ||
|  |   buff.write(val.service, val.service.length, 'ascii'); | ||
|  |   buff.writeUInt8(val.regexp.length); | ||
|  |   buff.write(val.regexp, val.regexp.length, 'ascii'); | ||
|  |   namePack(val.replacement, buff, label_index); | ||
|  |   return WRITE_RESOURCE_DONE; | ||
|  | } | ||
|  | 
 | ||
|  | // https://tools.ietf.org/html/rfc6698
 | ||
|  | function writeTlsa(buff, val) { | ||
|  |   assertUndefined(val.usage, 'TLSA record requires "usage"'); | ||
|  |   assertUndefined(val.selector, 'TLSA record requires "selector"'); | ||
|  |   assertUndefined(val.matchingtype, 'TLSA record requires "matchingtype"'); | ||
|  |   assertUndefined(val.buff, 'TLSA record requires "buff"'); | ||
|  |   buff.writeUInt8(val.usage); | ||
|  |   buff.writeUInt8(val.selector); | ||
|  |   buff.writeUInt8(val.matchingtype); | ||
|  |   buff.copy(val.buff); | ||
|  |   return WRITE_RESOURCE_DONE; | ||
|  | } | ||
|  | 
 | ||
|  | function makeEdns(packet) { | ||
|  |   packet.edns = { | ||
|  |     name: '', | ||
|  |     type: consts.NAME_TO_QTYPE.OPT, | ||
|  |     class: packet.payload, | ||
|  |     options: [], | ||
|  |     ttl: 0 | ||
|  |   }; | ||
|  |   packet.edns_options = packet.edns.options; // TODO: 'edns_options' is DEPRECATED!
 | ||
|  |   packet.additional.push(packet.edns); | ||
|  |   return WRITE_HEADER; | ||
|  | } | ||
|  | 
 | ||
|  | function writeOpt(buff, val) { | ||
|  |   var opt; | ||
|  |   for (var i=0, len=val.options.length; i<len; i++) { | ||
|  |     opt = val.options[i]; | ||
|  |     buff.writeUInt16BE(opt.code); | ||
|  |     buff.writeUInt16BE(opt.data.length); | ||
|  |     buff.copy(opt.data); | ||
|  |   } | ||
|  |   return WRITE_RESOURCE_DONE; | ||
|  | } | ||
|  | 
 | ||
|  | Packet.write = function(buff, packet) { | ||
|  |   var state = WRITE_HEADER, | ||
|  |       val, | ||
|  |       section, | ||
|  |       count, | ||
|  |       rdata, | ||
|  |       last_resource, | ||
|  |       label_index = {}; | ||
|  | 
 | ||
|  |   buff = new BufferCursor(buff); | ||
|  | 
 | ||
|  |   // the existence of 'edns' in a packet indicates that a proper OPT record exists
 | ||
|  |   // in 'additional' and that all of the other fields in packet (that are parsed by
 | ||
|  |   // 'parseOpt') are properly set. If it does not exist, we assume that the user
 | ||
|  |   // is requesting that we create one for them.
 | ||
|  |   if (typeof packet.edns_version !== 'undefined' && typeof packet.edns === "undefined") | ||
|  |     state = makeEdns(packet); | ||
|  | 
 | ||
|  |   // TODO: this is unnecessarily inefficient. rewrite this using a
 | ||
|  |   //       function table instead. (same for Packet.parse too).
 | ||
|  |   while (true) { | ||
|  |     try { | ||
|  |       switch (state) { | ||
|  |         case WRITE_HEADER: | ||
|  |           state = writeHeader(buff, packet); | ||
|  |           break; | ||
|  |         case WRITE_TRUNCATE: | ||
|  |           state = writeTruncate(buff, packet, section, last_resource); | ||
|  |           break; | ||
|  |         case WRITE_QUESTION: | ||
|  |           state = writeQuestion(buff, packet.question[0], label_index); | ||
|  |           section = 'answer'; | ||
|  |           count = 0; | ||
|  |           break; | ||
|  |         case WRITE_RESOURCE_RECORD: | ||
|  |           last_resource = buff.tell(); | ||
|  |           if (packet[section].length == count) { | ||
|  |             switch (section) { | ||
|  |               case 'answer': | ||
|  |                 section = 'authority'; | ||
|  |                 state = WRITE_RESOURCE_RECORD; | ||
|  |                 break; | ||
|  |               case 'authority': | ||
|  |                 section = 'additional'; | ||
|  |                 state = WRITE_RESOURCE_RECORD; | ||
|  |                 break; | ||
|  |               case 'additional': | ||
|  |                 state = WRITE_END; | ||
|  |                 break; | ||
|  |             } | ||
|  |             count = 0; | ||
|  |           } else { | ||
|  |             state = WRITE_RESOURCE_WRITE; | ||
|  |           } | ||
|  |           break; | ||
|  |         case WRITE_RESOURCE_WRITE: | ||
|  |           rdata = {}; | ||
|  |           val = packet[section][count]; | ||
|  |           state = writeResource(buff, val, label_index, rdata); | ||
|  |           break; | ||
|  |         case WRITE_RESOURCE_DONE: | ||
|  |           count += 1; | ||
|  |           state = writeResourceDone(buff, rdata); | ||
|  |           break; | ||
|  |         case WRITE_A: | ||
|  |         case WRITE_AAAA: | ||
|  |           state = writeIp(buff, val); | ||
|  |           break; | ||
|  |         case WRITE_NS: | ||
|  |         case WRITE_CNAME: | ||
|  |         case WRITE_PTR: | ||
|  |           state = writeCname(buff, val, label_index); | ||
|  |           break; | ||
|  |         case WRITE_SPF: | ||
|  |         case WRITE_TXT: | ||
|  |           state = writeTxt(buff, val); | ||
|  |           break; | ||
|  |         case WRITE_MX: | ||
|  |           state = writeMx(buff, val, label_index); | ||
|  |           break; | ||
|  |         case WRITE_SRV: | ||
|  |           state = writeSrv(buff, val, label_index); | ||
|  |           break; | ||
|  |         case WRITE_SOA: | ||
|  |           state = writeSoa(buff, val, label_index); | ||
|  |           break; | ||
|  |         case WRITE_OPT: | ||
|  |           state = writeOpt(buff, val); | ||
|  |           break; | ||
|  |         case WRITE_NAPTR: | ||
|  |           state = writeNaptr(buff, val, label_index); | ||
|  |           break; | ||
|  |         case WRITE_TLSA: | ||
|  |           state = writeTlsa(buff, val); | ||
|  |           break; | ||
|  |         case WRITE_END: | ||
|  |           return buff.tell(); | ||
|  |         default: | ||
|  |           if (typeof val.data !== 'object') | ||
|  |             throw new Error('Packet.write Unknown State: ' + state); | ||
|  |           // write unhandled RR type
 | ||
|  |           buff.copy(val.data); | ||
|  |           state = WRITE_RESOURCE_DONE; | ||
|  |       } | ||
|  |     } catch (e) { | ||
|  |       if (e instanceof BufferCursorOverflow) { | ||
|  |         state = WRITE_TRUNCATE; | ||
|  |       } else { | ||
|  |         throw e; | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | function parseHeader(msg, packet) { | ||
|  |   packet.header.id = msg.readUInt16BE(); | ||
|  |   var val = msg.readUInt16BE(); | ||
|  |   packet.header.qr = (val & 0x8000) >> 15; | ||
|  |   packet.header.opcode = (val & 0x7800) >> 11; | ||
|  |   packet.header.aa = (val & 0x400) >> 10; | ||
|  |   packet.header.tc = (val & 0x200) >> 9; | ||
|  |   packet.header.rd = (val & 0x100) >> 8; | ||
|  |   packet.header.ra = (val & 0x80) >> 7; | ||
|  |   packet.header.res1 = (val & 0x40) >> 6; | ||
|  |   packet.header.res2 = (val & 0x20) >> 5; | ||
|  |   packet.header.res3 = (val & 0x10) >> 4; | ||
|  |   packet.header.rcode = (val & 0xF); | ||
|  |   packet.question = new Array(msg.readUInt16BE()); | ||
|  |   packet.answer = new Array(msg.readUInt16BE()); | ||
|  |   packet.authority = new Array(msg.readUInt16BE()); | ||
|  |   packet.additional = new Array(msg.readUInt16BE()); | ||
|  |   return PARSE_QUESTION; | ||
|  | } | ||
|  | 
 | ||
|  | function parseQuestion(msg, packet) { | ||
|  |   var val = {}; | ||
|  |   val.name = nameUnpack(msg); | ||
|  |   val.type = msg.readUInt16BE(); | ||
|  |   val.class = msg.readUInt16BE(); | ||
|  |   packet.question[0] = val; | ||
|  |   assert(packet.question.length === 1); | ||
|  |   // TODO handle qdcount > 1 in practice no one sends this
 | ||
|  |   return PARSE_RESOURCE_RECORD; | ||
|  | } | ||
|  | 
 | ||
|  | function parseRR(msg, val, rdata) { | ||
|  |   val.name = nameUnpack(msg); | ||
|  |   val.type = msg.readUInt16BE(); | ||
|  |   val.class = msg.readUInt16BE(); | ||
|  |   val.ttl = msg.readUInt32BE(); | ||
|  |   rdata.len = msg.readUInt16BE(); | ||
|  |   return val.type; | ||
|  | } | ||
|  | 
 | ||
|  | function parseA(val, msg) { | ||
|  |   var address = '' + | ||
|  |     msg.readUInt8() + | ||
|  |     '.' + msg.readUInt8() + | ||
|  |     '.' + msg.readUInt8() + | ||
|  |     '.' + msg.readUInt8(); | ||
|  |   val.address = address; | ||
|  |   return PARSE_RESOURCE_DONE; | ||
|  | } | ||
|  | 
 | ||
|  | function parseAAAA(val, msg) { | ||
|  |   var address = ''; | ||
|  |   var compressed = false; | ||
|  | 
 | ||
|  |   for (var i = 0; i < 8; i++) { | ||
|  |     if (i > 0) address += ':'; | ||
|  |     // TODO zero compression
 | ||
|  |     address += msg.readUInt16BE().toString(16); | ||
|  |   } | ||
|  |   val.address = address; | ||
|  |   return PARSE_RESOURCE_DONE; | ||
|  | } | ||
|  | 
 | ||
|  | function parseCname(val, msg) { | ||
|  |   val.data = nameUnpack(msg); | ||
|  |   return PARSE_RESOURCE_DONE; | ||
|  | } | ||
|  | 
 | ||
|  | function parseTxt(val, msg, rdata) { | ||
|  |   val.data = []; | ||
|  |   var end = msg.tell() + rdata.len; | ||
|  |   while (msg.tell() != end) { | ||
|  |     var len = msg.readUInt8(); | ||
|  |     val.data.push(msg.toString('utf8', len)); | ||
|  |   } | ||
|  |   return PARSE_RESOURCE_DONE; | ||
|  | } | ||
|  | 
 | ||
|  | function parseMx(val, msg, rdata) { | ||
|  |   val.priority = msg.readUInt16BE(); | ||
|  |   val.exchange = nameUnpack(msg); | ||
|  |   return PARSE_RESOURCE_DONE; | ||
|  | } | ||
|  | 
 | ||
|  | // TODO: SRV fixture failing for '_xmpp-server._tcp.gmail.com.srv.js'
 | ||
|  | //       https://tools.ietf.org/html/rfc2782
 | ||
|  | function parseSrv(val, msg) { | ||
|  |   val.priority = msg.readUInt16BE(); | ||
|  |   val.weight = msg.readUInt16BE(); | ||
|  |   val.port = msg.readUInt16BE(); | ||
|  |   val.target = nameUnpack(msg); | ||
|  |   return PARSE_RESOURCE_DONE; | ||
|  | } | ||
|  | 
 | ||
|  | function parseSoa(val, msg) { | ||
|  |   val.primary = nameUnpack(msg); | ||
|  |   val.admin = nameUnpack(msg); | ||
|  |   val.serial = msg.readUInt32BE(); | ||
|  |   val.refresh = msg.readInt32BE(); | ||
|  |   val.retry = msg.readInt32BE(); | ||
|  |   val.expiration = msg.readInt32BE(); | ||
|  |   val.minimum = msg.readInt32BE(); | ||
|  |   return PARSE_RESOURCE_DONE; | ||
|  | } | ||
|  | 
 | ||
|  | // http://tools.ietf.org/html/rfc3403#section-4.1
 | ||
|  | function parseNaptr(val, msg) { | ||
|  |   val.order = msg.readUInt16BE(); | ||
|  |   val.preference = msg.readUInt16BE(); | ||
|  |   var len = msg.readUInt8(); | ||
|  |   val.flags = msg.toString('ascii', len); | ||
|  |   len = msg.readUInt8(); | ||
|  |   val.service = msg.toString('ascii', len); | ||
|  |   len = msg.readUInt8(); | ||
|  |   val.regexp = msg.toString('ascii', len); | ||
|  |   val.replacement = nameUnpack(msg); | ||
|  |   return PARSE_RESOURCE_DONE; | ||
|  | } | ||
|  | 
 | ||
|  | function parseTlsa(val, msg, rdata) { | ||
|  |   val.usage = msg.readUInt8(); | ||
|  |   val.selector = msg.readUInt8(); | ||
|  |   val.matchingtype = msg.readUInt8(); | ||
|  |   val.buff = msg.slice(rdata.len - 3).buffer; // 3 because of the 3 UInt8s above.
 | ||
|  |   return PARSE_RESOURCE_DONE; | ||
|  | } | ||
|  | 
 | ||
|  | // https://tools.ietf.org/html/rfc6891#section-6.1.2
 | ||
|  | // https://tools.ietf.org/html/rfc2671#section-4.4
 | ||
|  | //       - [payload size selection](https://tools.ietf.org/html/rfc6891#section-6.2.5)
 | ||
|  | function parseOpt(val, msg, rdata, packet) { | ||
|  |   // assert first entry in additional
 | ||
|  |   rdata.buf = msg.slice(rdata.len); | ||
|  | 
 | ||
|  |   val.rcode = ((val.ttl & 0xFF000000) >> 20) + packet.header.rcode; | ||
|  |   val.version = (val.ttl >> 16) & 0xFF; | ||
|  |   val.do = (val.ttl >> 15) & 1; | ||
|  |   val.z = val.ttl & 0x7F; | ||
|  |   val.options = []; | ||
|  | 
 | ||
|  |   packet.edns = val; | ||
|  |   packet.edns_version = val.version; // TODO: return BADVERS for unsupported version! (Section 6.1.3)
 | ||
|  | 
 | ||
|  |   // !! BEGIN DEPRECATION NOTICE !!
 | ||
|  |   // THESE FIELDS MAY BE REMOVED IN THE FUTURE!
 | ||
|  |   packet.edns_options = val.options; | ||
|  |   packet.payload = val.class; | ||
|  |   // !! END DEPRECATION NOTICE !!
 | ||
|  | 
 | ||
|  |   while (!rdata.buf.eof()) { | ||
|  |     val.options.push({ | ||
|  |       code: rdata.buf.readUInt16BE(), | ||
|  |       data: rdata.buf.slice(rdata.buf.readUInt16BE()).buffer | ||
|  |     }); | ||
|  |   } | ||
|  |   return PARSE_RESOURCE_DONE; | ||
|  | } | ||
|  | 
 | ||
|  | var | ||
|  |   PARSE_HEADER          = 100000, | ||
|  |   PARSE_QUESTION        = 100001, | ||
|  |   PARSE_RESOURCE_RECORD = 100002, | ||
|  |   PARSE_RR_UNPACK       = 100003, | ||
|  |   PARSE_RESOURCE_DONE   = 100004, | ||
|  |   PARSE_END             = 100005, | ||
|  |   PARSE_A     = consts.NAME_TO_QTYPE.A, | ||
|  |   PARSE_NS    = consts.NAME_TO_QTYPE.NS, | ||
|  |   PARSE_CNAME = consts.NAME_TO_QTYPE.CNAME, | ||
|  |   PARSE_SOA   = consts.NAME_TO_QTYPE.SOA, | ||
|  |   PARSE_PTR   = consts.NAME_TO_QTYPE.PTR, | ||
|  |   PARSE_MX    = consts.NAME_TO_QTYPE.MX, | ||
|  |   PARSE_TXT   = consts.NAME_TO_QTYPE.TXT, | ||
|  |   PARSE_AAAA  = consts.NAME_TO_QTYPE.AAAA, | ||
|  |   PARSE_SRV   = consts.NAME_TO_QTYPE.SRV, | ||
|  |   PARSE_NAPTR = consts.NAME_TO_QTYPE.NAPTR, | ||
|  |   PARSE_OPT   = consts.NAME_TO_QTYPE.OPT, | ||
|  |   PARSE_SPF   = consts.NAME_TO_QTYPE.SPF, | ||
|  |   PARSE_TLSA  = consts.NAME_TO_QTYPE.TLSA; | ||
|  |    | ||
|  | 
 | ||
|  | Packet.parse = function(msg) { | ||
|  |   var state, | ||
|  |       pos, | ||
|  |       val, | ||
|  |       rdata, | ||
|  |       section, | ||
|  |       count; | ||
|  | 
 | ||
|  |   var packet = new Packet(); | ||
|  | 
 | ||
|  |   pos = 0; | ||
|  |   state = PARSE_HEADER; | ||
|  | 
 | ||
|  |   msg = new BufferCursor(msg); | ||
|  | 
 | ||
|  |   while (true) { | ||
|  |     switch (state) { | ||
|  |       case PARSE_HEADER: | ||
|  |         state = parseHeader(msg, packet); | ||
|  |         break; | ||
|  |       case PARSE_QUESTION: | ||
|  |         state = parseQuestion(msg, packet); | ||
|  |         section = 'answer'; | ||
|  |         count = 0; | ||
|  |         break; | ||
|  |       case PARSE_RESOURCE_RECORD: | ||
|  |         // console.log('PARSE_RESOURCE_RECORD: count = %d, %s.len = %d', count, section, packet[section].length);
 | ||
|  |         if (count === packet[section].length) { | ||
|  |           switch (section) { | ||
|  |             case 'answer': | ||
|  |               section = 'authority'; | ||
|  |               count = 0; | ||
|  |               break; | ||
|  |             case 'authority': | ||
|  |               section = 'additional'; | ||
|  |               count = 0; | ||
|  |               break; | ||
|  |             case 'additional': | ||
|  |               state = PARSE_END; | ||
|  |               break; | ||
|  |           } | ||
|  |         } else { | ||
|  |           state = PARSE_RR_UNPACK; | ||
|  |         } | ||
|  |         break; | ||
|  |       case PARSE_RR_UNPACK: | ||
|  |         val = {}; | ||
|  |         rdata = {}; | ||
|  |         state = parseRR(msg, val, rdata); | ||
|  |         break; | ||
|  |       case PARSE_RESOURCE_DONE: | ||
|  |         packet[section][count++] = val; | ||
|  |         state = PARSE_RESOURCE_RECORD; | ||
|  |         break; | ||
|  |       case PARSE_A: | ||
|  |         state = parseA(val, msg); | ||
|  |         break; | ||
|  |       case PARSE_AAAA: | ||
|  |         state = parseAAAA(val, msg); | ||
|  |         break; | ||
|  |       case PARSE_NS: | ||
|  |       case PARSE_CNAME: | ||
|  |       case PARSE_PTR: | ||
|  |         state = parseCname(val, msg); | ||
|  |         break; | ||
|  |       case PARSE_SPF: | ||
|  |       case PARSE_TXT: | ||
|  |         state = parseTxt(val, msg, rdata); | ||
|  |         break; | ||
|  |       case PARSE_MX: | ||
|  |         state = parseMx(val, msg); | ||
|  |         break; | ||
|  |       case PARSE_SRV: | ||
|  |         state = parseSrv(val, msg); | ||
|  |         break; | ||
|  |       case PARSE_SOA: | ||
|  |         state = parseSoa(val, msg); | ||
|  |         break; | ||
|  |       case PARSE_OPT: | ||
|  |         state = parseOpt(val, msg, rdata, packet); | ||
|  |         break; | ||
|  |       case PARSE_NAPTR: | ||
|  |         state = parseNaptr(val, msg); | ||
|  |         break; | ||
|  |       case PARSE_TLSA: | ||
|  |         state = parseTlsa(val, msg, rdata); | ||
|  |         break; | ||
|  |       case PARSE_END: | ||
|  |         return packet; | ||
|  |       default: | ||
|  |         //console.log(state, val);
 | ||
|  |         val.data = msg.slice(rdata.len); | ||
|  |         state = PARSE_RESOURCE_DONE; | ||
|  |         break; | ||
|  |     } | ||
|  |   } | ||
|  | }; |