UNPKG

ts-bacnet

Version:

The BACnet protocol library written in pure JavaScript.

1,296 lines 67.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.decodeApplicationTime = exports.decodeBacnetTime = exports.decodeApplicationDate = exports.decodeDate = exports.decodeBitstring = exports.decodeCharacterString = exports.decodeOctetString = exports.decodeReal = exports.decodeSigned = exports.decodeReadAccessResult = exports.encodeReadAccessResult = exports.bacappDecodeApplicationData = exports.decodeTagNumberAndValue = exports.decodeObjectId = exports.decodeIsOpeningTag = exports.decodeIsClosingTag = exports.decodeIsClosingTagNumber = exports.decodeIsOpeningTagNumber = exports.decodeIsContextTag = exports.decodeTagNumber = exports.encodeContextSigned = exports.encodeContextBitstring = exports.bacappEncodePropertyState = exports.bacappEncodeContextDeviceObjPropertyRef = exports.bacappEncodeApplicationData = exports.encodeContextBoolean = exports.encodeReadAccessSpecification = exports.encodeClosingTag = exports.encodeOpeningTag = exports.encodeContextObjectId = exports.encodeApplicationTime = exports.encodeApplicationDate = exports.encodeBacnetDate = exports.encodeApplicationBitstring = exports.encodeApplicationSigned = exports.encodeApplicationEnumerated = exports.encodeApplicationUnsigned = exports.encodeApplicationObjectId = exports.encodeApplicationBoolean = exports.encodeApplicationOctetString = exports.encodeContextEnumerated = exports.encodeContextUnsigned = exports.encodeContextReal = exports.encodeTag = exports.encodeBacnetObjectId = exports.decodeEnumerated = exports.decodeUnsigned = exports.encodeUnsigned = exports.MAX_YEARS = exports.START_YEAR = void 0; exports.encodeContextCharacterString = exports.encodeApplicationCharacterString = exports.decodeContextObjectId = exports.decodeContextCharacterString = exports.bacappEncodeContextTimestamp = exports.bacappEncodeTimestamp = exports.decodeReadAccessSpecification = void 0; //@ts-nocheck const iconv_lite_1 = __importDefault(require("iconv-lite")); const enums_1 = __importDefault(require("./enums")); exports.START_YEAR = 1900; exports.MAX_YEARS = 256; const getBuffer = () => { return { buffer: Buffer.alloc(1472), offset: 0, }; }; const getSignedLength = (value) => { if (value >= -128 && value < 128) return 1; else if (value >= -32768 && value < 32768) return 2; else if (value > -8388608 && value < 8388608) return 3; else return 4; }; const getUnsignedLength = (value) => { if (value < 0x100) return 1; else if (value < 0x10000) return 2; else if (value < 0x1000000) return 3; else return 4; }; const getEncodingType = (encoding, decodingBuffer, decodingOffset) => { switch (encoding) { case enums_1.default.CharacterStringEncoding.UCS_2: if (decodingBuffer && decodingBuffer[decodingOffset] === 0xff && decodingBuffer[decodingOffset + 1] === 0xfe) { return "ucs2"; } return "UTF-16BE"; // Default to big-endian case enums_1.default.CharacterStringEncoding.ISO_8859_1: return "latin1"; case enums_1.default.CharacterStringEncoding.MICROSOFT_DBCS: return "cp850"; case enums_1.default.CharacterStringEncoding.JIS_X_0208: return "Shift_JIS"; default: return "utf8"; } }; const encodeUnsigned = (buffer, value, length) => { buffer.buffer.writeUIntBE(value, buffer.offset, length, true); buffer.offset += length; }; exports.encodeUnsigned = encodeUnsigned; const encodeBacnetUnsigned = (buffer, value) => { (0, exports.encodeUnsigned)(buffer, value, getUnsignedLength(value)); }; const encodeSigned = (buffer, value, length) => { buffer.buffer.writeIntBE(value, buffer.offset, length, true); buffer.offset += length; }; const encodeBacnetSigned = (buffer, value) => { encodeSigned(buffer, value, getSignedLength(value)); }; const encodeBacnetReal = (buffer, value) => { buffer.buffer.writeFloatBE(value, buffer.offset, true); buffer.offset += 4; }; const encodeBacnetDouble = (buffer, value) => { buffer.buffer.writeDoubleBE(value, buffer.offset, true); buffer.offset += 8; }; const decodeUnsigned = (buffer, offset, length) => { if (length === 0) { return { len: 0, value: 0, }; } return { len: length, //@ts-ignore value: buffer.readUIntBE(offset, length), }; }; exports.decodeUnsigned = decodeUnsigned; const decodeEnumerated = (buffer, offset, length) => { return (0, exports.decodeUnsigned)(buffer, offset, length); }; exports.decodeEnumerated = decodeEnumerated; const encodeBacnetObjectId = (buffer, objectType, instance) => { const value = (((objectType & enums_1.default.ASN1_MAX_OBJECT) << enums_1.default.ASN1_INSTANCE_BITS) | (instance & enums_1.default.ASN1_MAX_INSTANCE)) >>> 0; (0, exports.encodeUnsigned)(buffer, value, 4); }; exports.encodeBacnetObjectId = encodeBacnetObjectId; const encodeTag = (buffer, tagNumber, contextSpecific, lenValueType) => { let len = 1; const tmp = new Array(3); tmp[0] = 0; if (contextSpecific) { tmp[0] |= 0x8; } if (tagNumber <= 14) { tmp[0] |= tagNumber << 4; } else { tmp[0] |= 0xf0; tmp[1] = tagNumber; len++; } if (lenValueType <= 4) { tmp[0] |= lenValueType; Buffer.from(tmp).copy(buffer.buffer, buffer.offset, 0, len); buffer.offset += len; } else { tmp[0] |= 5; if (lenValueType <= 253) { tmp[len++] = lenValueType; Buffer.from(tmp).copy(buffer.buffer, buffer.offset, 0, len); buffer.offset += len; } else if (lenValueType <= 65535) { tmp[len++] = 254; Buffer.from(tmp).copy(buffer.buffer, buffer.offset, 0, len); buffer.offset += len; (0, exports.encodeUnsigned)(buffer, lenValueType, 2); } else { tmp[len++] = 255; Buffer.from(tmp).copy(buffer.buffer, buffer.offset, 0, len); buffer.offset += len; (0, exports.encodeUnsigned)(buffer, lenValueType, 4); } } }; exports.encodeTag = encodeTag; const encodeBacnetEnumerated = (buffer, value) => { encodeBacnetUnsigned(buffer, value); }; const isExtendedTagNumber = (x) => { return (x & 0xf0) === 0xf0; }; const isExtendedValue = (x) => { return (x & 0x07) === 5; }; const isContextSpecific = (x) => { return (x & 0x8) === 0x8; }; const isOpeningTag = (x) => { return (x & 0x07) === 6; }; const isClosingTag = (x) => { return (x & 0x07) === 7; }; const encodeContextReal = (buffer, tagNumber, value) => { (0, exports.encodeTag)(buffer, tagNumber, true, 4); encodeBacnetReal(buffer, value); }; exports.encodeContextReal = encodeContextReal; const encodeContextUnsigned = (buffer, tagNumber, value) => { (0, exports.encodeTag)(buffer, tagNumber, true, getUnsignedLength(value)); encodeBacnetUnsigned(buffer, value); }; exports.encodeContextUnsigned = encodeContextUnsigned; const encodeContextEnumerated = (buffer, tagNumber, value) => { (0, exports.encodeContextUnsigned)(buffer, tagNumber, value); }; exports.encodeContextEnumerated = encodeContextEnumerated; const encodeOctetString = (buffer, octetString, octetOffset, octetCount) => { if (octetString) { for (let i = octetOffset; i < octetOffset + octetCount; i++) { buffer.buffer[buffer.offset++] = octetString[i]; } } }; const encodeApplicationOctetString = (buffer, octetString, octetOffset, octetCount) => { (0, exports.encodeTag)(buffer, enums_1.default.ApplicationTag.OCTET_STRING, false, octetCount); encodeOctetString(buffer, octetString, octetOffset, octetCount); }; exports.encodeApplicationOctetString = encodeApplicationOctetString; const encodeApplicationNull = (buffer) => { buffer.buffer[buffer.offset++] = enums_1.default.ApplicationTag.NULL; }; const encodeApplicationBoolean = (buffer, booleanValue) => { (0, exports.encodeTag)(buffer, enums_1.default.ApplicationTag.BOOLEAN, false, booleanValue ? 1 : 0); }; exports.encodeApplicationBoolean = encodeApplicationBoolean; const encodeApplicationReal = (buffer, value) => { (0, exports.encodeTag)(buffer, enums_1.default.ApplicationTag.REAL, false, 4); encodeBacnetReal(buffer, value); }; const encodeApplicationDouble = (buffer, value) => { (0, exports.encodeTag)(buffer, enums_1.default.ApplicationTag.DOUBLE, false, 8); encodeBacnetDouble(buffer, value); }; const bitstringBytesUsed = (bitString) => { let len = 0; if (bitString.bitsUsed > 0) { const lastBit = bitString.bitsUsed - 1; const usedBytes = lastBit / 8 + 1; len = Math.floor(usedBytes); } return len; }; const encodeApplicationObjectId = (buffer, objectType, instance) => { const tmp = getBuffer(); (0, exports.encodeBacnetObjectId)(tmp, objectType, instance); (0, exports.encodeTag)(buffer, enums_1.default.ApplicationTag.OBJECTIDENTIFIER, false, tmp.offset); tmp.buffer.copy(buffer.buffer, buffer.offset, 0, tmp.offset); buffer.offset += tmp.offset; }; exports.encodeApplicationObjectId = encodeApplicationObjectId; const encodeApplicationUnsigned = (buffer, value) => { const tmp = getBuffer(); encodeBacnetUnsigned(tmp, value); (0, exports.encodeTag)(buffer, enums_1.default.ApplicationTag.UNSIGNED_INTEGER, false, tmp.offset); tmp.buffer.copy(buffer.buffer, buffer.offset, 0, tmp.offset); buffer.offset += tmp.offset; }; exports.encodeApplicationUnsigned = encodeApplicationUnsigned; const encodeApplicationEnumerated = (buffer, value) => { const tmp = getBuffer(); encodeBacnetEnumerated(tmp, value); (0, exports.encodeTag)(buffer, enums_1.default.ApplicationTag.ENUMERATED, false, tmp.offset); tmp.buffer.copy(buffer.buffer, buffer.offset, 0, tmp.offset); buffer.offset += tmp.offset; }; exports.encodeApplicationEnumerated = encodeApplicationEnumerated; const encodeApplicationSigned = (buffer, value) => { const tmp = getBuffer(); encodeBacnetSigned(tmp, value); (0, exports.encodeTag)(buffer, enums_1.default.ApplicationTag.SIGNED_INTEGER, false, tmp.offset); tmp.buffer.copy(buffer.buffer, buffer.offset, 0, tmp.offset); buffer.offset += tmp.offset; }; exports.encodeApplicationSigned = encodeApplicationSigned; const byteReverseBits = (inByte) => { let outByte = 0; if ((inByte & 1) > 0) { outByte |= 0x80; } if ((inByte & 2) > 0) { outByte |= 0x40; } if ((inByte & 4) > 0) { outByte |= 0x20; } if ((inByte & 8) > 0) { outByte |= 0x10; } if ((inByte & 16) > 0) { outByte |= 0x8; } if ((inByte & 32) > 0) { outByte |= 0x4; } if ((inByte & 64) > 0) { outByte |= 0x2; } if ((inByte & 128) > 0) { outByte |= 1; } return outByte; }; const bitstringOctet = (bitString, octetIndex) => { let octet = 0; if (bitString.value && octetIndex < enums_1.default.ASN1_MAX_BITSTRING_BYTES) { octet = bitString.value[octetIndex]; } return octet; }; const encodeBitstring = (buffer, bitString) => { if (bitString.bitsUsed === 0) { buffer.buffer[buffer.offset++] = 0; } else { const usedBytes = bitstringBytesUsed(bitString); const remainingUsedBits = bitString.bitsUsed - (usedBytes - 1) * 8; buffer.buffer[buffer.offset++] = 8 - remainingUsedBits; for (let i = 0; i < usedBytes; i++) { buffer.buffer[buffer.offset++] = byteReverseBits(bitstringOctet(bitString, i)); } } }; const encodeApplicationBitstring = (buffer, bitString) => { let bitStringEncodedLength = 1; bitStringEncodedLength += bitstringBytesUsed(bitString); (0, exports.encodeTag)(buffer, enums_1.default.ApplicationTag.BIT_STRING, false, bitStringEncodedLength); encodeBitstring(buffer, bitString); }; exports.encodeApplicationBitstring = encodeApplicationBitstring; const encodeBacnetDate = (buffer, value) => { if (value === new Date(1, 1, 1)) { buffer.buffer[buffer.offset++] = 0xff; buffer.buffer[buffer.offset++] = 0xff; buffer.buffer[buffer.offset++] = 0xff; buffer.buffer[buffer.offset++] = 0xff; return; } if (value.getFullYear() >= exports.START_YEAR) { buffer.buffer[buffer.offset++] = value.getFullYear() - exports.START_YEAR; } else if (value.getFullYear() < exports.MAX_YEARS /* 1900 + 255 max */) { buffer.buffer[buffer.offset++] = value.getFullYear(); } else { throw new Error("invaide year: " + value.getFullYear()); } buffer.buffer[buffer.offset++] = value.getMonth(); buffer.buffer[buffer.offset++] = value.getDate(); buffer.buffer[buffer.offset++] = value.getDay() === 0 ? 7 : value.getDay(); }; exports.encodeBacnetDate = encodeBacnetDate; const encodeApplicationDate = (buffer, value) => { (0, exports.encodeTag)(buffer, enums_1.default.ApplicationTag.DATE, false, 4); (0, exports.encodeBacnetDate)(buffer, value); }; exports.encodeApplicationDate = encodeApplicationDate; const encodeBacnetTime = (buffer, value) => { buffer.buffer[buffer.offset++] = value.getHours(); buffer.buffer[buffer.offset++] = value.getMinutes(); buffer.buffer[buffer.offset++] = value.getSeconds(); buffer.buffer[buffer.offset++] = value.getMilliseconds() / 10; }; const encodeApplicationTime = (buffer, value) => { (0, exports.encodeTag)(buffer, enums_1.default.ApplicationTag.TIME, false, 4); encodeBacnetTime(buffer, value); }; exports.encodeApplicationTime = encodeApplicationTime; const bacappEncodeDatetime = (buffer, value) => { if (value !== new Date(1, 1, 1)) { (0, exports.encodeApplicationDate)(buffer, value); (0, exports.encodeApplicationTime)(buffer, value); } }; const encodeContextObjectId = (buffer, tagNumber, objectType, instance) => { (0, exports.encodeTag)(buffer, tagNumber, true, 4); (0, exports.encodeBacnetObjectId)(buffer, objectType, instance); }; exports.encodeContextObjectId = encodeContextObjectId; const encodeOpeningTag = (buffer, tagNumber) => { let len = 1; const tmp = new Array(2); tmp[0] = 0x8; if (tagNumber <= 14) { tmp[0] |= tagNumber << 4; } else { tmp[0] |= 0xf0; tmp[1] = tagNumber; len++; } tmp[0] |= 6; Buffer.from(tmp).copy(buffer.buffer, buffer.offset, 0, len); buffer.offset += len; }; exports.encodeOpeningTag = encodeOpeningTag; const encodeClosingTag = (buffer, tagNumber) => { let len = 1; const tmp = new Array(2); tmp[0] = 0x8; if (tagNumber <= 14) { tmp[0] |= tagNumber << 4; } else { tmp[0] |= 0xf0; tmp[1] = tagNumber; len++; } tmp[0] |= 7; Buffer.from(tmp).copy(buffer.buffer, buffer.offset, 0, len); buffer.offset += len; }; exports.encodeClosingTag = encodeClosingTag; const encodeReadAccessSpecification = (buffer, value) => { (0, exports.encodeContextObjectId)(buffer, 0, value.objectId.type, value.objectId.instance); (0, exports.encodeOpeningTag)(buffer, 1); value.properties.forEach((p) => { (0, exports.encodeContextEnumerated)(buffer, 0, p.id); if (p.index && p.index !== enums_1.default.ASN1_ARRAY_ALL) { (0, exports.encodeContextUnsigned)(buffer, 1, p.index); } }); (0, exports.encodeClosingTag)(buffer, 1); }; exports.encodeReadAccessSpecification = encodeReadAccessSpecification; const encodeContextBoolean = (buffer, tagNumber, booleanValue) => { (0, exports.encodeTag)(buffer, tagNumber, true, 1); buffer.buffer.writeUInt8(booleanValue ? 1 : 0, buffer.offset, true); buffer.offset += 1; }; exports.encodeContextBoolean = encodeContextBoolean; const encodeCovSubscription = (buffer, value) => { (0, exports.encodeOpeningTag)(buffer, 0); (0, exports.encodeOpeningTag)(buffer, 0); (0, exports.encodeOpeningTag)(buffer, 1); (0, exports.encodeApplicationUnsigned)(buffer, value.recipient.network); if (value.recipient.network === 0xffff) { (0, exports.encodeApplicationOctetString)(buffer, 0, 0, 0); } else { (0, exports.encodeApplicationOctetString)(buffer, value.recipient.address, 0, value.recipient.address.length); } (0, exports.encodeClosingTag)(buffer, 1); (0, exports.encodeClosingTag)(buffer, 0); (0, exports.encodeContextUnsigned)(buffer, 1, value.subscriptionProcessId); (0, exports.encodeClosingTag)(buffer, 0); (0, exports.encodeOpeningTag)(buffer, 1); (0, exports.encodeContextObjectId)(buffer, 0, value.monitoredObjectId.type, value.monitoredObjectId.instance); (0, exports.encodeContextEnumerated)(buffer, 1, value.monitoredProperty.id); if (value.monitoredProperty.index !== enums_1.default.ASN1_ARRAY_ALL) { (0, exports.encodeContextUnsigned)(buffer, 2, value.monitoredProperty.index); } (0, exports.encodeClosingTag)(buffer, 1); (0, exports.encodeContextBoolean)(buffer, 2, value.issueConfirmedNotifications); (0, exports.encodeContextUnsigned)(buffer, 3, value.timeRemaining); if (value.covIncrement > 0) { (0, exports.encodeContextReal)(buffer, 4, value.covIncrement); } }; const bacappEncodeApplicationData = (buffer, value) => { if (value.value === null) { value.type = enums_1.default.ApplicationTag.NULL; } switch (value.type) { case enums_1.default.ApplicationTag.NULL: encodeApplicationNull(buffer); break; case enums_1.default.ApplicationTag.BOOLEAN: (0, exports.encodeApplicationBoolean)(buffer, value.value); break; case enums_1.default.ApplicationTag.UNSIGNED_INTEGER: (0, exports.encodeApplicationUnsigned)(buffer, value.value); break; case enums_1.default.ApplicationTag.SIGNED_INTEGER: (0, exports.encodeApplicationSigned)(buffer, value.value); break; case enums_1.default.ApplicationTag.REAL: encodeApplicationReal(buffer, value.value); break; case enums_1.default.ApplicationTag.DOUBLE: encodeApplicationDouble(buffer, value.value); break; case enums_1.default.ApplicationTag.OCTET_STRING: (0, exports.encodeApplicationOctetString)(buffer, value.value, 0, value.value.length); break; case enums_1.default.ApplicationTag.CHARACTER_STRING: (0, exports.encodeApplicationCharacterString)(buffer, value.value, value.encoding); break; case enums_1.default.ApplicationTag.BIT_STRING: (0, exports.encodeApplicationBitstring)(buffer, value.value); break; case enums_1.default.ApplicationTag.ENUMERATED: (0, exports.encodeApplicationEnumerated)(buffer, value.value); break; case enums_1.default.ApplicationTag.DATE: (0, exports.encodeApplicationDate)(buffer, value.value); break; case enums_1.default.ApplicationTag.TIME: (0, exports.encodeApplicationTime)(buffer, value.value); break; case enums_1.default.ApplicationTag.TIMESTAMP: (0, exports.bacappEncodeTimestamp)(buffer, value.value); break; case enums_1.default.ApplicationTag.DATETIME: bacappEncodeDatetime(buffer, value.value); break; case enums_1.default.ApplicationTag.OBJECTIDENTIFIER: (0, exports.encodeApplicationObjectId)(buffer, value.value.type, value.value.instance); break; case enums_1.default.ApplicationTag.COV_SUBSCRIPTION: encodeCovSubscription(buffer, value.value); break; case enums_1.default.ApplicationTag.READ_ACCESS_RESULT: (0, exports.encodeReadAccessResult)(buffer, value.value); break; case enums_1.default.ApplicationTag.READ_ACCESS_SPECIFICATION: (0, exports.encodeReadAccessSpecification)(buffer, value.value); break; case undefined: throw new Error("Cannot encode a value if the type has not been specified"); default: throw ("No encode for ApplicationTag type: " + enums_1.default.getEnumName(enums_1.default.ApplicationTag, value.type)); } }; exports.bacappEncodeApplicationData = bacappEncodeApplicationData; const bacappEncodeDeviceObjPropertyRef = (buffer, value) => { (0, exports.encodeContextObjectId)(buffer, 0, value.objectId.type, value.objectId.instance); (0, exports.encodeContextEnumerated)(buffer, 1, value.id); if (value.arrayIndex !== enums_1.default.ASN1_ARRAY_ALL) { (0, exports.encodeContextUnsigned)(buffer, 2, value.arrayIndex); } if (value.deviceIndentifier.type === enums_1.default.ObjectType.DEVICE) { (0, exports.encodeContextObjectId)(buffer, 3, value.deviceIndentifier.type, value.deviceIndentifier.instance); } }; const bacappEncodeContextDeviceObjPropertyRef = (buffer, tagNumber, value) => { (0, exports.encodeOpeningTag)(buffer, tagNumber); bacappEncodeDeviceObjPropertyRef(buffer, value); (0, exports.encodeClosingTag)(buffer, tagNumber); }; exports.bacappEncodeContextDeviceObjPropertyRef = bacappEncodeContextDeviceObjPropertyRef; const bacappEncodePropertyState = (buffer, value) => { switch (value.type) { case enums_1.default.PropertyStates.BOOLEAN_VALUE: (0, exports.encodeContextBoolean)(buffer, 0, value.state === 1); break; case enums_1.default.PropertyStates.BINARY_VALUE: (0, exports.encodeContextEnumerated)(buffer, 1, value.state); break; case enums_1.default.PropertyStates.EVENT_TYPE: (0, exports.encodeContextEnumerated)(buffer, 2, value.state); break; case enums_1.default.PropertyStates.POLARITY: (0, exports.encodeContextEnumerated)(buffer, 3, value.state); break; case enums_1.default.PropertyStates.PROGRAM_CHANGE: (0, exports.encodeContextEnumerated)(buffer, 4, value.state); break; case enums_1.default.PropertyStates.PROGRAM_STATE: (0, exports.encodeContextEnumerated)(buffer, 5, value.state); break; case enums_1.default.PropertyStates.REASON_FOR_HALT: (0, exports.encodeContextEnumerated)(buffer, 6, value.state); break; case enums_1.default.PropertyStates.RELIABILITY: (0, exports.encodeContextEnumerated)(buffer, 7, value.state); break; case enums_1.default.PropertyStates.STATE: (0, exports.encodeContextEnumerated)(buffer, 8, value.state); break; case enums_1.default.PropertyStates.SYSTEM_STATUS: (0, exports.encodeContextEnumerated)(buffer, 9, value.state); break; case enums_1.default.PropertyStates.UNITS: (0, exports.encodeContextEnumerated)(buffer, 10, value.state); break; case enums_1.default.PropertyStates.UNSIGNED_VALUE: (0, exports.encodeContextUnsigned)(buffer, 11, value.state); break; case enums_1.default.PropertyStates.LIFE_SAFETY_MODE: (0, exports.encodeContextEnumerated)(buffer, 12, value.state); break; case enums_1.default.PropertyStates.LIFE_SAFETY_STATE: (0, exports.encodeContextEnumerated)(buffer, 13, value.state); break; default: break; } }; exports.bacappEncodePropertyState = bacappEncodePropertyState; const encodeContextBitstring = (buffer, tagNumber, bitString) => { let bitStringEncodedLength = 1; bitStringEncodedLength += bitstringBytesUsed(bitString); (0, exports.encodeTag)(buffer, tagNumber, true, bitStringEncodedLength); encodeBitstring(buffer, bitString); }; exports.encodeContextBitstring = encodeContextBitstring; const encodeContextSigned = (buffer, tagNumber, value) => { (0, exports.encodeTag)(buffer, tagNumber, true, getSignedLength(value)); encodeBacnetSigned(buffer, value); }; exports.encodeContextSigned = encodeContextSigned; const encodeContextTime = (buffer, tagNumber, value) => { (0, exports.encodeTag)(buffer, tagNumber, true, 4); encodeBacnetTime(buffer, value); }; const bacappEncodeContextDatetime = (buffer, tagNumber, value) => { if (value !== new Date(1, 1, 1)) { (0, exports.encodeOpeningTag)(buffer, tagNumber); bacappEncodeDatetime(buffer, value); (0, exports.encodeClosingTag)(buffer, tagNumber); } else { throw new Error("wrong Datetime while bacapp encoding context"); } }; const decodeTagNumber = (buffer, offset) => { let len = 1; let tagNumber; if (isExtendedTagNumber(buffer[offset])) { tagNumber = buffer[offset + 1]; len++; } else { tagNumber = buffer[offset] >> 4; } return { len, tagNumber, }; }; exports.decodeTagNumber = decodeTagNumber; const decodeIsContextTag = (buffer, offset, tagNumber) => { const result = (0, exports.decodeTagNumber)(buffer, offset); return isContextSpecific(buffer[offset]) && result.tagNumber === tagNumber; }; exports.decodeIsContextTag = decodeIsContextTag; const decodeIsOpeningTagNumber = (buffer, offset, tagNumber) => { const result = (0, exports.decodeTagNumber)(buffer, offset); return isOpeningTag(buffer[offset]) && result.tagNumber === tagNumber; }; exports.decodeIsOpeningTagNumber = decodeIsOpeningTagNumber; const decodeIsClosingTagNumber = (buffer, offset, tagNumber) => { const result = (0, exports.decodeTagNumber)(buffer, offset); return isClosingTag(buffer[offset]) && result.tagNumber === tagNumber; }; exports.decodeIsClosingTagNumber = decodeIsClosingTagNumber; const decodeIsClosingTag = (buffer, offset) => { return (buffer[offset] & 0x07) === 7; }; exports.decodeIsClosingTag = decodeIsClosingTag; const decodeIsOpeningTag = (buffer, offset) => { return (buffer[offset] & 0x07) === 6; }; exports.decodeIsOpeningTag = decodeIsOpeningTag; const decodeObjectId = (buffer, offset, NOT_USED_NUMBER = 0) => { const result = (0, exports.decodeUnsigned)(buffer, offset, 4); const objectType = (result.value >> enums_1.default.ASN1_INSTANCE_BITS) & enums_1.default.ASN1_MAX_OBJECT; const instance = result.value & enums_1.default.ASN1_MAX_INSTANCE; return { len: result.len, objectType, instance, }; }; exports.decodeObjectId = decodeObjectId; const decodeObjectIdSafe = (buffer, offset, lenValue) => { if (lenValue !== 4) { return { len: 0, objectType: 0, instance: 0, }; } else { return (0, exports.decodeObjectId)(buffer, offset); } }; const decodeTagNumberAndValue = (buffer, offset) => { let value; let result = (0, exports.decodeTagNumber)(buffer, offset); let len = result.len; if (isExtendedValue(buffer[offset])) { if (buffer[offset + len] === 255) { len++; result = (0, exports.decodeUnsigned)(buffer, offset + len, 4); len += result.len; value = result.value; } else if (buffer[offset + len] === 254) { len++; result = (0, exports.decodeUnsigned)(buffer, offset + len, 2); len += result.len; value = result.value; } else { value = buffer[offset + len]; len++; } } else if (isOpeningTag(buffer[offset])) { value = 0; } else if (isClosingTag(buffer[offset])) { value = 0; } else { value = buffer[offset] & 0x07; } return { len, tagNumber: result.tagNumber, value, }; }; exports.decodeTagNumberAndValue = decodeTagNumberAndValue; const bacappDecodeApplicationData = (buffer, offset, maxOffset, objectType, propertyId) => { if (!isContextSpecific(buffer[offset])) { let result = (0, exports.decodeTagNumberAndValue)(buffer, offset); if (result) { let len = result.len; result = bacappDecodeData(buffer, offset + len, maxOffset, result.tagNumber, result.value); if (!result) { return undefined; } let resObj = { len: len + result.len, type: result.type, value: result.value, }; // HACK: Drop string specific handling ASAP if (result.encoding !== undefined) resObj.encoding = result.encoding; return resObj; } } else { return bacappDecodeContextApplicationData(buffer, offset, maxOffset, objectType, propertyId); } }; exports.bacappDecodeApplicationData = bacappDecodeApplicationData; const encodeReadAccessResult = (buffer, value) => { (0, exports.encodeContextObjectId)(buffer, 0, value.objectId.type, value.objectId.instance); (0, exports.encodeOpeningTag)(buffer, 1); value.values.forEach((item) => { (0, exports.encodeContextEnumerated)(buffer, 2, item.property.id); if (item.property.index !== enums_1.default.ASN1_ARRAY_ALL) { (0, exports.encodeContextUnsigned)(buffer, 3, item.property.index); } if (item.value && item.value[0] && item.value[0].value && item.value[0].value.type === "BacnetError") { (0, exports.encodeOpeningTag)(buffer, 5); (0, exports.encodeApplicationEnumerated)(buffer, item.value[0].value.errorClass); (0, exports.encodeApplicationEnumerated)(buffer, item.value[0].value.errorCode); (0, exports.encodeClosingTag)(buffer, 5); } else { if (!item.value) throw new Error("No value in item"); (0, exports.encodeOpeningTag)(buffer, 4); item.value.forEach((subItem) => { (0, exports.bacappEncodeApplicationData)(buffer, subItem); }); (0, exports.encodeClosingTag)(buffer, 4); } }); (0, exports.encodeClosingTag)(buffer, 1); }; exports.encodeReadAccessResult = encodeReadAccessResult; const decodeReadAccessResult = (buffer, offset, apduLen) => { let len = 0; let value = {}; if (!(0, exports.decodeIsContextTag)(buffer, offset + len, 0)) { return undefined; } len++; let result = (0, exports.decodeObjectId)(buffer, offset + len); value.objectId = { type: result.objectType, instance: result.instance, }; len += result.len; if (!(0, exports.decodeIsOpeningTagNumber)(buffer, offset + len, 1)) { return -1; // TODO: why? } len++; const values = []; while (apduLen - len > 0) { let newEntry = {}; if ((0, exports.decodeIsClosingTagNumber)(buffer, offset + len, 1)) { len++; break; } result = (0, exports.decodeTagNumberAndValue)(buffer, offset + len); len += result.len; if (result.tagNumber !== 2) { return undefined; } result = (0, exports.decodeEnumerated)(buffer, offset + len, result.value); newEntry.id = result.value; len += result.len; result = (0, exports.decodeTagNumberAndValue)(buffer, offset + len); if (result.tagNumber === 3) { len += result.len; result = (0, exports.decodeUnsigned)(buffer, offset + len, result.value); newEntry.index = result.value; len += result.len; } else { newEntry.index = enums_1.default.ASN1_ARRAY_ALL; } result = (0, exports.decodeTagNumberAndValue)(buffer, offset + len); len += result.len; if (result.tagNumber === 4) { const localValues = []; while (len + offset <= buffer.length && !(0, exports.decodeIsClosingTagNumber)(buffer, offset + len, 4)) { let localResult = (0, exports.bacappDecodeApplicationData)(buffer, offset + len, apduLen + offset - 1, value.objectId.type, newEntry.id); if (!localResult) { return undefined; } len += localResult.len; let resObj = { value: localResult.value, type: localResult.type, }; // HACK: Drop string specific handling ASAP if (localResult.encoding !== undefined) resObj.encoding = localResult.encoding; localValues.push(resObj); } if (!(0, exports.decodeIsClosingTagNumber)(buffer, offset + len, 4)) { return undefined; } if (localValues.count === 2 && localValues[0].type === enums_1.default.ApplicationTag.DATE && localValues[1].type === enums_1.default.ApplicationTag.TIME) { const date = localValues[0].value; const time = localValues[1].value; const bdatetime = new Date(date.year, date.month, date.day, time.hour, time.minute, time.second, time.millisecond); newEntry.value = [ { type: enums_1.default.ApplicationTag.DATETIME, value: bdatetime }, ]; } else { newEntry.value = localValues; } len++; } else if (result.tagNumber === 5) { let err = {}; result = (0, exports.decodeTagNumberAndValue)(buffer, offset + len); len += result.len; result = (0, exports.decodeEnumerated)(buffer, offset + len, result.value); len += result.len; err.errorClass = result.value; result = (0, exports.decodeTagNumberAndValue)(buffer, offset + len); len += result.len; result = (0, exports.decodeEnumerated)(buffer, offset + len, result.value); len += result.len; err.errorCode = result.value; if (!(0, exports.decodeIsClosingTagNumber)(buffer, offset + len, 5)) { return undefined; } len++; newEntry.value = [ { type: enums_1.default.ApplicationTag.ERROR, value: err, }, ]; } values.push(newEntry); } value.values = values; return { len, value, }; }; exports.decodeReadAccessResult = decodeReadAccessResult; const decodeSigned = (buffer, offset, length) => { return { len: length, value: buffer.readIntBE(offset, length, true), }; }; exports.decodeSigned = decodeSigned; const decodeReal = (buffer, offset) => { return { len: 4, value: buffer.readFloatBE(offset, true), }; }; exports.decodeReal = decodeReal; const decodeRealSafe = (buffer, offset, lenValue) => { if (lenValue !== 4) { return { len: lenValue, value: 0, }; } else { return (0, exports.decodeReal)(buffer, offset); } }; const decodeDouble = (buffer, offset) => { return { len: 8, value: buffer.readDoubleBE(offset, true), }; }; const decodeDoubleSafe = (buffer, offset, lenValue) => { if (lenValue !== 8) { return { len: lenValue, value: 0, }; } else { return decodeDouble(buffer, offset); } }; const decodeOctetString = (buffer, offset, maxLength, octetStringOffset, octetStringLength) => { const octetString = []; for (let i = octetStringOffset; i < octetStringOffset + octetStringLength; i++) { octetString.push(buffer[offset + i]); } return { len: octetStringLength, value: octetString, }; }; exports.decodeOctetString = decodeOctetString; const multiCharsetCharacterstringDecode = (buffer, offset, maxLength, encoding, length) => { const stringBuf = Buffer.alloc(length); buffer.copy(stringBuf, 0, offset, offset + length); return { value: iconv_lite_1.default.decode(stringBuf, getEncodingType(encoding, buffer, offset)), len: length + 1, encoding, }; }; const decodeCharacterString = (buffer, offset, maxLength, lenValue) => { return multiCharsetCharacterstringDecode(buffer, offset + 1, maxLength, buffer[offset], lenValue - 1); }; exports.decodeCharacterString = decodeCharacterString; const bitstringSetBitsUsed = (bitString, bytesUsed, unusedBits) => { bitString.bitsUsed = bytesUsed * 8; bitString.bitsUsed -= unusedBits; }; const decodeBitstring = (buffer, offset, lenValue) => { let len = 0; let bitString = {}; bitString.value = []; if (lenValue > 0) { const bytesUsed = lenValue - 1; if (bytesUsed <= enums_1.default.ASN1_MAX_BITSTRING_BYTES) { len = 1; for (let i = 0; i < bytesUsed; i++) { bitString.value.push(byteReverseBits(buffer[offset + len++])); } const unusedBits = buffer[offset] & 0x07; bitstringSetBitsUsed(bitString, bytesUsed, unusedBits); } } return { len, value: bitString, }; }; exports.decodeBitstring = decodeBitstring; const decodeDate = (buffer, offset) => { let date; const year = buffer[offset] + 1900; const month = buffer[offset + 1]; const day = buffer[offset + 2]; const wday = buffer[offset + 3]; if (month === 0xff && day === 0xff && wday === 0xff && year - 1900 === 0xff) { date = new Date(1, 1, 1); } else { date = new Date(year, month, day); } return { len: 4, value: date, }; }; exports.decodeDate = decodeDate; const decodeDateSafe = (buffer, offset, lenValue) => { if (lenValue !== 4) { return { len: lenValue, value: new Date(1, 1, 1), }; } else { return (0, exports.decodeDate)(buffer, offset); } }; const decodeApplicationDate = (buffer, offset) => { const result = (0, exports.decodeTagNumber)(buffer, offset); if (result.tagNumber === enums_1.default.ApplicationTag.DATE) { const value = (0, exports.decodeDate)(buffer, offset + 1); return { len: value.len + 1, value, }; } return undefined; }; exports.decodeApplicationDate = decodeApplicationDate; const decodeBacnetTime = (buffer, offset, value = new Date(1, 1, 1)) => { const hour = buffer[offset + 0]; const min = buffer[offset + 1]; const sec = buffer[offset + 2]; let hundredths = buffer[offset + 3]; if (hour === 0xff && min === 0xff && sec === 0xff && hundredths === 0xff) { value = new Date(1, 1, 1); } else { if (hundredths > 100) hundredths = 0; value = new Date(1, 1, 1, hour, min, sec, hundredths * 10); } return { len: 4, value, }; }; exports.decodeBacnetTime = decodeBacnetTime; const decodeBacnetTimeSafe = (buffer, offset, lenValue) => { if (lenValue !== 4) { return { len: lenValue, value: new Date(1, 1, 1), }; } else { return (0, exports.decodeBacnetTime)(buffer, offset); } }; const decodeApplicationTime = (buffer, offset) => { const result = (0, exports.decodeTagNumber)(buffer, offset); if (result.tagNumber === enums_1.default.ApplicationTag.TIME) { const value = (0, exports.decodeBacnetTime)(buffer, offset + 1); return { len: value.len + 1, value, }; } return undefined; }; exports.decodeApplicationTime = decodeApplicationTime; const decodeBacnetDatetime = (buffer, offset) => { let len = 0; const date = (0, exports.decodeApplicationDate)(buffer, offset + len); len += date.len; const time = (0, exports.decodeApplicationTime)(buffer, offset + len); len += time.len; return { len, value: new Date(date.year, date.month, date.day, time.hour, time.minute, time.second, time.millisecond), }; }; const bacappDecodeData = (buffer, offset, maxLength, tagDataType, lenValueType) => { let result; let value = { len: 0, type: tagDataType, }; switch (tagDataType) { case enums_1.default.ApplicationTag.NULL: value.value = null; break; case enums_1.default.ApplicationTag.BOOLEAN: value.value = lenValueType > 0; break; case enums_1.default.ApplicationTag.UNSIGNED_INTEGER: result = (0, exports.decodeUnsigned)(buffer, offset, lenValueType); value.len += result.len; value.value = result.value; break; case enums_1.default.ApplicationTag.SIGNED_INTEGER: result = (0, exports.decodeSigned)(buffer, offset, lenValueType); value.len += result.len; value.value = result.value; break; case enums_1.default.ApplicationTag.REAL: result = decodeRealSafe(buffer, offset, lenValueType); value.len += result.len; value.value = result.value; break; case enums_1.default.ApplicationTag.DOUBLE: result = decodeDoubleSafe(buffer, offset, lenValueType); value.len += result.len; value.value = result.value; break; case enums_1.default.ApplicationTag.OCTET_STRING: result = (0, exports.decodeOctetString)(buffer, offset, maxLength, 0, lenValueType); value.len += result.len; value.value = result.value; break; case enums_1.default.ApplicationTag.CHARACTER_STRING: result = (0, exports.decodeCharacterString)(buffer, offset, maxLength, lenValueType); value.len += result.len; value.value = result.value; value.encoding = result.encoding; break; case enums_1.default.ApplicationTag.BIT_STRING: result = (0, exports.decodeBitstring)(buffer, offset, lenValueType); value.len += result.len; value.value = result.value; break; case enums_1.default.ApplicationTag.ENUMERATED: result = (0, exports.decodeEnumerated)(buffer, offset, lenValueType); value.len += result.len; value.value = result.value; break; case enums_1.default.ApplicationTag.DATE: result = decodeDateSafe(buffer, offset, lenValueType); value.len += result.len; value.value = result.value; break; case enums_1.default.ApplicationTag.TIME: result = decodeBacnetTimeSafe(buffer, offset, lenValueType); value.len += result.len; value.value = result.value; break; case enums_1.default.ApplicationTag.OBJECTIDENTIFIER: result = decodeObjectIdSafe(buffer, offset, lenValueType); value.len += result.len; value.value = { type: result.objectType, instance: result.instance }; break; default: break; } return value; }; const bacappContextTagType = (property, tagNumber) => { let tag = 0; switch (property) { case enums_1.default.PropertyIdentifier.ACTUAL_SHED_LEVEL: case enums_1.default.PropertyIdentifier.REQUESTED_SHED_LEVEL: case enums_1.default.PropertyIdentifier.EXPECTED_SHED_LEVEL: switch (tagNumber) { case 0: case 1: tag = enums_1.default.ApplicationTag.UNSIGNED_INTEGER; break; case 2: tag = enums_1.default.ApplicationTag.REAL; break; default: break; } break; case enums_1.default.PropertyIdentifier.ACTION: switch (tagNumber) { case 0: case 1: tag = enums_1.default.ApplicationTag.OBJECTIDENTIFIER; break; case 2: tag = enums_1.default.ApplicationTag.ENUMERATED; break; case 3: case 5: case 6: tag = enums_1.default.ApplicationTag.UNSIGNED_INTEGER; break; case 7: case 8: tag = enums_1.default.ApplicationTag.BOOLEAN; break; default: break; } break; case enums_1.default.PropertyIdentifier.LIST_OF_GROUP_MEMBERS: switch (tagNumber) { case 0: tag = enums_1.default.ApplicationTag.OBJECTIDENTIFIER; break; default: break; } break; case enums_1.default.PropertyIdentifier.EXCEPTION_SCHEDULE: switch (tagNumber) { case 1: tag = enums_1.default.ApplicationTag.OBJECTIDENTIFIER; break; case 3: tag = enums_1.default.ApplicationTag.UNSIGNED_INTEGER; break; default: break; } break; case enums_1.default.PropertyIdentifier.LOG_DEVICE_OBJECT_PROPERTY: switch (tagNumber) { case 0: case 3: tag = enums_1.default.ApplicationTag.OBJECTIDENTIFIER; break; case 1: tag = enums_1.default.ApplicationTag.ENUMERATED; break; case 2: tag = enums_1.default.ApplicationTag.UNSIGNED_INTEGER; break; default: break; } break; case enums_1.default.PropertyIdentifier.SUBORDINATE_LIST: switch (tagNumber) { case 0: case 1: tag = enums_1.default.ApplicationTag.OBJECTIDENTIFIER; break; default: break; } break; case enums_1.default.PropertyIdentifier.RECIPIENT_LIST: switch (tagNumber) { case 0: tag = enums_1.default.ApplicationTag.OBJECTIDENTIFIER; break; default: break; } break; case enums_1.default.PropertyIdentifier.ACTIVE_COV_SUBSCRIPTIONS: switch (tagNumber) { case 0: case 1: break; case 2: tag = enums_1.default.ApplicationTag.BOOLEAN; break; case 3: tag = enums_1.default.ApplicationTag.UNSIGNED_INTEGER; break; case 4: tag = enums_1.default.ApplicationTag.REAL; break; default: break; } break; default: break; } return tag; }; const decodeDeviceObjPropertyRef = (buffer, offset, NOTUSEDARG = 0) => { let len = 0; let arrayIndex = enums_1.default.ASN1_ARRAY_ALL; if (!(0, exports.decodeIsContextTag)(buffer, offset + len, 0)) { return undefined; } len++; let objectId = (0, exports.decodeObjectId)(buffer, offset + len); len += objectId.len; let result = (0, exports.decodeTagNumberAndValue)(buffer, offset + len); len += result.len; if (result.tagNumber !== 1) { return undefined; } let id = (0, exports.decodeEnumerated)(buffer, offset + len, result.value); len += id.len; result = (0, exports.decodeTagNumberAndValue)(buffer, offset + len); if (result.tagNumber === 2) { len += result.len; arrayIndex = (0, exports.decodeUnsigned)(buffer, offset + len, result.value); len += arrayIndex.len; } if ((0, exports.decodeIsContextTag)(buffer, offset + len, 3)) { if (!isClosingTag(buffer[offset + len])) { len++; objectId = (0, exports.decodeObjectId)(buffer, offset + len); len += objectId.len; } } return { len, value: { objectId: objectId, id: id, }, }; }; const decodeReadAccessSpecification = (buffer, offset, apduLen) => { let len = 0; let value = {}; if (!(0, exports.decodeIsContextTag)(buffer, offset + len, 0)) { return undefined; } len++; let decodedValue = (0, exports.decodeObjectId)(buffer, offset + len); value.objectId = { type: decodedValue.objectType, instance: decodedValue.instance, }; len += decodedValue.len; if (!(0, exports.decodeIsOpeningTagNumber)(buffer, offset + len, 1)) { return undefined; } len++; const propertyIdAndArrayIndex = []; while (apduLen - len > 1 && !(0, exports.decodeIsClosingTagNumber)(buffer, offset + len, 1)) { let propertyRef = {}; if (!isContextSpecific(buffer[offset + len])) { return undefined; } let result = (0, exports.decodeTagN