ember-zli
Version:
Interact with EmberZNet-based adapters using zigbee-herdsman 'ember' driver
177 lines (176 loc) • 9.36 kB
JavaScript
import { EZSP_MAX_FRAME_LENGTH } from "zigbee-herdsman/dist/adapter/ember/ezsp/consts.js";
/**
* @see https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-zep.c
* @see https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-ieee802154.c
* @see https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-zbee-nwk.c
*------------------------------------------------------------
*
* ZEP Packets must be received in the following format:
* |UDP Header| ZEP Header |IEEE 802.15.4 Packet|
* | 8 bytes | 16/32 bytes | <= 127 bytes |
*------------------------------------------------------------
*
* ZEP v1 Header will have the following format:
* |Preamble|Version|Channel ID|Device ID|CRC/LQI Mode|LQI Val|Reserved|Length|
* |2 bytes |1 byte | 1 byte | 2 bytes | 1 byte |1 byte |7 bytes |1 byte|
*
* ZEP v2 Header will have the following format (if type=1/Data):
* |Preamble|Version| Type |Channel ID|Device ID|CRC/LQI Mode|LQI Val|NTP Timestamp|Sequence#|Reserved|Length|
* |2 bytes |1 byte |1 byte| 1 byte | 2 bytes | 1 byte |1 byte | 8 bytes | 4 bytes |10 bytes|1 byte|
*
* ZEP v2 Header will have the following format (if type=2/Ack):
* |Preamble|Version| Type |Sequence#|
* |2 bytes |1 byte |1 byte| 4 bytes |
*------------------------------------------------------------
*/
const ZEP_PREAMBLE = "EX";
const ZEP_PROTOCOL_VERSION = 2;
const ZEP_PROTOCOL_TYPE = 1;
/** Baseline NTP time if bit-0=0 -> 7-Feb-2036 @ 06:28:16 UTC */
const NTP_MSB_0_BASE_TIME = 2085978496000n;
/** Baseline NTP time if bit-0=1 -> 1-Jan-1900 @ 01:00:00 UTC */
const NTP_MSB_1_BASE_TIME = -2208988800000n;
const getZepTimestamp = () => {
const now = BigInt(Date.now());
const useBase1 = now < NTP_MSB_0_BASE_TIME; // time < Feb-2036
// MSB_1_BASE_TIME: dates <= Feb-2036, MSB_0_BASE_TIME: if base0 needed for dates >= Feb-2036
const baseTime = now - (useBase1 ? NTP_MSB_1_BASE_TIME : NTP_MSB_0_BASE_TIME);
let seconds = baseTime / 1000n;
const fraction = ((baseTime % 1000n) * 0x100000000n) / 1000n;
if (useBase1) {
seconds |= 0x80000000n; // set high-order bit if MSB_1_BASE_TIME 1900 used
}
return BigInt.asIntN(64, (seconds << 32n) | fraction);
};
export const createWiresharkZEPFrame = (channelId, deviceId, lqi, rssi, sequence, data, lqiMode = false) => {
const buffer = Buffer.alloc(167);
let offset = 0;
// The IEEE 802.15.4 packet encapsulated in the ZEP frame must have the "TI CC24xx" format
// See figure 21 on page 24 of the CC2420 datasheet: https://www.ti.com/lit/ds/symlink/cc2420.pdf
// So, two bytes must be added at the end:
// * First byte: RSSI value as a signed 8 bits integer (range -128 to 127)
// * Second byte:
// - the most significant bit is set to 1 if the CRC of the frame is correct
// - the 7 least significant bits contain the LQI value as a unsigned 7 bits integer (range 0 to 127)
data[data.length - 2] = rssi;
data[data.length - 1] = 0x80 | ((lqi >> 1) & 0x7f);
// Protocol ID String | Character string | 2.0.3 to 4.2.5
buffer.write(ZEP_PREAMBLE, offset);
offset += 2;
// Protocol Version | Unsigned integer (8 bits) | 1.2.0 to 4.2.5
buffer.writeUInt8(ZEP_PROTOCOL_VERSION, offset++);
// Type | Unsigned integer (8 bits) | 1.2.0 to 1.8.15, 1.12.0 to 4.2.5
buffer.writeUInt8(ZEP_PROTOCOL_TYPE, offset++);
// Channel ID | Unsigned integer (8 bits) | 1.2.0 to 4.2.5
buffer.writeUInt8(channelId, offset++);
// Device ID | Unsigned integer (16 bits) | 1.2.0 to 4.2.5
buffer.writeUint16BE(deviceId, offset);
offset += 2;
// LQI/CRC Mode | Boolean | 1.2.0 to 4.2.5
buffer.writeUInt8(lqiMode ? 1 : 0, offset++);
// Link Quality Indication | Unsigned integer (8 bits) | 1.2.0 to 4.2.5
buffer.writeUInt8(lqi, offset++);
// Timestamp | Date and time | 1.2.0 to 4.2.5
buffer.writeBigInt64BE(getZepTimestamp(), offset);
offset += 8;
// Sequence Number | Unsigned integer (32 bits) | 1.2.0 to 4.2.5
buffer.writeUint32BE(sequence, offset);
offset += 4;
// Reserved Fields | Byte sequence | 2.0.0 to 4.2.5
offset += 10;
// Length | Unsigned integer (8 bits) | 1.2.0 to 4.2.5
buffer.writeUInt8(data.length, offset++);
buffer.set(data, offset);
offset += data.length;
return buffer.subarray(0, offset); // increased to "beyond last" above
};
/**
* @see https://datatracker.ietf.org/doc/id/draft-gharris-opsawg-pcap-00.html
*/
/** seconds + microseconds */
export const PCAP_MAGIC_NUMBER_MS = 0xa1b2c3d4;
/** seconds + nanoseconds */
export const PCAP_MAGIC_NUMBER_NS = 0xa1b23c4d;
const PCAP_VERSION_MAJOR = 2;
const PCAP_VERSION_MINOR = 4;
/** IEEE 802.15.4 Low-Rate Wireless Networks, with each packet having the FCS at the end of the frame. */
const PCAP_LINKTYPE_IEEE802_15_4_WITH_FCS = 195;
export const createPcapFileHeader = (magicNumber = PCAP_MAGIC_NUMBER_MS) => {
const fileHeader = Buffer.alloc(24);
/**
* An unsigned magic number, whose value is either the hexadecimal number 0xA1B2C3D4 or the hexadecimal number 0xA1B23C4D.
* If the value is 0xA1B2C3D4, time stamps in Packet Records (see Figure 2) are in seconds and microseconds;
* if it is 0xA1B23C4D, time stamps in Packet Records are in seconds and nanoseconds.
* These numbers can be used to distinguish sections that have been saved on little-endian machines from the ones saved on big-endian machines,
* and to heuristically identify pcap files.
* 32 bits
* */
fileHeader.writeUInt32LE(magicNumber, 0);
/**
* An unsigned value, giving the number of the current major version of the format.
* The value for the current version of the format is 2.
* This value should change if the format changes in such a way that code that reads the new format could not read the old format
* (i.e., code to read both formats would have to check the version number and use different code paths for the two formats)
* and code that reads the old format could not read the new format.
* 16 bits
*/
fileHeader.writeUInt16LE(PCAP_VERSION_MAJOR, 4);
/**
* An unsigned value, giving the number of the current minor version of the format.
* The value is for the current version of the format is 4.
* This value should change if the format changes in such a way that code that reads the new format could read the old format
* without checking the version number but code that reads the old format could not read all files in the new format.
* 16 bits
*/
fileHeader.writeUInt16LE(PCAP_VERSION_MINOR, 6);
/**
* Not used - SHOULD be filled with 0 by pcap file writers, and MUST be ignored by pcap file readers.
* This value was documented by some older implementations as "gmt to local correction".
* Some older pcap file writers stored non-zero values in this field.
* 32 bits
*/
fileHeader.writeUInt32LE(0, 8);
/**
* Not used - SHOULD be filled with 0 by pcap file writers, and MUST be ignored by pcap file readers.
* This value was documented by some older implementations as "accuracy of timestamps".
* Some older pcap file writers stored non-zero values in this field.
* 32 bits
*/
fileHeader.writeUInt32LE(0, 12);
/**
* An unsigned value indicating the maximum number of octets captured from each packet.
* The portion of each packet that exceeds this value will not be stored in the file.
* This value MUST NOT be zero; if no limit was specified, the value should be a number greater than or equal
* to the largest packet length in the file.
* 32 bits
*/
fileHeader.writeUInt32LE(EZSP_MAX_FRAME_LENGTH, 16);
/**
* An unsigned value that defines, in the lower 28 bits, the link layer type of packets in the file.
* 32 bits
*/
fileHeader.writeUInt32LE(PCAP_LINKTYPE_IEEE802_15_4_WITH_FCS, 20);
return fileHeader;
};
export const createPcapPacketRecordMs = (packetData) => {
const packetHeader = Buffer.alloc(16);
const timestamp = (Date.now() * 1000) / 1000000;
const timestampSec = Math.trunc(timestamp);
/** 32-bit unsigned integer that represents the number of seconds that have elapsed since 1970-01-01 00:00:00 UTC */
packetHeader.writeUInt32LE(timestampSec, 0);
/** Number of microseconds or nanoseconds that have elapsed since that seconds. */
packetHeader.writeUInt32LE(Math.trunc((timestamp - timestampSec) * 1000000.0), 4);
/**
* Unsigned value that indicates the number of octets captured from the packet (i.e. the length of the Packet Data field).
* It will be the minimum value among the Original Packet Length and the snapshot length for the interface (SnapLen, defined in Figure 1).
* 32 bits
*/
packetHeader.writeUInt32LE(packetData.length, 8);
/**
* Unsigned value that indicates the actual length of the packet when it was transmitted on the network.
* It can be different from the Captured Packet Length if the packet has been truncated by the capture process.
* 32 bits
*/
packetHeader.writeUInt32LE(packetData.length, 12);
return Buffer.concat([packetHeader, packetData]);
};