@homebridge/ciao
Version:
ciao is a RFC 6763 compliant dns-sd library, advertising on multicast dns (RFC 6762) implemented in plain Typescript/JavaScript
150 lines • 7.22 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.NSECRecord = void 0;
const tslib_1 = require("tslib");
const assert_1 = tslib_1.__importDefault(require("assert"));
const fast_deep_equal_1 = tslib_1.__importDefault(require("fast-deep-equal"));
const dns_equal_1 = require("../../util/dns-equal");
const ResourceRecord_1 = require("../ResourceRecord");
class NSECRecord extends ResourceRecord_1.ResourceRecord {
constructor(name, nextDomainName, rrtypes, ttl, flushFlag) {
if (typeof name === "string") {
super(name, 47 /* RType.NSEC */, ttl || NSECRecord.RR_DEFAULT_TTL_SHORT, flushFlag);
}
else {
(0, assert_1.default)(name.type === 47 /* RType.NSEC */);
super(name);
}
if (!nextDomainName.endsWith(".")) {
nextDomainName += ".";
}
this.nextDomainName = nextDomainName;
this.rrTypeWindows = NSECRecord.rrTypesToWindowMap(rrtypes);
}
getLowerCasedNextDomainName() {
return this.lowerCasedNextDomainName || (this.lowerCasedNextDomainName = (0, dns_equal_1.dnsLowerCase)(this.nextDomainName));
}
getRRTypesBitMapEncodingLength() {
let rrTypesBitMapLength = 0;
for (const window of this.rrTypeWindows) {
(0, assert_1.default)(window.rrtypes.length > 0, "types array for windowId " + window.windowId + " cannot be empty!");
rrTypesBitMapLength += 2 // 1 byte for windowId; 1 byte for bitmap length
+ window.bitMapSize;
}
return rrTypesBitMapLength;
}
getRDataEncodingLength(coder) {
// RFC 4034 4.1.1. name compression MUST NOT be used for the nextDomainName, though RFC 6762 18.14 specifies it should
return (coder.legacyUnicastEncoding
? coder.getUncompressedNameLength(this.nextDomainName)
: coder.getUncompressedNameLength(this.nextDomainName)) // we skip compression for NSEC records for now, as Ubiquiti mdns forward can't handle that
+ this.getRRTypesBitMapEncodingLength();
}
encodeRData(coder, buffer, offset) {
const oldOffset = offset;
const length = coder.legacyUnicastEncoding
? coder.encodeUncompressedName(this.nextDomainName, offset)
: coder.encodeUncompressedName(this.nextDomainName, offset); // we skip compression for NSEC records for now, as Ubiquiti mdns forward can't handle that
offset += length;
// RFC 4034 4.1.2. type bit maps field has the following format ( Window Block # | Bitmap Length | Bitmap )+ (with | concatenation)
// e.g. 0x00 0x01 0x40 => defines the window 0; bitmap length 1; and the bitmap 10000000, meaning the first bit is
// set for the 0th window => rrTypes = [A]. The bitmap length depends on the rtype with the highest value for the
// given value (max 32 bytes per bitmap)
for (const window of this.rrTypeWindows) {
buffer.writeUInt8(window.windowId, offset++);
buffer.writeUInt8(window.bitMapSize, offset++);
const bitmap = Buffer.alloc(window.bitMapSize);
for (const type of window.rrtypes) {
const byteNum = (type & 0xFF) >> 3; // basically floored division by 8
let mask = bitmap.readUInt8(byteNum);
mask |= 1 << (7 - (type & 0x7)); // OR with 1 shifted according to the lowest 3 bits
bitmap.writeUInt8(mask, byteNum);
}
bitmap.copy(buffer, offset);
offset += bitmap.length;
}
return offset - oldOffset;
}
static decodeData(coder, header, buffer, offset) {
const oldOffset = offset;
/**
* Quick note to the line below. We base "false" as the second argument to decodeName, telling
* it to not resolve pointers.
* We discovered that especially UniFi routers with a VLAN setup and mdns forwarding enabled,
* fail to properly encode pointers inside the nextDomainName field.
* Those pointers simply point to random points in the record data, resulting in decoding to fail.
* As the field doesn't have any meaning, and we simply don't use it, we just skip decoding for now.
*/
const decodedNextDomainName = coder.decodeName(offset, false);
offset += decodedNextDomainName.readBytes;
const rrTypes = [];
while (offset < buffer.length) {
const windowId = buffer.readUInt8(offset++);
const bitMapLength = buffer.readUInt8(offset++);
const upperRType = windowId << 8;
for (let block = 0; block < bitMapLength; block++) {
const byte = buffer.readUInt8(offset++);
for (let bit = 0; bit < 8; bit++) { // iterate over every bit
if (byte & (1 << (7 - bit))) { // check if bit is set
const rType = upperRType | (block << 3) | bit; // OR upperWindowNum | basically block * 8 | bit number
rrTypes.push(rType);
}
}
}
}
return {
data: new NSECRecord(header, decodedNextDomainName.data, rrTypes),
readBytes: offset - oldOffset,
};
}
clone() {
return new NSECRecord(this.getRecordRepresentation(), this.nextDomainName, NSECRecord.windowsToRRTypes(this.rrTypeWindows));
}
dataAsString() {
return `${this.nextDomainName} [${NSECRecord.windowsToRRTypes(this.rrTypeWindows).map(rtype => "" + rtype).join(",")}]`;
}
dataEquals(record) {
return this.getLowerCasedNextDomainName() === record.getLowerCasedNextDomainName() && (0, fast_deep_equal_1.default)(this.rrTypeWindows, record.rrTypeWindows);
}
static rrTypesToWindowMap(rrtypes) {
const rrTypeWindows = [];
for (const rrtype of rrtypes) {
const windowId = rrtype >> 8;
let window = undefined;
for (const window0 of rrTypeWindows) {
if (window0.windowId === windowId) {
window = window0;
break;
}
}
if (!window) {
window = {
windowId: windowId,
bitMapSize: Math.ceil((rrtype & 0xFF) / 8),
rrtypes: [rrtype],
};
rrTypeWindows.push(window);
}
else {
window.rrtypes.push(rrtype);
const bitMapSize = Math.ceil((rrtype & 0xFF) / 8);
if (bitMapSize > window.bitMapSize) {
window.bitMapSize = bitMapSize;
}
}
}
// sort by windowId
rrTypeWindows.sort((a, b) => a.windowId - b.windowId);
rrTypeWindows.forEach(window => window.rrtypes.sort((a, b) => a - b));
return rrTypeWindows;
}
static windowsToRRTypes(windows) {
const rrtypes = [];
for (const window of windows) {
rrtypes.push(...window.rrtypes);
}
return rrtypes;
}
}
exports.NSECRecord = NSECRecord;
//# sourceMappingURL=NSECRecord.js.map