UNPKG

@halsystems/red-bacnet

Version:
1,297 lines 65.1 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.decodeContextCharacterString = exports.bacappEncodeContextTimestamp = exports.bacappEncodeTimestamp = exports.decodeReadAccessSpecification = 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.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 = void 0; exports.encodeContextCharacterString = exports.encodeApplicationCharacterString = exports.decodeContextObjectId = void 0; const iconv = require("iconv-lite"); const baEnum = require("./enum"); const getBuffer = () => ({ 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 baEnum.CharacterStringEncoding.UTF_8: return 'utf8'; case baEnum.CharacterStringEncoding.UCS_2: if (decodingBuffer && decodingBuffer[decodingOffset] === 0xFF && decodingBuffer[decodingOffset + 1] === 0xFE) { return 'ucs2'; } return 'UTF-16BE'; // Default to big-endian case baEnum.CharacterStringEncoding.ISO_8859_1: return 'latin1'; case baEnum.CharacterStringEncoding.UCS_4: return 'utf8'; // HACK: There is currently no support for UTF-32 case baEnum.CharacterStringEncoding.MICROSOFT_DBCS: return 'cp850'; case baEnum.CharacterStringEncoding.JIS_X_0208: return 'Shift_JIS'; default: return 'utf8'; } }; const encodeUnsigned = (buffer, value, length) => { buffer.buffer.writeUIntBE(value, buffer.offset, length); buffer.offset += length; }; const encodeBacnetUnsigned = (buffer, value) => { encodeUnsigned(buffer, value, getUnsignedLength(value)); }; const encodeSigned = (buffer, value, length) => { buffer.buffer.writeIntBE(value, buffer.offset, length); buffer.offset += length; }; const encodeBacnetSigned = (buffer, value) => { encodeSigned(buffer, value, getSignedLength(value)); }; const encodeBacnetReal = (buffer, value) => { buffer.buffer.writeFloatBE(value, buffer.offset); buffer.offset += 4; }; const encodeBacnetDouble = (buffer, value) => { buffer.buffer.writeDoubleBE(value, buffer.offset); buffer.offset += 8; }; const decodeUnsigned = (buffer, offset, length) => ({ len: length, value: buffer.readUIntBE(offset, length) }); exports.decodeUnsigned = decodeUnsigned; const decodeEnumerated = (buffer, offset, lenValue) => { return (0, exports.decodeUnsigned)(buffer, offset, lenValue); }; exports.decodeEnumerated = decodeEnumerated; const encodeBacnetObjectId = (buffer, objectType, instance) => { const value = (((objectType & baEnum.ASN1_MAX_OBJECT) << baEnum.ASN1_INSTANCE_BITS) | (instance & baEnum.ASN1_MAX_INSTANCE)) >>> 0; 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; encodeUnsigned(buffer, lenValueType, 2); } else { tmp[len++] = 255; Buffer.from(tmp).copy(buffer.buffer, buffer.offset, 0, len); buffer.offset += len; 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, baEnum.ApplicationTags.OCTET_STRING, false, octetCount); encodeOctetString(buffer, octetString, octetOffset, octetCount); }; exports.encodeApplicationOctetString = encodeApplicationOctetString; const encodeApplicationNull = (buffer) => { buffer.buffer[buffer.offset++] = baEnum.ApplicationTags.NULL; }; const encodeApplicationBoolean = (buffer, booleanValue) => { (0, exports.encodeTag)(buffer, baEnum.ApplicationTags.BOOLEAN, false, booleanValue ? 1 : 0); }; exports.encodeApplicationBoolean = encodeApplicationBoolean; const encodeApplicationReal = (buffer, value) => { (0, exports.encodeTag)(buffer, baEnum.ApplicationTags.REAL, false, 4); encodeBacnetReal(buffer, value); }; const encodeApplicationDouble = (buffer, value) => { (0, exports.encodeTag)(buffer, baEnum.ApplicationTags.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, baEnum.ApplicationTags.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, baEnum.ApplicationTags.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, baEnum.ApplicationTags.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, baEnum.ApplicationTags.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) { if (octetIndex < baEnum.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, baEnum.ApplicationTags.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() >= 1900) { buffer.buffer[buffer.offset++] = (value.getFullYear() - 1900); } else if (value.getFullYear() < 0x100) { buffer.buffer[buffer.offset++] = value.getFullYear(); } else { return; } buffer.buffer[buffer.offset++] = value.getMonth(); buffer.buffer[buffer.offset++] = value.getDate(); buffer.buffer[buffer.offset++] = (value.getDay() === 0) ? 7 : value.getDay(); }; const encodeApplicationDate = (buffer, value) => { (0, exports.encodeTag)(buffer, baEnum.ApplicationTags.DATE, false, 4); 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, baEnum.ApplicationTags.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 !== baEnum.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); 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 !== baEnum.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 = baEnum.ApplicationTags.NULL; } switch (value.type) { case baEnum.ApplicationTags.NULL: encodeApplicationNull(buffer); break; case baEnum.ApplicationTags.BOOLEAN: (0, exports.encodeApplicationBoolean)(buffer, value.value); break; case baEnum.ApplicationTags.UNSIGNED_INTEGER: (0, exports.encodeApplicationUnsigned)(buffer, value.value); break; case baEnum.ApplicationTags.SIGNED_INTEGER: (0, exports.encodeApplicationSigned)(buffer, value.value); break; case baEnum.ApplicationTags.REAL: encodeApplicationReal(buffer, value.value); break; case baEnum.ApplicationTags.DOUBLE: encodeApplicationDouble(buffer, value.value); break; case baEnum.ApplicationTags.OCTET_STRING: (0, exports.encodeApplicationOctetString)(buffer, value.value, 0, value.value.length); break; case baEnum.ApplicationTags.CHARACTER_STRING: (0, exports.encodeApplicationCharacterString)(buffer, value.value, value.encoding); break; case baEnum.ApplicationTags.BIT_STRING: (0, exports.encodeApplicationBitstring)(buffer, value.value); break; case baEnum.ApplicationTags.ENUMERATED: (0, exports.encodeApplicationEnumerated)(buffer, value.value); break; case baEnum.ApplicationTags.DATE: (0, exports.encodeApplicationDate)(buffer, value.value); break; case baEnum.ApplicationTags.TIME: (0, exports.encodeApplicationTime)(buffer, value.value); break; case baEnum.ApplicationTags.TIMESTAMP: (0, exports.bacappEncodeTimestamp)(buffer, value.value); break; case baEnum.ApplicationTags.DATETIME: bacappEncodeDatetime(buffer, value.value); break; case baEnum.ApplicationTags.OBJECTIDENTIFIER: (0, exports.encodeApplicationObjectId)(buffer, value.value.type, value.value.instance); break; case baEnum.ApplicationTags.COV_SUBSCRIPTION: encodeCovSubscription(buffer, value.value); break; case baEnum.ApplicationTags.READ_ACCESS_RESULT: (0, exports.encodeReadAccessResult)(buffer, value.value); break; case baEnum.ApplicationTags.READ_ACCESS_SPECIFICATION: (0, exports.encodeReadAccessSpecification)(buffer, value.value); break; default: throw new Error('Unknown 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 !== baEnum.ASN1_ARRAY_ALL) { (0, exports.encodeContextUnsigned)(buffer, 2, value.arrayIndex); } if (value.deviceIndentifier.type === baEnum.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 baEnum.PropertyStates.BOOLEAN_VALUE: (0, exports.encodeContextBoolean)(buffer, 0, value.state === 1 ? true : false); break; case baEnum.PropertyStates.BINARY_VALUE: (0, exports.encodeContextEnumerated)(buffer, 1, value.state); break; case baEnum.PropertyStates.EVENT_TYPE: (0, exports.encodeContextEnumerated)(buffer, 2, value.state); break; case baEnum.PropertyStates.POLARITY: (0, exports.encodeContextEnumerated)(buffer, 3, value.state); break; case baEnum.PropertyStates.PROGRAM_CHANGE: (0, exports.encodeContextEnumerated)(buffer, 4, value.state); break; case baEnum.PropertyStates.PROGRAM_STATE: (0, exports.encodeContextEnumerated)(buffer, 5, value.state); break; case baEnum.PropertyStates.REASON_FOR_HALT: (0, exports.encodeContextEnumerated)(buffer, 6, value.state); break; case baEnum.PropertyStates.RELIABILITY: (0, exports.encodeContextEnumerated)(buffer, 7, value.state); break; case baEnum.PropertyStates.STATE: (0, exports.encodeContextEnumerated)(buffer, 8, value.state); break; case baEnum.PropertyStates.SYSTEM_STATUS: (0, exports.encodeContextEnumerated)(buffer, 9, value.state); break; case baEnum.PropertyStates.UNITS: (0, exports.encodeContextEnumerated)(buffer, 10, value.state); break; case baEnum.PropertyStates.UNSIGNED_VALUE: (0, exports.encodeContextUnsigned)(buffer, 11, value.state); break; case baEnum.PropertyStates.LIFE_SAFETY_MODE: (0, exports.encodeContextEnumerated)(buffer, 12, value.state); break; case baEnum.PropertyStates.LIFE_SAFETY_STATE: (0, exports.encodeContextEnumerated)(buffer, 13, value.state); break; default: break; } }; exports.bacappEncodePropertyState = bacappEncodePropertyState; const encodeContextBitstring = (buffer, tagNumber, bitString) => { const bitStringEncodedLength = bitstringBytesUsed(bitString) + 1; (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); } }; 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: len, tagNumber: 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) => { const result = (0, exports.decodeUnsigned)(buffer, offset, 4); const objectType = (result.value >> baEnum.ASN1_INSTANCE_BITS) & baEnum.ASN1_MAX_OBJECT; const instance = result.value & baEnum.ASN1_MAX_INSTANCE; return { len: result.len, objectType: objectType, instance: 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; const tag = (0, exports.decodeTagNumber)(buffer, offset); let len = tag.len; if (isExtendedValue(buffer[offset])) { if (buffer[offset + len] === 255) { len++; const result = (0, exports.decodeUnsigned)(buffer, offset + len, 4); len += result.len; value = result.value; } else if (buffer[offset + len] === 254) { len++; const 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: len, tagNumber: tag.tagNumber, value: value }; }; exports.decodeTagNumberAndValue = decodeTagNumberAndValue; const bacappDecodeApplicationData = (buffer, offset, maxOffset, objectType, propertyId) => { if (!isContextSpecific(buffer[offset])) { const tag = (0, exports.decodeTagNumberAndValue)(buffer, offset); if (tag) { const len = tag.len; const result = bacappDecodeData(buffer, offset + len, maxOffset, tag.tagNumber, tag.value); if (!result) return; let resObj = { len: len + result.len, type: result.type, value: result.value }; if (result.originalBitString) { //protocols supported addition resObj.originalBitString = result.originalBitString; } // 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 !== baEnum.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 { (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; const value = {}; if (!(0, exports.decodeIsContextTag)(buffer, offset + len, 0)) return; 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; len++; const values = []; while ((apduLen - len) > 0) { const 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; 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 = baEnum.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)) { const localResult = (0, exports.bacappDecodeApplicationData)(buffer, offset + len, apduLen + offset - 1, value.objectId.type, newEntry.id); if (!localResult) return; len += localResult.len; const 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; if ((localValues.length === 2) && (localValues[0].type === baEnum.ApplicationTags.DATE) && (localValues[1].type === baEnum.ApplicationTags.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: baEnum.ApplicationTags.DATETIME, value: bdatetime }]; } else { newEntry.value = localValues; } len++; } else if (result.tagNumber === 5) { const 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; len++; newEntry.value = [{ type: baEnum.ApplicationTags.ERROR, value: err }]; } values.push(newEntry); } value.values = values; return { len: len, value: value }; }; exports.decodeReadAccessResult = decodeReadAccessResult; const decodeSigned = (buffer, offset, length) => ({ len: length, value: buffer.readIntBE(offset, length) }); exports.decodeSigned = decodeSigned; const decodeReal = (buffer, offset) => ({ len: 4, value: buffer.readFloatBE(offset) }); 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) => ({ len: 8, value: buffer.readDoubleBE(offset) }); 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.decode(stringBuf, getEncodingType(encoding, buffer, offset)), len: length + 1, encoding: 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; const bitString = { value: [], bitsUsed: 0 }; const originalBitString = { value: [] }; if (lenValue > 0) { const bytesUsed = lenValue - 1; if (bytesUsed <= baEnum.ASN1_MAX_BITSTRING_BYTES) { len = 1; for (let i = 0; i < bytesUsed; i++) { originalBitString.value.push(buffer[offset + len]); bitString.value.push(byteReverseBits(buffer[offset + len++])); } const unusedBits = buffer[offset] & 0x07; bitstringSetBitsUsed(bitString, bytesUsed, unusedBits); } } return { len: len, value: bitString, originalBitString: originalBitString }; }; 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 === baEnum.ApplicationTags.DATE) { const value = (0, exports.decodeDate)(buffer, offset + 1); return { len: value.len + 1, value: value.value }; } }; exports.decodeApplicationDate = decodeApplicationDate; const decodeBacnetTime = (buffer, offset) => { let value; 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: value }; }; exports.decodeBacnetTime = decodeBacnetTime; const decodeBacnetTimeSafe = (buffer, offset, len) => { if (len !== 4) { return { len, 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 === baEnum.ApplicationTags.TIME) { const value = (0, exports.decodeBacnetTime)(buffer, offset + 1); return { len: value.len + 1, value: value.value }; } }; exports.decodeApplicationTime = decodeApplicationTime; const decodeBacnetDatetime = (buffer, offset) => { let len = 0; const rawDate = (0, exports.decodeApplicationDate)(buffer, offset + len); len += rawDate.len; const date = rawDate.value; const rawTime = (0, exports.decodeApplicationTime)(buffer, offset + len); len += rawTime.len; const time = rawTime.value; return { len: len, value: new Date(date.getFullYear(), date.getMonth(), date.getDay(), time.getHours(), time.getMinutes(), time.getSeconds(), time.getMilliseconds()) }; }; const bacappDecodeData = (buffer, offset, maxLength, tagDataType, lenValueType) => { let result; const value = { len: 0, type: tagDataType }; switch (tagDataType) { case baEnum.ApplicationTags.NULL: value.value = null; break; case baEnum.ApplicationTags.BOOLEAN: value.value = lenValueType > 0 ? true : false; break; case baEnum.ApplicationTags.UNSIGNED_INTEGER: result = (0, exports.decodeUnsigned)(buffer, offset, lenValueType); value.len += result.len; value.value = result.value; break; case baEnum.ApplicationTags.SIGNED_INTEGER: result = (0, exports.decodeSigned)(buffer, offset, lenValueType); value.len += result.len; value.value = result.value; break; case baEnum.ApplicationTags.REAL: result = decodeRealSafe(buffer, offset, lenValueType); value.len += result.len; value.value = result.value; break; case baEnum.ApplicationTags.DOUBLE: result = decodeDoubleSafe(buffer, offset, lenValueType); value.len += result.len; value.value = result.value; break; case baEnum.ApplicationTags.OCTET_STRING: result = (0, exports.decodeOctetString)(buffer, offset, maxLength, 0, lenValueType); value.len += result.len; value.value = result.value; break; case baEnum.ApplicationTags.CHARACTER_STRING: result = (0, exports.decodeCharacterString)(buffer, offset, maxLength, lenValueType); value.len += result.len; value.value = result.value; value.encoding = result.encoding; break; case baEnum.ApplicationTags.BIT_STRING: result = (0, exports.decodeBitstring)(buffer, offset, lenValueType); value.len += result.len; value.value = result.value; if (result.originalBitString) { //protocols supported addition value.originalBitString = result.originalBitString; } break; case baEnum.ApplicationTags.ENUMERATED: result = (0, exports.decodeEnumerated)(buffer, offset, lenValueType); value.len += result.len; value.value = result.value; break; case baEnum.ApplicationTags.DATE: result = decodeDateSafe(buffer, offset, lenValueType); value.len += result.len; value.value = result.value; break; case baEnum.ApplicationTags.TIME: result = decodeBacnetTimeSafe(buffer, offset, lenValueType); value.len += result.len; value.value = result.value; break; case baEnum.ApplicationTags.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 baEnum.PropertyIdentifier.ACTUAL_SHED_LEVEL: case baEnum.PropertyIdentifier.REQUESTED_SHED_LEVEL: case baEnum.PropertyIdentifier.EXPECTED_SHED_LEVEL: switch (tagNumber) { case 0: case 1: tag = baEnum.ApplicationTags.UNSIGNED_INTEGER; break; case 2: tag = baEnum.ApplicationTags.REAL; break; default: break; } break; case baEnum.PropertyIdentifier.ACTION: switch (tagNumber) { case 0: case 1: tag = baEnum.ApplicationTags.OBJECTIDENTIFIER; break; case 2: tag = baEnum.ApplicationTags.ENUMERATED; break; case 3: case 5: case 6: tag = baEnum.ApplicationTags.UNSIGNED_INTEGER; break; case 7: case 8: tag = baEnum.ApplicationTags.BOOLEAN; break; default: break; } break; case baEnum.PropertyIdentifier.LIST_OF_GROUP_MEMBERS: switch (tagNumber) { case 0: tag = baEnum.ApplicationTags.OBJECTIDENTIFIER; break; default: break; } break; case baEnum.PropertyIdentifier.EXCEPTION_SCHEDULE: switch (tagNumber) { case 1: tag = baEnum.ApplicationTags.OBJECTIDENTIFIER; break; case 3: tag = baEnum.ApplicationTags.UNSIGNED_INTEGER; break; default: break; } break; case baEnum.PropertyIdentifier.LOG_DEVICE_OBJECT_PROPERTY: switch (tagNumber) { case 0: case 3: tag = baEnum.ApplicationTags.OBJECTIDENTIFIER; break; case 1: tag = baEnum.ApplicationTags.ENUMERATED; break; case 2: tag = baEnum.ApplicationTags.UNSIGNED_INTEGER; break; default: break; } break; case baEnum.PropertyIdentifier.SUBORDINATE_LIST: switch (tagNumber) { case 0: case 1: tag = baEnum.ApplicationTags.OBJECTIDENTIFIER; break; default: break; } break; case baEnum.PropertyIdentifier.RECIPIENT_LIST: switch (tagNumber) { case 0: tag = baEnum.ApplicationTags.OBJECTIDENTIFIER; break; default: break; } break; case baEnum.PropertyIdentifier.ACTIVE_COV_SUBSCRIPTIONS: switch (tagNumber) { case 0: case 1: break; case 2: tag = baEnum.ApplicationTags.BOOLEAN; break; case 3: tag = baEnum.ApplicationTags.UNSIGNED_INTEGER; break; case 4: tag = baEnum.ApplicationTags.REAL; break; default: break; } break; default: break; } return tag; }; const decodeDeviceObjPropertyRef = (buffer, offset) => { let len = 0; let arrayIndex = baEnum.ASN1_ARRAY_ALL; if (!(0, exports.decodeIsContextTag)(buffer, offset + len, 0)) return; 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; const 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; // FIXME: This doesn't seem to be used 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: len, value: { objectId: objectId, id: id } }; }; const decodeReadAccessSpecification = (buffer, offset, apduLen) => { let len = 0; const value = {}; if (!(0, exports.decodeIsContextTag)(buffer, offset + len, 0)) return; 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; len++; const propertyIdAndArrayIndex = []; while ((apduLen - len) > 1 && !(0, exports.decodeIsClosingTagNumber)(buffer, offset + len, 1)) { const propertyRef = {}; if (!isContextSpecific(buffer[offset + len])) return; const result = (0, exports.decodeTagNumberAndValue)(buffer, offset + len); len += result.len; if (result.tagNumber !== 0) return; if ((len + result.value) >= apduLen) return; decodedValue = (0, exports.decodeEnumerated)(buffer, offset + len, result.value); propertyRef.id = decodedValue.value; len += decodedValue.len; propertyRef.index = baEnum.ASN1_ARRAY_ALL; if (isContextSpecific(buffer[offset + len]) && !isClosingTag(buffer[offset + len])) { const tmp = (0, exports.decodeTagNumberAndValue)(buffer, offset + len); if (tmp.tagNumber === 1) { len += tmp.len; if ((len + tmp.value) >= apduLen) return; decodedValue = (0, exports.decodeUnsigned)(buffer, offset + len, tmp.value); propertyRef.index = decodedValue.value; len += decodedValue.len; } } propertyIdAndArrayIndex.push(propertyRef); } if (!(0, exports.decodeIsClosingTagNumber)(buffer, offset + len, 1)) return; len++; value.properties = propertyIdAndArrayIndex; return { len: len, value: value }; }; exports.decodeReadAccessSpecification = decodeReadAccessSpecification; const decodeCovSubscription = (buffer, offset, apduLen) => { let len = 0; const value = {}; let result; let decodedValue; value.recipient = {}; if (!(0, exports.decodeIsOpeningTagNumber)(buffer, offset + len, 0)) return; len++; if (!(0, exports.decodeIsOpeningTagNumber)(buffer, offset + len, 0)) return; len++; if (!(0, exports.decodeIsOpeningTagNumber)(buffer, offset + len, 1)) return; len++; result = (0, exports.decodeTagNumberAndValue)(buffer, offset + len); le