UNPKG

zigbee-herdsman

Version:

An open source ZigBee gateway solution with node.js.

266 lines 10.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.eui64BEBufferToHex = exports.eui64LEBufferToHex = exports.isBroadcastAddress = exports.uint32MaskToChannels = exports.channelsToUInt32Mask = void 0; exports.crc16X25 = crc16X25; exports.crc16XMODEM = crc16XMODEM; exports.crc16CCITT = crc16CCITT; exports.crc16CCITTFALSE = crc16CCITTFALSE; exports.aes128MmoHash = aes128MmoHash; exports.checkInstallCode = checkInstallCode; const node_crypto_1 = require("node:crypto"); const consts_1 = require("./consts"); const enums_1 = require("./enums"); /** * Convert a channels array to a uint32 channel mask. * @param channels * @returns */ const channelsToUInt32Mask = (channels) => { return channels.reduce((a, c) => a + (1 << c), 0); }; exports.channelsToUInt32Mask = channelsToUInt32Mask; /** * Convert a uint32 channel mask to a channels array. * @param mask * @returns */ const uint32MaskToChannels = (mask) => { const channels = []; for (const channel of consts_1.ALL_802_15_4_CHANNELS) { if ((2 ** channel) & mask) { channels.push(channel); } } return channels; }; exports.uint32MaskToChannels = uint32MaskToChannels; const isBroadcastAddress = (address) => { return (address === enums_1.BroadcastAddress.DEFAULT || address === enums_1.BroadcastAddress.RX_ON_WHEN_IDLE || address === enums_1.BroadcastAddress.SLEEPY || address === enums_1.BroadcastAddress.LOW_POWER_ROUTERS); }; exports.isBroadcastAddress = isBroadcastAddress; /** * Represent a little endian buffer in `0x...` form * * NOTE: the buffer is always copied to avoid reversal in reference */ const eui64LEBufferToHex = (eui64LEBuf) => `0x${Buffer.from(eui64LEBuf).reverse().toString("hex")}`; exports.eui64LEBufferToHex = eui64LEBufferToHex; /** * Represent a big endian buffer in `0x...` form */ const eui64BEBufferToHex = (eui64BEBuf) => `0x${eui64BEBuf.toString("hex")}`; exports.eui64BEBufferToHex = eui64BEBufferToHex; /** * Calculate the CRC 8, 16 or 32 for the given data. * * @see https://www.crccalc.com/ * * @param data * @param length CRC Length * @param poly Polynomial * @param crc Initialization value * @param xorOut Final XOR value * @param refIn Reflected In * @param refOut Reflected Out * @returns The calculated CRC * * NOTE: This is not exported for test coverage reasons (large number of combinations possible, many unused). * Specific, needed, algorithms should be defined as exported wrappers below, and coverage added for them. */ function calcCRC(data, length, poly, crc = 0, xorOut = 0, refIn = false, refOut = false) { // https://web.archive.org/web/20150226083354/http://leetcode.com/2011/08/reverse-bits.html const reflect = (x, size) => { if (size === 8) { x = ((x & 0x55) << 1) | ((x & 0xaa) >> 1); x = ((x & 0x33) << 2) | ((x & 0xcc) >> 2); x = ((x & 0x0f) << 4) | ((x & 0xf0) >> 4); } else if (size === 16) { x = ((x & 0x5555) << 1) | ((x & 0xaaaa) >> 1); x = ((x & 0x3333) << 2) | ((x & 0xcccc) >> 2); x = ((x & 0x0f0f) << 4) | ((x & 0xf0f0) >> 4); x = ((x & 0x00ff) << 8) | ((x & 0xff00) >> 8); /* v8 ignore start */ } /* if (size === 32) */ else { x = ((x & 0x55555555) << 1) | ((x & 0xaaaaaaaa) >> 1); x = ((x & 0x33333333) << 2) | ((x & 0xcccccccc) >> 2); x = ((x & 0x0f0f0f0f) << 4) | ((x & 0xf0f0f0f0) >> 4); x = ((x & 0x00ff00ff) << 8) | ((x & 0xff00ff00) >> 8); x = ((x & 0x0000ffff) << 16) | ((x & 0xffff0000) >> 16); } /* v8 ignore stop */ return x; }; poly = (1 << length) | poly; for (let byte of data) { if (refIn) { byte = reflect(byte, 8); } crc ^= byte << (length - 8); for (let i = 0; i < 8; i++) { crc <<= 1; if (crc & (1 << length)) { crc ^= poly; } } } if (refOut) { crc = reflect(crc, length); } return crc ^ xorOut; } /** * CRC-16/X-25 * aka CRC-16/IBM-SDLC * aka CRC-16/ISO-HDLC * aka CRC-16/ISO-IEC-14443-3-B * aka CRC-B * aka X-25 * * Shortcut for `calcCRC(data, 16, 0x1021, 0xFFFF, 0xFFFF, true, true)` * * Used for Install Codes - see Document 13-0402-13 - 10.1 */ function crc16X25(data) { return calcCRC(data, 16, 0x1021, 0xffff, 0xffff, true, true); } /** * CRC-16/XMODEM * aka CRC-16/ACORN * aka CRC-16/LTE * aka CRC-16/V-41-MSB * aka XMODEM * aka ZMODEM * * Shortcut for `calcCRC(data, 16, 0x1021)` * * Used for XMODEM transfers, often involved in ZigBee environments */ function crc16XMODEM(data) { return calcCRC(data, 16, 0x1021); } /** * CRC-16/CCITT * aka CRC-16/KERMIT * aka CRC-16/BLUETOOTH * aka CRC-16/CCITT-TRUE * aka CRC-16/V-41-LSB * aka CRC-CCITT * aka KERMIT * * Shortcut for `calcCRC(data, 16, 0x1021, 0x0000, 0x0000, true, true)` */ function crc16CCITT(data) { return calcCRC(data, 16, 0x1021, 0x0000, 0x0000, true, true); } /** * CRC-16/CCITT-FALSE * aka CRC-16/IBM-3740 * aka CRC-16/AUTOSAR * * Shortcut for `calcCRC(data, 16, 0x1021, 0xffff)` */ function crc16CCITTFALSE(data) { return calcCRC(data, 16, 0x1021, 0xffff); } function aes128MmoHashUpdate(result, data, dataSize) { while (dataSize >= consts_1.AES_MMO_128_BLOCK_SIZE) { const cipher = (0, node_crypto_1.createCipheriv)("aes-128-ecb", result, null); const block = data.subarray(0, consts_1.AES_MMO_128_BLOCK_SIZE); const encryptedBlock = Buffer.concat([cipher.update(block), cipher.final()]); // XOR encrypted and plaintext for (let i = 0; i < consts_1.AES_MMO_128_BLOCK_SIZE; i++) { result[i] = encryptedBlock[i] ^ block[i]; } data = data.subarray(consts_1.AES_MMO_128_BLOCK_SIZE); dataSize -= consts_1.AES_MMO_128_BLOCK_SIZE; } } /** * AES-128-MMO (Matyas-Meyer-Oseas) hashing (using node 'crypto' built-in with 'aes-128-ecb') * * Used for Install Codes - see Document 13-0402-13 - 10.1 */ function aes128MmoHash(data) { const hashResult = Buffer.alloc(consts_1.AES_MMO_128_BLOCK_SIZE); const temp = Buffer.alloc(consts_1.AES_MMO_128_BLOCK_SIZE); let remainingLength = data.length; let position = 0; for (position; remainingLength >= consts_1.AES_MMO_128_BLOCK_SIZE;) { const chunk = data.subarray(position, position + consts_1.AES_MMO_128_BLOCK_SIZE); aes128MmoHashUpdate(hashResult, chunk, chunk.length); position += consts_1.AES_MMO_128_BLOCK_SIZE; remainingLength -= consts_1.AES_MMO_128_BLOCK_SIZE; } for (let i = 0; i < remainingLength; i++) { temp[i] = data[position + i]; } // per the spec, concatenate a 1 bit followed by all zero bits temp[remainingLength] = 0x80; // if appending the bit string will push us beyond the 16-byte boundary, hash that block and append another 16-byte block if (consts_1.AES_MMO_128_BLOCK_SIZE - remainingLength < 3) { aes128MmoHashUpdate(hashResult, temp, consts_1.AES_MMO_128_BLOCK_SIZE); temp.fill(0); } temp[consts_1.AES_MMO_128_BLOCK_SIZE - 2] = (data.length >> 5) & 0xff; temp[consts_1.AES_MMO_128_BLOCK_SIZE - 1] = (data.length << 3) & 0xff; aes128MmoHashUpdate(hashResult, temp, consts_1.AES_MMO_128_BLOCK_SIZE); const result = Buffer.alloc(consts_1.AES_MMO_128_BLOCK_SIZE); for (let i = 0; i < consts_1.AES_MMO_128_BLOCK_SIZE; i++) { result[i] = hashResult[i]; } return result; } /** * Check if install code (little-endian) is valid, and if not, and requested, fix it. * * WARNING: Due to conflicting sizes between 8-length code with invalid CRC, and 10-length code missing CRC, given 8-length codes are always assumed to be 8-length code with invalid CRC (most probable scenario). * * @param code The code to check. Reference is not modified by this procedure but is returned when code was valid, as `outCode`. * @param adjust If false, throws if the install code is invalid, otherwise try to fix it (CRC) * @returns * - The adjusted code, or `code` if not adjusted. * - If adjust is false, undefined, otherwise, the reason why the code needed adjusting or undefined if not. * - Throws when adjust=false and invalid, or cannot fix. */ function checkInstallCode(code, adjust = true) { const crcLowByteIndex = code.length - consts_1.INSTALL_CODE_CRC_SIZE; const crcHighByteIndex = code.length - consts_1.INSTALL_CODE_CRC_SIZE + 1; for (const codeSize of consts_1.INSTALL_CODE_SIZES) { if (code.length === codeSize) { // install code has CRC, check if valid, if not, replace it const crc = crc16X25(code.subarray(0, -2)); const crcHighByte = (crc >> 8) & 0xff; const crcLowByte = crc & 0xff; if (code[crcLowByteIndex] !== crcLowByte || code[crcHighByteIndex] !== crcHighByte) { // see WARNING above, 8 is smallest valid length, so always ends up here if (adjust) { const outCode = Buffer.from(code); outCode[crcLowByteIndex] = crcLowByte; outCode[crcHighByteIndex] = crcHighByte; return [outCode, "invalid CRC"]; } throw new Error(`Install code ${code.toString("hex")} failed CRC validation`); } return [code, undefined]; } if (code.length === codeSize - consts_1.INSTALL_CODE_CRC_SIZE) { if (adjust) { // install code is missing CRC const crc = crc16X25(code); const outCode = Buffer.alloc(code.length + consts_1.INSTALL_CODE_CRC_SIZE); code.copy(outCode, 0); outCode.writeUInt16LE(crc, code.length); return [outCode, "missing CRC"]; } throw new Error(`Install code ${code.toString("hex")} failed CRC validation`); } } // never returned from within the above loop throw new Error(`Install code ${code.toString("hex")} has invalid size`); } //# sourceMappingURL=utils.js.map