Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A bare-bones subtypes implementation #21

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 65 additions & 11 deletions lib/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ util.inherits(Browser, EventEmitter)
*/
function Browser (mdns, opts, onup) {
if (typeof opts === 'function') return new Browser(mdns, null, opts)
var self = this

EventEmitter.call(this)

Expand All @@ -38,11 +39,21 @@ function Browser (mdns, opts, onup) {
this._txt = dnsTxt(opts.txt)

if (!opts || !opts.type) {
this._name = WILDCARD
this._names = [ WILDCARD ]
this._wildcard = true
} else {
this._name = serviceName.stringify(opts.type, opts.protocol || 'tcp') + TLD
if (opts.name) this._name = opts.name + '.' + this._name
// When requesting specific subtypes, add those to the name map, else
// use the single service type and any optional name prefix.
if ((opts.subtypes === undefined) || (opts.subtypes.length === 0)) {
this._names = [ serviceName.stringify(opts.type, opts.protocol || 'tcp') + TLD ]
if (opts.name) this._name = opts.name + '.' + this._name
} else {
self._names = []
opts.subtypes.forEach(function (subtype) {
self._names.push('_' + subtype + '._sub.' +
serviceName.stringify(opts.type, opts.protocol || 'tcp') + TLD)
})
}
this._wildcard = false
}

Expand All @@ -55,19 +66,31 @@ function Browser (mdns, opts, onup) {

Browser.prototype.start = function () {
if (this._onresponse) return

var self = this
var nameMap = {}
var nameInMap = function (recordName) {
var nameMatch = false
self._names.forEach(function (name) {
if (name === recordName) {
nameMatch = true
}
})
return nameMatch
}

// List of names for the browser to listen for. In a normal search this will
// be the primary name stored on the browser. In case of a wildcard search
// the names will be determined at runtime as responses come in.
var nameMap = {}
if (!this._wildcard) nameMap[this._name] = true
if (!this._wildcard) {
this._names.forEach(function (name) {
nameMap[name] = true
})
}

this._onresponse = function (packet, rinfo) {
if (self._wildcard) {
packet.answers.forEach(function (answer) {
if (answer.type !== 'PTR' || answer.name !== self._name || answer.name in nameMap) return
if (answer.type !== 'PTR' || nameInMap(answer.map) || answer.name in nameMap) return
nameMap[answer.data] = true
self._mdns.query(answer.data, 'PTR')
})
Expand All @@ -82,8 +105,27 @@ Browser.prototype.start = function () {
if (matches.length === 0) return

matches.forEach(function (service) {
if (self._serviceMap[service.fqdn]) return // ignore already registered services
self._addService(service)
var serviceIndex = 0
if (self._serviceMap[service.fqdn]) {
// ignore already registered services, which exist is the new service
// has no subtype
if (service.subtypes.length === 0) return

// Check to see if this includes a subtype that didn't exist previously
// If so, add it to the service already cached and emit a CB
for (serviceIndex = 0; serviceIndex < self.services.length; serviceIndex += 1) {
if (self.services[serviceIndex].fqdn === service.fqdn) {
break
}
}
// If the service subtype type already exists in the service, ignore it.
if (self.services[serviceIndex].subtypes.indexOf(service.subtypes[0]) !== -1) return

self.services[serviceIndex].subtypes.push(service.subtypes[0])
self.emit('up', self.services[serviceIndex])
} else {
self._addService(service)
}
})
})
}
Expand All @@ -100,7 +142,10 @@ Browser.prototype.stop = function () {
}

Browser.prototype.update = function () {
this._mdns.query(this._name, 'PTR')
var self = this
this._names.forEach(function (name) {
self._mdns.query(name, 'PTR')
})
}

Browser.prototype._addService = function (service) {
Expand Down Expand Up @@ -166,14 +211,23 @@ function buildServicesFor (name, packet, txt, referer) {
var parts = rr.name.split('.')
var name = parts[0]
var types = serviceName.parse(parts.slice(1, -1).join('.'))
var subparts = ptr.name.split('.')
service.name = name
service.fqdn = rr.name
service.host = rr.data.target
service.referer = referer
service.port = rr.data.port
service.type = types.name
service.protocol = types.protocol
service.subtypes = types.subtypes

// If the subparts length is larger than the parts length, then
// there does indeed exist a subtype and we add that to the main
// service record.
if (subparts.length > (parts.length - 1)) {
service.subtypes = [ subparts[0].slice(1) ]
} else {
service.subtypes = []
}
} else if (rr.type === 'TXT') {
service.rawTxt = rr.data
service.txt = txt.decode(rr.data)
Expand Down
56 changes: 43 additions & 13 deletions lib/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ function Service (opts) {
this.type = serviceName.stringify(opts.type, this.protocol)
this.host = opts.host || os.hostname()
this.port = opts.port
this.addresses = opts.addresses
this.fqdn = this.name + '.' + this.type + TLD
this.subtypes = opts.subtypes || null
this.txt = opts.txt || null
Expand All @@ -31,27 +32,56 @@ function Service (opts) {
}

Service.prototype._records = function () {
var records = [rrPtr(this), rrSrv(this), rrTxt(this)]
var records = [ rrPtrServices(this), rrPtr(this), rrSrv(this), rrTxt(this) ]

if (this.subtypes) {
for (var subtypeIndex = 0; subtypeIndex < this.subtypes.length; subtypeIndex += 1) {
records.push(rrPtr(this, subtypeIndex))
}
}

var self = this
var interfaces = os.networkInterfaces()
Object.keys(interfaces).forEach(function (name) {
interfaces[name].forEach(function (addr) {
if (addr.internal) return
if (addr.family === 'IPv4') {
records.push(rrA(self, addr.address))
} else {
records.push(rrAaaa(self, addr.address))
}
if (!this.addresses) {
var interfaces = os.networkInterfaces()
Object.keys(interfaces).forEach(function (name) {
interfaces[name].forEach(function (addr) {
if (addr.internal) return
if (addr.family === 'IPv4') {
records.push(rrA(self, addr.address))
} else {
records.push(rrAaaa(self, addr.address))
}
})
})
})
} else {
if (this.addresses.ipv4) {
this.addresses.ipv4.forEach(function (addr) {
records.push(rrA(self, addr))
})
}
if (this.addresses.ipv6) {
this.addresses.ipv6.forEach(function (addr) {
records.push(rrAaaa(self, addr))
})
}
}

return records
}

function rrPtr (service) {
function rrPtrServices (service) {
return {
name: '_services._dns-sd._udp.local',
type: 'PTR',
ttl: 28800,
data: service.type + TLD
}
}

function rrPtr (service, subtypeIndex) {
return {
name: service.type + TLD,
name: (subtypeIndex !== undefined) ? '_' + service.subtypes[subtypeIndex] + '._sub.' +
service.type + TLD : service.type + TLD,
type: 'PTR',
ttl: 28800,
data: service.fqdn
Expand Down
75 changes: 75 additions & 0 deletions test/bonjour.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ test('bonjour.find', function (bonjour, t) {
var ups = 0

browser.on('up', function (s) {
if (s.name === 'Sub Foo') return

if (s.name === 'Foo Bar') {
t.equal(s.name, 'Foo Bar')
t.equal(s.fqdn, 'Foo Bar._test._tcp.local')
Expand Down Expand Up @@ -113,6 +115,79 @@ test('bonjour.find', function (bonjour, t) {

bonjour.publish({ name: 'Foo Bar', type: 'test', port: 3000 }).on('up', next())
bonjour.publish({ name: 'Invalid', type: 'test2', port: 3000 }).on('up', next())
bonjour.publish({ name: 'Sub Foo', type: 'test', subtypes: [ 'stOne', 'stTwo' ], port: 3000 }).on('up', next())
bonjour.publish({ name: 'Baz', type: 'test', port: 3000, txt: { foo: 'bar' } }).on('up', next())
})

test('bonjour.find - all services', function (bonjour, t) {
var next = afterAll(function () {
var browserServices = bonjour.find({})
var ups = 0
var found = []

browserServices.on('up', function (s) {
// Ensures that bonjour responds to the '_services._dns-sd._udp.local'
// request.
found.push(s.name)

if (++ups === 4) {
found.sort()
t.equal(found[0], 'Baz')
t.equal(found[1], 'Foo Bar')
t.equal(found[2], 'Invalid')
t.equal(found[3], 'Sub Foo')
setTimeout(function () {
bonjour.destroy()
t.end()
}, 50)
}
})
})

bonjour.publish({ name: 'Foo Bar', type: 'test', port: 3000 }).on('up', next())
bonjour.publish({ name: 'Invalid', type: 'test2', port: 3000 }).on('up', next())
bonjour.publish({ name: 'Sub Foo', type: 'test', subtypes: [ 'stOne', 'stTwo' ], port: 3000 }).on('up', next())
bonjour.publish({ name: 'Baz', type: 'test', port: 3000, txt: { foo: 'bar' } }).on('up', next())
})

test('bonjour.find - subtypes', function (bonjour, t) {
var next = afterAll(function () {
var browserSubtypes = bonjour.find({ type: 'test', subtypes: [ 'stOne', 'stTwo' ] })
var subUp = 0

browserSubtypes.on('up', function (s) {
t.equal(s.name, 'Sub Foo')
t.equal(s.fqdn, 'Sub Foo._test._tcp.local')
t.deepEqual(s.txt, {})
t.deepEqual(s.rawTxt, new Buffer('00', 'hex'))
t.equal(s.host, os.hostname())
t.equal(s.port, 3000)
t.equal(s.type, 'test')
t.equal(s.protocol, 'tcp')
t.equal(s.referer.address, '127.0.0.1')
t.equal(s.referer.family, 'IPv4')
t.ok(Number.isFinite(s.referer.port))
t.ok(Number.isFinite(s.referer.size))
if (++subUp === 2) {
// Subtypes may be out of order depending on order records were
// received in.
var testCount = 0
s.subtypes.forEach(function (subtype) {
if ((subtype === 'stTwo') || (subtype === 'stOne')) {
testCount += 1
}
})
setTimeout(function () {
bonjour.destroy()
t.end()
}, 50)
}
})
})

bonjour.publish({ name: 'Foo Bar', type: 'test', port: 3000 }).on('up', next())
bonjour.publish({ name: 'Invalid', type: 'test2', port: 3000 }).on('up', next())
bonjour.publish({ name: 'Sub Foo', type: 'test', subtypes: [ 'stOne', 'stTwo' ], port: 3000 }).on('up', next())
bonjour.publish({ name: 'Baz', type: 'test', port: 3000, txt: { foo: 'bar' } }).on('up', next())
})

Expand Down
28 changes: 27 additions & 1 deletion test/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,22 +71,48 @@ test('txt', function (t) {
t.end()
})

test('addresses', function (t) {
var s1 = new Service({ name: 'Foo Bar', type: 'http', port: 3000, host: 'testhost1.com', addresses: { ipv4: [ '1.2.3.4' ], ipv6: [ '2001:db8::01:02', 'fe80::01:02' ] } })
var s2 = new Service({ name: 'Foo Bar', type: 'http', port: 3000, host: 'testhost2.com', addresses: { ipv4: [ '5.6.7.8', '9.10.11.12' ] } })

t.deepEqual(s1.addresses, { ipv4: [ '1.2.3.4' ], ipv6: [ '2001:db8::01:02', 'fe80::01:02' ] })
t.deepEqual(s2.addresses, { ipv4: [ '5.6.7.8', '9.10.11.12' ] })
t.end()
})

test('_records() - minimal', function (t) {
var s = new Service({ name: 'Foo Bar', type: 'http', protocol: 'tcp', port: 3000 })
t.deepEqual(s._records(), [
{ data: '_http._tcp.local', name: '_services._dns-sd._udp.local', ttl: 28800, type: 'PTR' },
{ data: s.fqdn, name: '_http._tcp.local', ttl: 28800, type: 'PTR' },
{ data: { port: 3000, target: os.hostname() }, name: s.fqdn, ttl: 120, type: 'SRV' },
{ data: new Buffer('00', 'hex'), name: s.fqdn, ttl: 4500, type: 'TXT' }
].concat(getAddressesRecords(s.host)))
t.end()
})

test('_records() - everything', function (t) {
test('_records() - everything bar addresses', function (t) {
var s = new Service({ name: 'Foo Bar', type: 'http', protocol: 'tcp', port: 3000, host: 'example.com', txt: { foo: 'bar' } })
t.deepEqual(s._records(), [
{ data: '_http._tcp.local', name: '_services._dns-sd._udp.local', ttl: 28800, type: 'PTR' },
{ data: s.fqdn, name: '_http._tcp.local', ttl: 28800, type: 'PTR' },
{ data: { port: 3000, target: 'example.com' }, name: s.fqdn, ttl: 120, type: 'SRV' },
{ data: new Buffer('07666f6f3d626172', 'hex'), name: s.fqdn, ttl: 4500, type: 'TXT' }
].concat(getAddressesRecords(s.host)))
t.end()
})

test('_records() - everything including addresses', function (t) {
var s = new Service({ name: 'Foo Bar', type: 'http', protocol: 'tcp', port: 3000, host: 'example.com', txt: { foo: 'bar' },
addresses: { ipv4: [ '13.14.15.16' ], ipv6: [ '2001:db8::01:03', 'fe80::01:03' ] } })
t.deepEqual(s._records(), [
{ data: '_http._tcp.local', name: '_services._dns-sd._udp.local', ttl: 28800, type: 'PTR' },
{ data: s.fqdn, name: '_http._tcp.local', ttl: 28800, type: 'PTR' },
{ data: { port: 3000, target: 'example.com' }, name: s.fqdn, ttl: 120, type: 'SRV' },
{ data: new Buffer('07666f6f3d626172', 'hex'), name: s.fqdn, ttl: 4500, type: 'TXT' },
{ data: '13.14.15.16', name: 'example.com', ttl: 120, type: 'A' },
{ data: '2001:db8::01:03', name: 'example.com', ttl: 120, type: 'AAAA' },
{ data: 'fe80::01:03', name: 'example.com', ttl: 120, type: 'AAAA' }
])
t.end()
})