UNPKG

@homebridge/ciao

Version:

ciao is a RFC 6763 compliant dns-sd library, advertising on multicast dns (RFC 6762) implemented in plain Typescript/JavaScript

170 lines 6.89 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ResourceRecord = void 0; const tslib_1 = require("tslib"); const assert_1 = tslib_1.__importDefault(require("assert")); const debug_1 = tslib_1.__importDefault(require("debug")); const dns_equal_1 = require("../util/dns-equal"); const dns_string_utils_1 = require("./dns-string-utils"); const DNSLabelCoder_1 = require("./DNSLabelCoder"); const debug = (0, debug_1.default)("ciao:decoder"); class ResourceRecord { constructor(name, type, ttl = ResourceRecord.RR_DEFAULT_TTL, flushFlag = false, clazz = 1 /* RClass.IN */) { this.flushFlag = false; if (typeof name === "string") { if (!name.endsWith(".")) { name = name + "."; } this.name = name; this.type = type; this.class = clazz; this.ttl = ttl; this.flushFlag = flushFlag; } else { this.name = name.name; this.type = name.type; this.class = name.class; this.ttl = name.ttl; this.flushFlag = name.flushFlag; } } getLowerCasedName() { return this.lowerCasedName || (this.lowerCasedName = (0, dns_equal_1.dnsLowerCase)(this.name)); } getEncodingLength(coder) { return coder.getNameLength(this.name) + 10 // 2 bytes TYPE; 2 bytes class, 4 bytes TTL, 2 bytes RDLength + this.getRDataEncodingLength(coder); } encode(coder, buffer, offset) { const oldOffset = offset; const nameLength = coder.encodeName(this.name, offset); offset += nameLength; buffer.writeUInt16BE(this.type, offset); offset += 2; let rClass = this.class; if (this.flushFlag) { // for pseudo records like OPT, TSIG, TKEY, SIG0 the top bit should not be interpreted as the flush flag // though we do not support those (OPT seems to be the only used, though no idea for what [by Apple for mdns]) rClass |= ResourceRecord.FLUSH_MASK; } buffer.writeUInt16BE(rClass, offset); offset += 2; buffer.writeUInt32BE(this.ttl, offset); offset += 4; const dataLength = this.encodeRData(coder, buffer, offset + 2); buffer.writeUInt16BE(dataLength, offset); offset += 2 + dataLength; return offset - oldOffset; // written bytes } getRawData() { const coder = DNSLabelCoder_1.NonCompressionLabelCoder.INSTANCE; // this forces uncompressed names const length = this.getRDataEncodingLength(coder); const buffer = Buffer.allocUnsafe(length); coder.initBuf(buffer); const writtenBytes = this.encodeRData(coder, buffer, 0); (0, assert_1.default)(writtenBytes === buffer.length, "Didn't completely write to the buffer! (" + writtenBytes + "!=" + buffer.length + ")"); coder.initBuf(); // reset buffer to undefined return buffer; } static clone(records) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error return records.map(record => record.clone()); } getRecordRepresentation() { return { name: this.name, type: this.type, class: this.class, ttl: this.ttl, flushFlag: this.flushFlag, }; } /** * Returns if this and the supplied record are the same (ignoring ttl and flush flag) * @param record */ aboutEqual(record) { return this.type === record.type && this.name === record.name && this.class === record.class && this.dataEquals(record); } representsSameData(record) { return this.type === record.type && this.name === record.name && this.class === record.class; } asString() { // same as aboutEqual, ttl is not included return `RR ${this.name} ${this.type} ${this.class} ${this.dataAsString()}`; } static decode(context, coder, buffer, offset) { const oldOffset = offset; const decodedHeader = this.decodeRecordHeader(coder, buffer, offset); offset += decodedHeader.readBytes; const header = decodedHeader.data; const rrDecoder = this.typeToRecordDecoder.get(header.type); if (!rrDecoder) { return { readBytes: (offset + header.rDataLength) - oldOffset }; } coder.initRRLocation(oldOffset, offset, header.rDataLength); // defines record offset and rdata offset for local compression const rdata = buffer.subarray(0, offset + header.rDataLength); let decodedRecord; try { // we slice the buffer (below), so out of bounds error are instantly detected decodedRecord = rrDecoder(coder, header, rdata, offset); } catch (error) { debug(`Received malformed rdata section for ${(0, dns_string_utils_1.dnsTypeToString)(header.type)} ${header.name} ${header.ttl} \ from ${context.address}:${context.port} with data '${rdata.subarray(offset).toString("hex")}': ${error.stack}`); return { readBytes: (offset + header.rDataLength) - oldOffset }; } offset += decodedRecord.readBytes; coder.clearRRLocation(); return { data: decodedRecord.data, readBytes: offset - oldOffset, }; } static decodeRecordHeader(coder, buffer, offset) { const oldOffset = offset; const decodedName = coder.decodeName(offset); offset += decodedName.readBytes; const type = buffer.readUInt16BE(offset); offset += 2; const rClass = buffer.readUInt16BE(offset); offset += 2; let clazz; let flushFlag = false; if (type !== 41 /* RType.OPT */) { clazz = (rClass & this.NOT_FLUSH_MASK); flushFlag = !!(rClass & this.FLUSH_MASK); } else { // OPT class field encodes udpPayloadSize field clazz = rClass; } const ttl = buffer.readUInt32BE(offset); offset += 4; const rDataLength = buffer.readUInt16BE(offset); offset += 2; const rHeader = { name: decodedName.data, type: type, class: clazz, ttl: ttl, flushFlag: flushFlag, rDataLength: rDataLength, }; return { data: rHeader, readBytes: offset - oldOffset, }; } } exports.ResourceRecord = ResourceRecord; ResourceRecord.typeToRecordDecoder = new Map(); ResourceRecord.FLUSH_MASK = 0x8000; // 2 bytes, first bit set ResourceRecord.NOT_FLUSH_MASK = 0x7FFF; ResourceRecord.RR_DEFAULT_TTL_SHORT = 120; // 120 seconds ResourceRecord.RR_DEFAULT_TTL = 4500; // 75 minutes //# sourceMappingURL=ResourceRecord.js.map