dnssd
Version:
Bonjour/Avahi-like service discovery in pure JavaScript
359 lines (319 loc) • 11 kB
JavaScript
'use strict';
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* Wraps a buffer for easier reading / writing without keeping track of offsets.
* @class
*
* instead of:
* buffer.writeUInt8(1, 0);
* buffer.writeUInt8(2, 1);
* buffer.writeUInt8(3, 2);
*
* do:
* wrapper.writeUInt8(1);
* wrapper.writeUInt8(2);
* wrapper.writeUInt8(3);
*/
var BufferWrapper = function () {
/**
* @param {Buffer} [buffer]
* @param {integer} [position]
*/
function BufferWrapper(buffer) {
var position = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
_classCallCheck(this, BufferWrapper);
this.buffer = buffer || Buffer.alloc(512);
this.position = position;
}
_createClass(BufferWrapper, [{
key: 'readUInt8',
value: function readUInt8() {
var value = this.buffer.readUInt8(this.position);
this.position += 1;
return value;
}
}, {
key: 'writeUInt8',
value: function writeUInt8(value) {
this._checkLength(1);
this.buffer.writeUInt8(value, this.position);
this.position += 1;
}
}, {
key: 'readUInt16BE',
value: function readUInt16BE() {
var value = this.buffer.readUInt16BE(this.position);
this.position += 2;
return value;
}
}, {
key: 'writeUInt16BE',
value: function writeUInt16BE(value) {
this._checkLength(2);
this.buffer.writeUInt16BE(value, this.position);
this.position += 2;
}
}, {
key: 'readUInt32BE',
value: function readUInt32BE() {
var value = this.buffer.readUInt32BE(this.position);
this.position += 4;
return value;
}
}, {
key: 'writeUInt32BE',
value: function writeUInt32BE(value) {
this._checkLength(4);
this.buffer.writeUInt32BE(value, this.position);
this.position += 4;
}
}, {
key: 'readUIntBE',
value: function readUIntBE(len) {
var value = this.buffer.readUIntBE(this.position, len);
this.position += len;
return value;
}
}, {
key: 'writeUIntBE',
value: function writeUIntBE(value, len) {
this._checkLength(len);
this.buffer.writeUIntBE(value, this.position, len);
this.position += len;
}
}, {
key: 'readString',
value: function readString(len) {
var str = this.buffer.toString('utf8', this.position, this.position + len);
this.position += len;
return str;
}
}, {
key: 'writeString',
value: function writeString(str) {
var len = Buffer.byteLength(str);
this._checkLength(len);
this.buffer.write(str, this.position);
this.position += len;
}
/**
* Returns a sub portion of the wrapped buffer
* @param {integer} len
* @return {Buffer}
*/
}, {
key: 'read',
value: function read(len) {
var buf = Buffer.alloc(len).fill(0);
this.buffer.copy(buf, 0, this.position);
this.position += len;
return buf;
}
/**
* Writes another buffer onto the wrapped buffer
* @param {Buffer} buffer
*/
}, {
key: 'add',
value: function add(buffer) {
this._checkLength(buffer.length);
buffer.copy(this.buffer, this.position);
this.position += buffer.length;
}
}, {
key: 'seek',
value: function seek(position) {
this.position = position;
}
}, {
key: 'skip',
value: function skip(len) {
this.position += len;
}
}, {
key: 'tell',
value: function tell() {
return this.position;
}
}, {
key: 'remaining',
value: function remaining() {
return this.buffer.length - this.position;
}
}, {
key: 'unwrap',
value: function unwrap() {
return this.buffer.slice(0, this.position);
}
}, {
key: '_checkLength',
value: function _checkLength(len) {
var needed = len - this.remaining();
var amount = needed > 512 ? needed * 1.5 : 512;
if (needed > 0) this._grow(amount);
}
}, {
key: '_grow',
value: function _grow(amount) {
this.buffer = Buffer.concat([this.buffer, Buffer.alloc(amount).fill(0)]);
}
}, {
key: 'indexOf',
value: function indexOf(needle) {
// limit indexOf search up to current position in buffer, no need to
// search for stuff after this.position
var haystack = this.buffer.slice(0, this.position);
if (!haystack.length || !needle.length) return -1;
if (needle.length > haystack.length) return -1;
// use node's indexof if this version has it
if (typeof Buffer.prototype.indexOf === 'function') {
return haystack.indexOf(needle);
}
// otherwise do naive search
var maxIndex = haystack.length - needle.length;
var index = 0;
var pos = 0;
for (; index <= maxIndex; index++, pos = 0) {
while (haystack[index + pos] === needle[pos]) {
if (++pos === needle.length) return index;
}
}
return -1;
}
/**
* Reads a fully qualified domain name from the buffer following the dns
* message format / compression style.
*
* Basic:
* Each label is preceded by an uint8 specifying the length of the label,
* finishing with a 0 which indicates the root label.
*
* +---+------+---+--------+---+-----+---+
* | 3 | wwww | 6 | google | 3 | com | 0 | --> www.google.com.
* +---+------+---+--------+---+-----+---+
*
* Compression:
* A pointer is used to point to the location of the previously written labels.
* If a length byte is > 192 (0xC0) then it means its a pointer to other
* labels and not a length marker. The pointer is 2 octets long.
*
* +---+------+-------------+
* | 3 | wwww | 0xC000 + 34 | --> www.google.com.
* +---+------+-------------+
* ^-- the "google.com." part can be found @ offset 34
*
* @return {string}
*/
}, {
key: 'readFQDN',
value: function readFQDN() {
var labels = [];
var len = void 0,
farthest = void 0;
while (this.remaining() >= 0 && (len = this.readUInt8())) {
// Handle dns compression. If the length is > 192, it means its a pointer.
// The pointer points to a previous position in the buffer to move to and
// read from. Pointer (a int16be) = 0xC000 + position
if (len < 192) {
labels.push(this.readString(len));
} else {
var position = (len << 8) + this.readUInt8() - 0xC000;
// If a pointer was found, keep track of the farthest position reached
// (the current position) before following the pointers so we can return
// to it later after following all the compression pointers
if (!farthest) farthest = this.position;
this.seek(position);
}
}
// reset to correct position after following pointers (if any)
if (farthest) this.seek(farthest);
return labels.join('.') + '.'; // + root label
}
/**
* Writes a fully qualified domain name
* Same rules as readFQDN above. Does compression.
*
* @param {string} name
*/
}, {
key: 'writeFQDN',
value: function writeFQDN(name) {
var _this = this;
// convert name into an array of buffers
var labels = name.split('.').filter(function (s) {
return !!s;
}).map(function (label) {
var len = Buffer.byteLength(label);
var buf = Buffer.alloc(1 + len);
buf.writeUInt8(len, 0);
buf.write(label, 1);
return buf;
});
// add root label (a single ".") to the end (zero length label = 0)
labels.push(Buffer.alloc(1));
// compress
var compressed = this._getCompressedLabels(labels);
compressed.forEach(function (label) {
return _this.add(label);
});
}
/**
* Finds a compressed version of given labels within the buffer
*
* Checks if a sub section has been written before, starting with all labels
* and removing the first label on each successive search until a match (index)
* is found, or until NO match is found.
*
* Ex:
*
* 1st pass: Instance._service._tcp.local
* 2nd pass: _service._tcp.local
* 3rd pass: _tcp.local
* ^-- found "_tcp.local" @ 34, try to compress more
*
* 4th pass: Instance._service.[0xC000 + 34]
* 5th pass: _service.[0xC000 + 34]
* ^-- found "_service.[0xC000 + 34]" @ 52, try to compress more
*
* 6th pass: Instance.[0xC000 + 52]
*
* Nothing else found, returns [Instance, 0xC000+52]
*
* @param {Buffer[]} labels
* @return {Buffer[]} - compressed version
*/
}, {
key: '_getCompressedLabels',
value: function _getCompressedLabels(labels) {
var copy = [].concat(_toConsumableArray(labels));
var wrapper = this;
function compress(lastPointer) {
// re-loop on each compression attempt
copy.forEach(function (label, index) {
// if a pointer was found on the last compress call, don't bother trying
// to find a previous instance of a pointer, it doesn't do any good.
// no need to change [0xC000 + 54] pointer to a [0xC000 + 23] pointer
if (lastPointer && label === lastPointer) return;
if (label.length === 1 && label[0] === 0) return;
var subset = copy.slice(index);
var pos = wrapper.indexOf(Buffer.concat(subset));
if (!!~pos) {
var pointer = Buffer.alloc(2);
pointer.writeUInt16BE(0xC000 + pos, 0);
// drop this label and everything after it (stopping forEach loop)
// put the pointer there instead
copy.splice(index, copy.length - index);
copy.push(pointer);
compress(pointer); // try to compress some more
}
});
}
compress();
return copy;
}
}]);
return BufferWrapper;
}();
module.exports = BufferWrapper;