UNPKG

dnssd

Version:

Bonjour/Avahi-like service discovery in pure JavaScript

427 lines (331 loc) 14 kB
const fs = require('fs'); const path = require('path'); const chai = require('chai'); const expect = chai.expect; const rewire = require('rewire'); const sinon = require('sinon'); const sinonChai = require('sinon-chai'); chai.use(sinonChai); const dir = process['test-dir'] || '../../src'; const BufferWrapper = require(dir + '/BufferWrapper'); const ResourceRecord = require(dir + '/ResourceRecord'); const QueryRecord = require(dir + '/QueryRecord'); const hex = require(dir + '/hex'); const filename = require('path').basename(__filename); const debug = require(dir + '/debug')(`dnssd:${filename}`); const Packet = rewire(dir + '/Packet'); describe('Packet', function() { const packetDir = path.resolve(__dirname, '../data/packets/'); describe('#constructor', function() { it('should start with an empty packet', function() { expect((new Packet()).isEmpty()).to.be.true; }); it('should create a packet from a given buffer', sinon.test(function() { this.stub(Packet.prototype, 'parseBuffer'); const fakebuffer = {}; const packet = new Packet(fakebuffer); expect(packet.parseBuffer).to.have.been.calledWith(fakebuffer); })); it('should make .isInvalid() false if parsing fails', sinon.test(function() { this.stub(Packet.prototype, 'parseBuffer').throws(); this.stub(Packet.prototype, 'isValid').returns(true); const fakebuffer = {}; const packet = new Packet(fakebuffer); expect(packet.isValid()).to.be.false; })); }); describe('#parseHeader', function() { it('should parse out header fields', function() { const buf = new Buffer([0, 37, 0, 0, 0, 4, 0, 3, 0, 2, 0, 1]); const wrapper = new BufferWrapper(buf); const header = Packet.prototype.parseHeader(wrapper); expect(header.ID).to.equal(37); expect(header.QDCount).to.equal(4); expect(header.ANCount).to.equal(3); expect(header.NSCount).to.equal(2); expect(header.ARCount).to.equal(1); }); }); describe('#writeHeader', function() { it('should write the header correctly', function() { const packet = new Packet(); packet.header.ID = 45; packet.header.QR = 1; packet.header.OPCODE = 5; packet.header.AA = 0; packet.header.TC = 1; packet.header.RD = 0; packet.header.RA = 0; packet.header.Z = 0; packet.header.AD = 0; packet.header.CD = 0; packet.header.RCODE = 1; packet.header.QDCount = 1; packet.header.ANCount = 2; packet.header.NSCount = 3; packet.header.ARCount = 4; packet.questions = [1]; // length = 1 packet.answers = [1, 2]; // length = 2 packet.authorities = [1, 2, 3]; // length = 3 packet.additionals = [1, 2, 3, 4]; // length = 4 const wrapper = new BufferWrapper(); packet.writeHeader(wrapper); wrapper.seek(0); // reset position const header = Packet.prototype.parseHeader(wrapper); expect(header).to.eql(packet.header); }); }); describe('#parseBuffer', function() { const isResourceRecord = record => record instanceof ResourceRecord; const isQueryRecord = record => record instanceof QueryRecord; function expectRightTypes(packet) { expect(packet.questions.every(isQueryRecord)).to.be.true; expect(packet.answers.every(isResourceRecord)).to.be.true; expect(packet.additionals.every(isResourceRecord)).to.be.true; expect(packet.authorities.every(isResourceRecord)).to.be.true; } function generateTestFn(file, runTests) { return function() { const data = fs.readFileSync(packetDir + '/' + file); const packet = new Packet(data); if (debug.v.isEnabled) debug.v('%s:\n%s', file, hex.view(data)); debug('%s:\n%s', file, packet); expectRightTypes(packet); runTests(packet); }; } function test(file, runTests) { return it(file, generateTestFn(file, runTests)); } test.only = function(file, runTests) { return it.only(file, generateTestFn(file, runTests)); }; describe('should parse uncompressed packetDir', function() { test('service probe.uncompressed.bin', function(packet) { expect(packet.isProbe()).to.be.true; expect(packet.questions).to.have.lengthOf(1); expect(packet.authorities).to.have.lengthOf(1); }); test('service announcement.uncompressed.bin', function(packet) { expect(packet.isAnswer()).to.be.true; expect(packet.answers).to.have.lengthOf(4); expect(packet.additionals).to.have.lengthOf(4); }); test('service goodbye.uncompressed.bin', function(packet) { expect(packet.isAnswer()).to.be.true; expect(packet.answers).to.have.lengthOf(1); }); test('service announcement with large TXT.uncompressed.bin', function(packet) { expect(packet.isAnswer()).to.be.true; expect(packet.answers).to.have.lengthOf(4); expect(packet.additionals).to.have.lengthOf(4); }); test('enumerate query.uncompressed.bin', function(packet) { expect(packet.isQuery()).to.be.true; expect(packet.questions).to.have.lengthOf(5); }); test('query with known answer.uncompressed.bin', function(packet) { expect(packet.isQuery()).to.be.true; expect(packet.questions).to.have.lengthOf(1); expect(packet.answers).to.have.lengthOf(1); }); test('oddly repeated questions.uncompressed.bin', function(packet) { expect(packet.isQuery()).to.be.true; expect(packet.questions).to.have.lengthOf(4); expect(packet.answers).to.have.lengthOf(1); }); test('query with lots of known answers.uncompressed.bin', function(packet) { expect(packet.isQuery()).to.be.true; expect(packet.questions).to.have.lengthOf(1); expect(packet.answers).to.have.lengthOf(9); }); test('multiple queries, known answer, opt.uncompressed.bin', function(packet) { expect(packet.isQuery()).to.be.true; expect(packet.questions).to.have.lengthOf(8); expect(packet.answers).to.have.lengthOf(1); expect(packet.additionals).to.have.lengthOf(1); }); test('answer with HINFO.uncompressed.bin', function(packet) { expect(packet.isAnswer()).to.be.true; expect(packet.answers).to.have.lengthOf(1); expect(packet.additionals).to.have.lengthOf(1); }); test('multiple announce with OPT.uncompressed.bin', function(packet) { expect(packet.isAnswer()).to.be.true; expect(packet.answers).to.have.lengthOf(8); expect(packet.additionals).to.have.lengthOf(5); }); test('chromecast probe.uncompressed.bin', function(packet) { expect(packet.isProbe()).to.be.true; expect(packet.questions).to.have.lengthOf(2); expect(packet.authorities).to.have.lengthOf(3); }); }); describe('should parse compressed packetDir', function() { test('service probe.bin', function(packet) { expect(packet.isProbe()).to.be.true; expect(packet.questions).to.have.lengthOf(1); expect(packet.authorities).to.have.lengthOf(1); }); test('service announcement.bin', function(packet) { expect(packet.isAnswer()).to.be.true; expect(packet.answers).to.have.lengthOf(4); expect(packet.additionals).to.have.lengthOf(4); }); test('service goodbye.bin', function(packet) { expect(packet.isAnswer()).to.be.true; expect(packet.answers).to.have.lengthOf(1); }); test('service announcement with large TXT.bin', function(packet) { expect(packet.isAnswer()).to.be.true; expect(packet.answers).to.have.lengthOf(4); expect(packet.additionals).to.have.lengthOf(4); }); test('enumerate query.bin', function(packet) { expect(packet.isQuery()).to.be.true; expect(packet.questions).to.have.lengthOf(5); }); test('query with known answer.bin', function(packet) { expect(packet.isQuery()).to.be.true; expect(packet.questions).to.have.lengthOf(1); expect(packet.answers).to.have.lengthOf(1); }); test('oddly repeated questions.bin', function(packet) { expect(packet.isQuery()).to.be.true; expect(packet.questions).to.have.lengthOf(4); expect(packet.answers).to.have.lengthOf(1); }); test('query with lots of known answers.bin', function(packet) { expect(packet.isQuery()).to.be.true; expect(packet.questions).to.have.lengthOf(1); expect(packet.answers).to.have.lengthOf(9); }); test('multiple queries, known answer, opt.bin', function(packet) { expect(packet.isQuery()).to.be.true; expect(packet.questions).to.have.lengthOf(8); expect(packet.answers).to.have.lengthOf(1); expect(packet.additionals).to.have.lengthOf(1); }); test('answer with HINFO.bin', function(packet) { expect(packet.isAnswer()).to.be.true; expect(packet.answers).to.have.lengthOf(1); expect(packet.additionals).to.have.lengthOf(1); }); test('multiple announce with OPT.bin', function(packet) { expect(packet.isAnswer()).to.be.true; expect(packet.answers).to.have.lengthOf(8); expect(packet.additionals).to.have.lengthOf(5); }); test('chromecast probe.bin', function(packet) { expect(packet.isProbe()).to.be.true; expect(packet.questions).to.have.lengthOf(2); expect(packet.authorities).to.have.lengthOf(3); }); }); }); describe('#toBuffer', function() { describe('should write packet to buffer and do label compression', function() { const compressedFiles = fs.readdirSync(packetDir) .filter(name => name.indexOf('uncompressed') === -1); compressedFiles.forEach((file) => { it(file, function() { const input = fs.readFileSync(packetDir + '/' + file); const packet = new Packet(input); const output = packet.toBuffer(); if (debug.v.isEnabled) { debug.v('%s:\n%s\n\nINPUT: \n%s\n\nOUTPUT: \n%s\n\nAre equal?: %s', file, packet, hex.view(input), hex.view(output), output.equals(input)); } expect(output.equals(input)).to.be.true; }); }); }); }); describe('#split', function() { it('should split answers in half', function() { const C = new ResourceRecord.TXT({name: 'C'}); const D = new ResourceRecord.TXT({name: 'D'}); const A = new ResourceRecord.TXT({name: 'A', additionals: [C]}); const B = new ResourceRecord.TXT({name: 'B', additionals: [D]}); const answerPacket = new Packet(); answerPacket.setAnswers([A, B]); answerPacket.setAdditionals([C, D]); answerPacket.setResponseBit(); const [one, two] = answerPacket.split(); expect(one.answers).to.eql([A]); expect(two.answers).to.eql([B]); expect(one.additionals).to.eql([C]); expect(two.additionals).to.eql([D]); }); it('should split questions in half', function() { const A = new QueryRecord({name: 'C'}); const B = new QueryRecord({name: 'D'}); const C = new ResourceRecord.TXT({name: 'A'}); const D = new ResourceRecord.TXT({name: 'B'}); const queryPacket = new Packet(); queryPacket.setQuestions([A, B]); queryPacket.setAnswers([C, D]); const [one, two] = queryPacket.split(); expect(one.questions).to.eql([A, B]); expect(two.questions).to.eql([]); expect(one.answers).to.eql([C]); expect(two.answers).to.eql([D]); }); it('should give up and return empty packets for anything else...', function() { const A = new QueryRecord({name: 'C'}); const B = new QueryRecord({name: 'D'}); const unknownPacket = new Packet(); unknownPacket.setAnswers([A, B]); unknownPacket.setAuthorities([A, B]); const [one, two] = unknownPacket.split(); expect(one.isEmpty()).to.be.true; expect(two.isEmpty()).to.be.true; }); }); describe('#equals', function() { const A = new QueryRecord({name: 'A'}); const B = new QueryRecord({name: 'B'}); const C = new ResourceRecord.TXT({name: 'C'}); const D = new ResourceRecord.TXT({name: 'D'}); const packet_1 = new Packet(); packet_1.setQuestions([A, B]); const packet_2 = new Packet(); packet_2.setAnswers([C, D]); const packet_3 = new Packet(); packet_3.setAnswers([A, B]); packet_3.setAdditionals([C]); const packet_4 = new Packet(); // <-- same, but diff object packet_4.setAnswers([A, B]); packet_4.setAdditionals([C]); const packet_5 = new Packet(); packet_5.setAnswers([A, A, B]); // <-- repeats packet_5.setAdditionals([C]); const packet_6 = new Packet(); // <-- empty it('return false if packets are not equal', function() { expect(packet_1.equals(packet_2)).to.be.false; expect(packet_2.equals(packet_1)).to.be.false; expect(packet_2.equals(packet_3)).to.be.false; expect(packet_4.equals(packet_5)).to.be.false; }); it('return true if packets are equal', function() { expect(packet_1.equals(packet_1)).to.be.true; expect(packet_3.equals(packet_4)).to.be.true; expect(packet_6.equals(packet_6)).to.be.true; }); }); describe('#toString', function() { describe('should look pretty and not throw', function() { const files = fs.readdirSync(packetDir); files.forEach((file) => { it(file, function() { const input = fs.readFileSync(packetDir + '/' + file); const packet = new Packet(input); debug('\n%s', packet.toString()); // shouldn't throw }); }); }); }); });