;(function (exports) { 'use strict'; var pdns = exports.DNS_PARSER = {}; var classes = exports.DNS_CLASSES || require('./dns.classes.js').DNS_CLASSES; var types = exports.DNS_TYPES || require('./dns.types.js').DNS_TYPES; // Order http://www.zytrax.com/books/dns/ch15/ pdns.unpackHeader = function (i) { // i is one element from a Uint16Array (as a 16-bit unsigned integer) var header = { id: 0 // added here to preserve console.log order , qr: (i & 0x8000) >> 15 // Query Response (0 is question, 1 is response) , opcode: (i & 0x7800) >> 11 // 0 is question , aa: (i & 0x400) >> 10 // Authoritative Answer (response-only) , tc: (i & 0x200) >> 9 // TrunCated - expect another packet with same (?) id , rd: (i & 0x100) >> 8 // Recursion Desired , ra: (i & 0x80) >> 7 , res1: (i & 0x40) >> 6 // z , res2: (i & 0x20) >> 5 // ad , res3: (i & 0x10) >> 4 // cd , rcode: (i & 0xF) // Error Code (response-only) }; return header; }; pdns._unpackLabels = exports.DNS_UNPACK_LABELS || require('./dns.unpack-labels.js').DNS_UNPACK_LABELS; var optWarned = false; pdns.unpackOpt = function (ab, packet, rec) { var dv; // https://tools.ietf.org/html/rfc6891#section-6 if (!optWarned) { console.warn('OPT is not yet supported'); optWarned = true; } if ('undefined' !== typeof packet.edns_version) { console.warn("More that one OPT, should respond with FORMERR, but not implemented"); } if (packet.name) { console.warn("name '" + packet.name + "' should not exist for type OPT 0x29 41"); } packet.payload = rec.class; // TODO index into the correct place in the ArrayBuffer dv = new DataView(new ArrayBuffer(4)); // rec.ttl is actually 1 (extended_rcode), 1 (edns_version), 2:1 (DO), 2:7 (Z) dv.setUint32(0, packet.ttl, false); // is xrcode this edns_options? packet.xrcode = '0x' + dv.getUint8(0, false).toString(16); if ('0x0' === packet.xrcode) { // use 4-bit rcode instead of 12-bit rcode } else { // shift xrcode up by 4 bits (8 bits + 4 trailing 0s) // OR with existing rcode (now 12-bits total) console.warn('extended_rcode not supported yet'); } packet.edns_version = dv.getUint8(1, false); packet.do = dv.getUint8(2, false) & 0x8000; // 1000 0000 packet.z = dv.getUint16(2, false) & 0x7FFF; // 0111 1111 /* "edns_options": [], "payload": 4096, "edns_version": 0, "do": 0 */ }; pdns.unpack = function (ab) { if (ab.buffer) { ab = ab.buffer; } // SANITY Check if (ab.byteLength < 12) { throw new Error( "A DNS header has a minimum length of 12 bytes but this packet has only " + ab.byteLength + "bytes." ); } // DO: new Uint8Array(arrayBuffer); // DO NOT: Uint8Array.from(arrayBuffer); // WILL NOT WORK // DO: new DataView(arrayBuffer).getUint16(7, false); // DO NOT: arrayBuffer.slice(); // // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer // https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/slice // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView var dv = new DataView(ab); var id = dv.getUint16(0, false); var packet = {}; packet.header = pdns.unpackHeader(dv.getUint16(2, false)); packet.qdcount = dv.getUint16(4, false); // query count packet.ancount = dv.getUint16(6, false); // answer count packet.nscount = dv.getUint16(8, false); // authority count packet.arcount = dv.getUint16(10, false); // additional count var total = 12; var i; var rec; var ui8 = new Uint8Array(ab); // TODO move to pdns.unpackQuestion to make testable function unpackQuestion(ab, dv, ui8, total) { var ototal = total; var q = pdns._unpackLabels(ui8, total, { name: '' // ex: daplie.com , type: 0 // ex: 1 , typeName: '' // ex: A , class: 0 // ex: 1 , className: '' // ex: IN , byteLength: 0 // the total byte length (including pointers, but not their labels) , labels: [] // an array of the labels individually , cpcount: 0 // the number of compression pointers traversed }); //console.log('unpackQuestion QNAME:'); //console.log(q); total += q.byteLength; if (ab.byteLength - total < 4) { // console.error(str.join('')); throw new Error( "Expected a 2-byte QTYPE and 2-byte QCLASS following " + total + "-byte QNAME" + " but packet itself has " + (ab.byteLength - total) + " bytes remaining" ); } q.type = dv.getUint16(total, false); total += 2; q.class = dv.getUint16(total, false); total += 2; q.byteLength = total - ototal; // mDNS uses the MS bit of class to request a unicast response q.unicastResponse = (q.class & 0x8000) !== 0; q.class &= 0x7fff; q.className = classes[q.class]; q.typeName = types[q.type]; return q; } function unpackAnswer(ab, dv, ui8, total) { var ototal = total; var err; var q = pdns._unpackLabels(ui8, total, { name: '' , type: 0 , typeName: '' , class: 0 , className: '' , byteLength: 0 , labels: [] , cpcount: 0 , rdstart: 0 //, rdata: 0 , rdlength: 0 }); //console.log('unpackAnswer [Q]NAME:'); //console.log(q); total += q.byteLength; q.className = classes[q.class]; q.typeName = types[q.type]; if (ab.byteLength - total < 10) { // console.error(str.join('')); //console.error(JSON.stringify(packet, null, 1)); //console.error(JSON.stringify(q, null, 1)); err = new Error( "Expected a 2-byte QTYPE, 2-byte QCLASS, 4-byte TTL, and 2-byte RDLENGTH following " + total + "-byte QNAME" + " but packet itself has " + (ab.byteLength - total) + " bytes remaining" ); err.packet = packet; err.record = q; throw err; } q.type = dv.getUint16(total, false); total += 2; q.class = dv.getUint16(total, false); total += 2; q.ttl = dv.getUint32(total, false); total += 4; q.rdlength = dv.getUint16(total, false); total += 2; q.class &= 0x7fff; q.className = classes[q.class]; q.typeName = types[q.type]; // TODO actually parse RDATA q.rdstart = total; // console.log('q.rdata', q.rdlength, 'bytes:'); // console.log(new Uint8Array(ab).slice(total, total + q.rdlength)); //q.rdata = Array.prototype.slice.apply(q.rdata); //q.rdend = q.rdstart + q.rdlength; total += q.rdlength; q.byteLength = total - ototal; return q; } packet.header.id = id; packet.question = []; for (i = 0; i < packet.qdcount; i += 1) { rec = unpackQuestion(ab, dv, ui8, total); total += rec.byteLength; packet.question.push(rec); } packet.answer = []; for (i = 0; i < packet.ancount; i += 1) { rec = unpackAnswer(ab, dv, ui8, total); total += rec.byteLength; packet.answer.push(rec); } packet.authority = []; for (i = 0; i < packet.nscount; i += 1) { rec = unpackAnswer(ab, dv, ui8, total); total += rec.byteLength; packet.authority.push(rec); } packet.edns_options = []; packet.additional = []; for (i = 0; i < packet.arcount; i += 1) { rec = unpackAnswer(ab, dv, ui8, total); total += rec.byteLength; if (0x29 === rec.type) { // OPT 41 (0x29) pdns.unpackOpt(ab, packet, rec); continue; } packet.additional.push(rec); } if (ab.byteLength !== total) { throw new Error( "Parsed " + total + " bytes, but packet is " + ab.byteLength + " bytes." ); } packet.byteLength = total; return packet; }; pdns.unpackRdata = exports.DNS_RDATA_PARSE || require('./dns.rdata.parse.js').DNS_RDATA_PARSE; }('undefined' !== typeof window ? window : exports));