diff --git a/pure-parser.js b/pure-parser.js new file mode 100644 index 0000000..0244dff --- /dev/null +++ b/pure-parser.js @@ -0,0 +1,235 @@ +'use strict'; + +// pass a terminal arg +var filename = process.argv[2]; +if (!filename) { + console.error("Usage: node aj-listener.js [count]"); + console.error("Example: node aj-listener.js _service 0"); + process.exit(1); +} + + +var PromiseA = require('bluebird'); +var fs = PromiseA.promisifyAll(require('fs')); + +fs.readFileAsync(filename, null).then(function (nb) { + // nb is a Uint8Array (ArrayBufferView) for nb.buffer + // nb.buffer is the actual ArrayBuffer + pdns.unpack(nb.buffer); +}); + +var pdns = module.exports; + +// 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 = { + 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.packHeader = function(h) { + var val = 0; + + val += (h.qr << 15) & 0x8000; + val += (h.opcode << 11) & 0x7800; + val += (h.aa << 10) & 0x400; + val += (h.tc << 9) & 0x200; + val += (h.rd << 8) & 0x100; + val += (h.ra << 7) & 0x80; + val += (h.res1 << 6) & 0x40; + val += (h.res2 << 5) & 0x20; + val += (h.res3 << 4) & 0x10; + val += h.rcode & 0xF; + + return val; +}; + +pdns.unpackQname = function (ui8) { + var total = 0; + var i; + + var len; + var q = { + byteLength: 0 + , name: '' + , type: 0 + , class: 0 + }; + var str = []; + + do { + len = ui8[total]; + //str.length = 0; // fast empty array + if (ui8.byteLength - total < len) { + throw new Error( + "Expected a string of length " + len + + " but packet itself has " + (ui8.byteLength - total) + " bytes remaining" + ); + } + for (i = 0; i < len; i += 1) { + total += 1; + // TODO check url-allowable characters + str.push(String.fromCharCode(ui8[total])); + } + total += 1; + if (ui8[total]) { + // pushd connecting '.', but not trailing + str.push('.'); + } + //console.log('total', total, ui8[total], String.fromCharCode(ui8[total])); + } while (len); + + //str.pop(); // remove trailing '.' + + q.name = str.join(''); + + return q; +}; + +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); + // 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); + var header = pdns.unpackHeader(dv.getUint16(2)); + var qdcount = dv.getUint16(4); // query count + var ancount = dv.getUint16(6); // answer count + var nscount = dv.getUint16(8); // authority count + var arcount = dv.getUint16(10); // additional count + var total = 12; + var data; + var i; + var q; + + //console.log('datalen', data.length); + console.log(dv, qdcount); + + // TODO move to pdns.unpackQuestion to make testable + function unpackQuestion() { + data = new Uint8Array(ab).slice(total); + q = pdns.unpackQname(data); + total += q.name.length + 2; // account for leading and trailing string length byte + + + 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); + console.log('type', q.type, total); + total += 2; + q.class = dv.getUint16(total); + console.log('class', q.class, total); + total += 2; + console.log('total', total); + + header.questions.push(q); + } + + function unpackAnswer(answers) { + data = new Uint8Array(ab).slice(total); + q = pdns.unpackQname(data); + total += q.name.length + 2; // account for leading and trailing string length byte + + + if (ab.byteLength - total < 10) { + // console.error(str.join('')); + throw 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" + ); + } + + q.type = dv.getUint16(total); + console.log('type', q.type, total); + + total += 2; + q.class = dv.getUint16(total); + console.log('class', q.class, total); + + total += 2; + q.ttl = dv.getUint32(total); + console.log('ttl', q.ttl, total); + + total += 4; + q.rdlength = dv.getUint16(total); + console.log('rdlength', q.rdlength, total); + + total += 2; + // TODO actually parse rdata + q.rdata = new Uint8Array(ab).slice(ab.byteLength - total, ab.byteLength + -total + q.rdlength); + q.rdata = Array.prototype.slice(q.data); + console.log('total', total); + + total += q.rdlength; + console.log('total', total); + + answers.push(q); + } + + console.log('qdcount', qdcount); + header.questions = []; + for (i = 0; i < qdcount; i += 1) { + unpackQuestion(); + } + + console.log('ancount', ancount); + header.answers = []; + for (i = 0; i < ancount; i += 1) { + unpackAnswer(header.answers); + } + + console.log('nscount', nscount); + header.authority = []; + for (i = 0; i < nscount; i += 1) { + unpackAnswer(header.authority); + } + + console.log('arcount', arcount); + header.additional = []; + for (i = 0; i < arcount; i += 1) { + unpackAnswer(header.additional); + } + + console.log('packet', header); +};