Skip to content

Commit d3f26aa

Browse files
authored
fix: Emit full GP data frames (#13)
1 parent b47428f commit d3f26aa

9 files changed

+82
-79
lines changed

README.md

-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ Some quick guidelines to keep the codebase maintainable:
3636
- [ ] lacking reference sniffs for multicast (group)
3737
- [x] Encoding/decoding of ZigBee NWK GP frames
3838
- [ ] lacking reference sniffs, needs full re-check
39-
- [ ] FULLENCR & auth tag checking codepaths
4039
- [x] Encoding/decoding of ZigBee NWK APS frames
4140
- [x] Network forming
4241
- [x] Network state saving (de facto backups)

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "zigbee-on-host",
3-
"version": "0.1.0",
3+
"version": "0.1.1",
44
"description": "ZigBee stack designed to run on a host and communicate with a radio co-processor (RCP)",
55
"engines": {
66
"node": ">=20.17.0"

src/dev/minimal-adapter.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { type Socket as DgramSocket, createSocket } from "node:dgram";
22
import { Socket } from "node:net";
33
import { SerialPort } from "serialport";
4-
import type { StreamRawConfig } from "src/spinel/spinel.js";
5-
import type { ZigbeeAPSHeader, ZigbeeAPSPayload } from "src/zigbee/zigbee-aps.js";
64
import { type NetworkParameters, OTRCPDriver } from "../drivers/ot-rcp-driver.js";
5+
import type { StreamRawConfig } from "../spinel/spinel.js";
76
import { logger } from "../utils/logger.js";
7+
import type { ZigbeeAPSHeader, ZigbeeAPSPayload } from "../zigbee/zigbee-aps.js";
88
import { DEFAULT_WIRESHARK_IP, DEFAULT_ZEP_UDP_PORT, createWiresharkZEPFrame } from "./wireshark.js";
99

1010
const NS = "minimal-adapter";

src/drivers/ot-rcp-driver.ts

+10-41
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ interface AdapterDriverEventMap {
9999
macFrame: [payload: Buffer, rssi?: number];
100100
fatalError: [message: string];
101101
frame: [sender16: number | undefined, sender64: bigint | undefined, apsHeader: ZigbeeAPSHeader, apsPayload: ZigbeeAPSPayload, rssi: number];
102+
gpFrame: [cmdId: number, payload: Buffer, macHeader: MACHeader, nwkHeader: ZigbeeNWKGPHeader, rssi: number];
102103
deviceJoined: [source16: number, source64: bigint];
103104
deviceRejoined: [source16: number, source64: bigint];
104105
deviceLeft: [source16: number, source64: bigint];
@@ -1107,7 +1108,7 @@ export class OTRCPDriver extends EventEmitter<AdapterDriverEventMap> {
11071108
nwkGPHeader,
11081109
);
11091110

1110-
this.processZigbeeNWKGPCommandFrame(nwkGPPayload, macHeader, nwkGPHeader, metadata?.rssi ?? 0);
1111+
this.processZigbeeNWKGPDataFrame(nwkGPPayload, macHeader, nwkGPHeader, metadata?.rssi ?? 0);
11111112
} else {
11121113
logger.debug(`<-~- NWKGP Ignoring frame with type ${nwkGPHeader.frameControl.frameType}`, NS);
11131114
return;
@@ -2514,52 +2515,20 @@ export class OTRCPDriver extends EventEmitter<AdapterDriverEventMap> {
25142515

25152516
// #region Zigbee NWK GP layer
25162517

2517-
public processZigbeeNWKGPCommandFrame(data: Buffer, macHeader: MACHeader, nwkHeader: ZigbeeNWKGPHeader, rssi: number): void {
2518-
if (nwkHeader.sourceId === undefined) {
2519-
return;
2520-
}
2521-
2518+
public processZigbeeNWKGPDataFrame(data: Buffer, macHeader: MACHeader, nwkHeader: ZigbeeNWKGPHeader, rssi: number): void {
25222519
let offset = 0;
25232520
const cmdId = data.readUInt8(offset);
25242521
offset += 1;
2522+
const framePayload = data.subarray(offset);
25252523

2526-
logger.debug(() => `<=== NWKGP CMD[cmdId=${cmdId} macSource=${macHeader.source16}:${macHeader.source64} sourceId=${nwkHeader.sourceId}]`, NS);
2527-
2528-
const apsHeader: ZigbeeAPSHeader = {
2529-
frameControl: {
2530-
frameType: ZigbeeAPSFrameType.CMD,
2531-
deliveryMode: macHeader.source16! < ZigbeeConsts.BCAST_MIN ? ZigbeeAPSDeliveryMode.UNICAST : ZigbeeAPSDeliveryMode.BCAST, // TODO: correct??
2532-
ackFormat: false,
2533-
security: false,
2534-
ackRequest: false,
2535-
extendedHeader: false,
2536-
},
2537-
profileId: ZigbeeConsts.GP_PROFILE_ID,
2538-
clusterId: ZigbeeConsts.GP_CLUSTER_ID,
2539-
sourceEndpoint: ZigbeeConsts.GP_ENDPOINT,
2540-
destEndpoint: ZigbeeConsts.GP_ENDPOINT,
2541-
group: ZigbeeConsts.GP_GROUP_ID,
2542-
};
2543-
// transform into a ZCL frame
2544-
// TODO: correct??
2545-
const gpdHeader = Buffer.alloc(15);
2546-
gpdHeader.writeUInt8(0b00000001, 0); // frameControl: FrameType.SPECIFIC + Direction.CLIENT_TO_SERVER + disableDefaultResponse=false
2547-
gpdHeader.writeUInt8(macHeader.sequenceNumber ?? 0, 1);
2548-
gpdHeader.writeUInt8(cmdId === 0xe0 ? ZigbeeConsts.GP_COMMISSIONING_NOTIFICATION : ZigbeeConsts.GP_NOTIFICATION, 2);
2549-
gpdHeader.writeUInt16LE(0, 3); // options, only srcID present
2550-
gpdHeader.writeUInt32LE(nwkHeader.sourceId, 5);
2551-
// omitted: gpdIEEEAddr (ieeeAddr)
2552-
// omitted: gpdEndpoint (uint8)
2553-
gpdHeader.writeUInt32LE(nwkHeader.securityFrameCounter ?? 0, 9);
2554-
gpdHeader.writeUInt8(cmdId, 13);
2555-
gpdHeader.writeUInt8(data.byteLength - offset, 14);
2556-
2557-
const payload = Buffer.alloc(gpdHeader.byteLength + data.byteLength - offset);
2558-
payload.set(gpdHeader, 0);
2559-
payload.set(data.subarray(offset), gpdHeader.byteLength);
2524+
logger.debug(
2525+
() =>
2526+
`<=== NWKGP[cmdId=${cmdId} destPANId=${macHeader.destinationPANId} dest64=${macHeader.destination64} sourceId=${nwkHeader.sourceId}]`,
2527+
NS,
2528+
);
25602529

25612530
setImmediate(() => {
2562-
this.emit("frame", nwkHeader.sourceId! & 0xffff, undefined, apsHeader, payload, rssi);
2531+
this.emit("gpFrame", cmdId, framePayload, macHeader, nwkHeader, rssi);
25632532
});
25642533
}
25652534

src/zigbee/zigbee-aps.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ export const enum ZigbeeAPSFrameType {
5252

5353
export const enum ZigbeeAPSDeliveryMode {
5454
UNICAST = 0x00,
55-
/** removed in Zigbee 2006 and later */
56-
// INDIRECT = 0x01,
55+
// INDIRECT = 0x01, /** removed in Zigbee 2006 and later */
5756
BCAST = 0x02,
5857
/** ZigBee 2006 and later */
5958
GROUP = 0x03,

src/zigbee/zigbee-nwkgp.ts

+5
Original file line numberDiff line numberDiff line change
@@ -122,17 +122,22 @@ export const enum ZigbeeNWKGPCommandId {
122122
SHORT_PRESS11 = 0x66,
123123
SHORT_PRESS12 = 0x67,
124124
SHORT_PRESS22 = 0x68,
125+
PRESS_8BIT_VECTOR = 0x69,
126+
RELEASE_8BIT_VECTOR = 0x6a,
125127
ATTRIBUTE_REPORTING = 0xa0,
126128
MANUFACTURE_SPECIFIC_ATTR_REPORTING = 0xa1,
127129
MULTI_CLUSTER_REPORTING = 0xa2,
128130
MANUFACTURER_SPECIFIC_MCLUSTER_REPORTING = 0xa3,
129131
REQUEST_ATTRIBUTES = 0xa4,
130132
READ_ATTRIBUTES_RESPONSE = 0xa5,
133+
ZCL_TUNNELING = 0xa6,
134+
COMPACT_ATTRIBUTE_REPORTING = 0xa8,
131135
ANY_SENSOR_COMMAND_A0_A3 = 0xaf,
132136
COMMISSIONING = 0xe0,
133137
DECOMMISSIONING = 0xe1,
134138
SUCCESS = 0xe2,
135139
CHANNEL_REQUEST = 0xe3,
140+
APPLICATION_DESCRIPTION = 0xe4,
136141
COMMISSIONING_REPLY = 0xf0,
137142
WRITE_ATTRIBUTES = 0xf1,
138143
READ_ATTRIBUTES = 0xf2,

src/zigbee/zigbee.ts

-2
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ export const enum ZigbeeConsts {
4141
GP_PROFILE_ID = 0xa1e0,
4242
GP_GROUP_ID = 0x0b84,
4343
GP_CLUSTER_ID = 0x0021,
44-
GP_COMMISSIONING_NOTIFICATION = 4,
45-
GP_NOTIFICATION = 0,
4644

4745
//---- Touchlink
4846
TOUCHLINK_PROFILE_ID = 0xc05e,

test/ot-rcp-driver.test.ts

+58-25
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import { SpinelCommandId } from "../src/spinel/commands";
77
import { SpinelPropertyId } from "../src/spinel/properties";
88
import { SPINEL_HEADER_FLG_SPINEL, encodeSpinelFrame } from "../src/spinel/spinel";
99
import { SpinelStatus } from "../src/spinel/statuses";
10-
import { MACAssociationStatus, decodeMACFrameControl, decodeMACHeader } from "../src/zigbee/mac";
10+
import { MACAssociationStatus, type MACHeader, decodeMACFrameControl, decodeMACHeader } from "../src/zigbee/mac";
1111
import { ZigbeeConsts } from "../src/zigbee/zigbee";
1212
import { type ZigbeeNWKLinkStatus, ZigbeeNWKManyToOne, ZigbeeNWKStatus } from "../src/zigbee/zigbee-nwk";
13+
import type { ZigbeeNWKGPHeader } from "../src/zigbee/zigbee-nwkgp";
1314
import {
1415
A_CHANNEL,
1516
A_EUI64,
@@ -546,40 +547,72 @@ describe("OT RCP Driver", () => {
546547
const onStreamRawFrameSpy = vi.spyOn(driver, "onStreamRawFrame");
547548
const onZigbeeAPSACKRequestSpy = vi.spyOn(driver, "onZigbeeAPSACKRequest");
548549
const onZigbeeAPSFrameSpy = vi.spyOn(driver, "onZigbeeAPSFrame");
549-
const processZigbeeNWKGPCommandFrameSpy = vi.spyOn(driver, "processZigbeeNWKGPCommandFrame");
550+
const processZigbeeNWKGPDataFrameSpy = vi.spyOn(driver, "processZigbeeNWKGPDataFrame");
550551

551552
driver.parser._transform(makeSpinelStreamRaw(1, NETDEF_ZGP_COMMISSIONING), "utf8", () => {});
552553
await vi.runOnlyPendingTimersAsync();
553554

555+
const expectedMACHeader: MACHeader = {
556+
frameControl: {
557+
frameType: 0x1,
558+
securityEnabled: false,
559+
framePending: false,
560+
ackRequest: false,
561+
panIdCompression: false,
562+
seqNumSuppress: false,
563+
iePresent: false,
564+
destAddrMode: 0x2,
565+
frameVersion: 0,
566+
sourceAddrMode: 0x0,
567+
},
568+
sequenceNumber: 70,
569+
destinationPANId: 0xffff,
570+
destination16: 0xffff,
571+
destination64: undefined,
572+
sourcePANId: 0xffff,
573+
source16: undefined,
574+
source64: undefined,
575+
auxSecHeader: undefined,
576+
superframeSpec: undefined,
577+
gtsInfo: undefined,
578+
pendAddr: undefined,
579+
commandId: undefined,
580+
headerIE: undefined,
581+
frameCounter: undefined,
582+
keySeqCounter: undefined,
583+
fcs: 0xffff,
584+
};
585+
const expectedNWKGPHeader: ZigbeeNWKGPHeader = {
586+
frameControl: {
587+
frameType: 0x0,
588+
protocolVersion: 3,
589+
autoCommissioning: false,
590+
nwkFrameControlExtension: false,
591+
},
592+
frameControlExt: undefined,
593+
sourceId: 0x0155f47a,
594+
endpoint: undefined,
595+
securityFrameCounter: undefined,
596+
micSize: 0,
597+
payloadLength: 52,
598+
mic: undefined,
599+
};
600+
554601
expect(onStreamRawFrameSpy).toHaveBeenCalledTimes(1);
555602
expect(onZigbeeAPSACKRequestSpy).toHaveBeenCalledTimes(0);
556603
expect(onZigbeeAPSFrameSpy).toHaveBeenCalledTimes(0);
557-
expect(processZigbeeNWKGPCommandFrameSpy).toHaveBeenCalledTimes(1);
604+
expect(processZigbeeNWKGPDataFrameSpy).toHaveBeenCalledTimes(1);
558605
expect(emitSpy).toHaveBeenCalledWith(
559-
"frame",
560-
0x0155f47a & 0xffff,
561-
undefined,
562-
{
563-
frameControl: {
564-
frameType: 0x1,
565-
deliveryMode: 0x2,
566-
ackFormat: false,
567-
security: false,
568-
ackRequest: false,
569-
extendedHeader: false,
570-
},
571-
group: ZigbeeConsts.GP_GROUP_ID,
572-
profileId: ZigbeeConsts.GP_PROFILE_ID,
573-
clusterId: ZigbeeConsts.GP_CLUSTER_ID,
574-
destEndpoint: ZigbeeConsts.GP_ENDPOINT,
575-
sourceEndpoint: ZigbeeConsts.GP_ENDPOINT,
576-
},
606+
"gpFrame",
607+
0xe0,
577608
Buffer.from([
578-
1, 70, 4, 0, 0, 122, 244, 85, 1, 0, 0, 0, 0, 0xe0, 51, 0x2, 0x85, 0xf2, 0xc9, 0x25, 0x82, 0x1d, 0xf4, 0x6f, 0x45, 0x8c, 0xf0,
579-
0xe6, 0x37, 0xaa, 0xc3, 0xba, 0xb6, 0xaa, 0x45, 0x83, 0x1a, 0x11, 0x46, 0x23, 0x0, 0x0, 0x4, 0x16, 0x10, 0x11, 0x22, 0x23, 0x18,
580-
0x19, 0x14, 0x15, 0x12, 0x13, 0x64, 0x65, 0x62, 0x63, 0x1e, 0x1f, 0x1c, 0x1d, 0x1a, 0x1b, 0x16, 0x17,
609+
0x2, 0x85, 0xf2, 0xc9, 0x25, 0x82, 0x1d, 0xf4, 0x6f, 0x45, 0x8c, 0xf0, 0xe6, 0x37, 0xaa, 0xc3, 0xba, 0xb6, 0xaa, 0x45, 0x83, 0x1a,
610+
0x11, 0x46, 0x23, 0x0, 0x0, 0x4, 0x16, 0x10, 0x11, 0x22, 0x23, 0x18, 0x19, 0x14, 0x15, 0x12, 0x13, 0x64, 0x65, 0x62, 0x63, 0x1e,
611+
0x1f, 0x1c, 0x1d, 0x1a, 0x1b, 0x16, 0x17,
581612
]),
582-
0, // rssi
613+
expectedMACHeader,
614+
expectedNWKGPHeader,
615+
0,
583616
);
584617
});
585618
});

test/zigbee.bench.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1+
import { bench, describe } from "vitest";
12
import {
23
decodeMACFrameControl,
34
decodeMACHeader,
45
decodeMACPayload,
56
decodeMACZigbeeBeacon,
67
encodeMACFrame,
78
encodeMACFrameZigbee,
8-
} from "src/zigbee/mac";
9-
import { decodeZigbeeAPSFrameControl, decodeZigbeeAPSHeader, decodeZigbeeAPSPayload, encodeZigbeeAPSFrame } from "src/zigbee/zigbee-aps";
10-
import { decodeZigbeeNWKFrameControl, decodeZigbeeNWKHeader, decodeZigbeeNWKPayload, encodeZigbeeNWKFrame } from "src/zigbee/zigbee-nwk";
11-
import { decodeZigbeeNWKGPFrameControl, decodeZigbeeNWKGPHeader, decodeZigbeeNWKGPPayload, encodeZigbeeNWKGPFrame } from "src/zigbee/zigbee-nwkgp";
12-
import { bench, describe } from "vitest";
9+
} from "../src/zigbee/mac";
1310
import { ZigbeeKeyType, makeKeyedHashByType, registerDefaultHashedKeys } from "../src/zigbee/zigbee";
11+
import { decodeZigbeeAPSFrameControl, decodeZigbeeAPSHeader, decodeZigbeeAPSPayload, encodeZigbeeAPSFrame } from "../src/zigbee/zigbee-aps";
12+
import { decodeZigbeeNWKFrameControl, decodeZigbeeNWKHeader, decodeZigbeeNWKPayload, encodeZigbeeNWKFrame } from "../src/zigbee/zigbee-nwk";
13+
import { decodeZigbeeNWKGPFrameControl, decodeZigbeeNWKGPHeader, decodeZigbeeNWKGPPayload, encodeZigbeeNWKGPFrame } from "../src/zigbee/zigbee-nwkgp";
1414
import {
1515
NET2_ASSOC_REQ_FROM_DEVICE,
1616
NET2_ASSOC_RESP_FROM_COORD,

0 commit comments

Comments
 (0)