(function (exports) { 'use strict'; var classes = exports.DNS_CLASSES || require('./dns.classes.js').DNS_CLASSES; var types = exports.DNS_TYPES || require('./dns.types.js').DNS_TYPES; var dnspack = exports.DNS_PACKER = { pack: function (packet) { // TODO update for edns payload size var ab = new ArrayBuffer(576); // http://serverfault.com/a/587626/93930 var dv = new DataView(ab); var labelsMap = {}; var total = 0; var id = packet.header.id; // 2 bytes var header = dnspack.packHeader(packet.header); // 2 bytes total = 12; // 2+2+2+2 bytes ({qd,an,ns,ar}count) if (!id) { throw new Error("'id' should be set to a crypto random id"); } dv.setUint16(0, id, false); dv.setUint16(2, header, false); dv.setUint16(4, packet.question.length, false); dv.setUint16(6, (packet.answer||[]).length, false); dv.setUint16(8, (packet.authority||[]).length, false); // EDNS is added as an additional with TYPE 41 (OPT, 0x29) dv.setUint16(10, (packet.additional||[]).length + (packet.payload ? 1 : 0), false); function lint(r) { if (!r.name && 'string' !== typeof r.name) { throw new Error("missing name"); } if (!r.class) { if (!r.className) { throw new Error("no className"); } if (!classes[r.className]) { console.warn("ignoring invalid class '" + r.className + "' for '" + r.name); } else { r.class = classes[r.className]; } } if (!r.type) { if (!r.typeName) { throw new Error("no typeName"); } if (!types[r.typeName]) { console.warn("ignoring invalid type '" + r.typeName + "' for '" + r.name); } else { r.type = types[r.typeName]; } } } function packLabelSequence(sequence, terminates) { if (labelsMap[sequence]) { // minimal compression pointer 0xc0 (192) dv.setUint8(total, 0xc0, false); total += 1; dv.setUint8(total, labelsMap[sequence].total, false); total += 1; return; } if (terminates) { // we don't want non-terminating rdata cached, just terminating names labelsMap[total] = { total: total, name: sequence }; labelsMap[sequence] = labelsMap[total]; } // allow 0-length string for TLD root NS queries (sequence && sequence.split('.') || []).forEach(function (label) { dv.setUint8(total, label.length, false); total += 1; label.split('').forEach(function (ch) { dv.setUint8(total, ch.charCodeAt(0), false); total += 1; }); }); if (terminates) { // trailing 0 (null) as label sequence terminator dv.setUint8(total, 0, false); total += 1; } } function packQuestion(q) { lint(q); packLabelSequence(q.name, true); dv.setUint16(total, q.type || types[q.typeName], false); total += 2; dv.setUint16(total, q.class || classes[q.className], false); total += 2; } function packAnswer(a) { packQuestion(a); if ('undefined' === typeof a.ttl) { throw new Error("missing ttl"); } // 32-bit ttl dv.setUint32(total, a.ttl, false); total += 4; if (a.rdata) { // 16-bit dv.setUint16(total, a.rdata.byteLength, false); total += 2; (new Uint8Array(a.rdata)).forEach(function (b) { dv.setUint8(total, b, false); total += 1; }); } else { total = dnspack.packRdata(ab, dv, total, a); } } packet.question.forEach(packQuestion); (packet.answer||[]).forEach(packAnswer); (packet.authority||[]).forEach(packAnswer); (packet.additional||[]).forEach(packAnswer); // TODO handle compression pointers // TODO handle edns return ab.slice(0, total); } /* , packLabels: function(map, labels) { } */ , packHeader: function(h) { var val = 0; var req = h.opcode; var res = h.qr || h.aa || h.ra || h.rcode; if (req && res) { throw new Error("Both request-only and response-only headers are set: " + JSON.stringify(h)); } // TODO check if is request or response val += ((h.qr || 0) << 15) & 0x8000; val += ((h.opcode || 0) << 11) & 0x7800; val += ((h.aa || 0) << 10) & 0x400; val += ((h.tc || 0) << 9) & 0x200; val += ((h.rd || 0) << 8) & 0x100; val += ((h.ra || 0) << 7) & 0x80; val += ((h.res1 || 0) << 6) & 0x40; val += ((h.res2 || 0) << 5) & 0x20; val += ((h.res3 || 0) << 4) & 0x10; val += (h.rcode || 0) & 0xF; return val; } }; dnspack.packRdata = exports.DNS_RDATA_PACK || require('./dns.rdata.pack.js').DNS_RDATA_PACK; }('undefined' !== typeof window ? window : exports));