UNPKG

inventoresed

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

365 lines (336 loc) 10.3 kB
import { BasicCCReport, BasicCCValues, CommandClass, InvalidCC, Security2CC, Security2CCMessageEncapsulation, Security2CCNonceGet, Security2CCNonceReport, SupervisionCCGet, SupervisionCCReport, } from "@zwave-js/cc"; import { SecurityClass, SecurityManager2, SupervisionStatus, ZWaveErrorCodes, } from "@zwave-js/core"; import { createMockZWaveRequestFrame, MockZWaveFrameType, type MockNodeBehavior, } from "@zwave-js/testing"; import { wait } from "alcalzone-shared/async"; import path from "path"; import { integrationTest } from "../integrationTestSuite"; integrationTest( "S2 Collisions: Both nodes send at the same time, with supervision", { // debug: true, // We need the cache to skip the CC interviews and mark S2 as supported provisioningDirectory: path.join( __dirname, "fixtures/s2CollisionsSupervised", ), customSetup: async (driver, controller, mockNode) => { // Create a security manager for the node const smNode = new SecurityManager2(); // Copy keys from the driver smNode.setKey( SecurityClass.S2_AccessControl, driver.options.securityKeys!.S2_AccessControl!, ); smNode.setKey( SecurityClass.S2_Authenticated, driver.options.securityKeys!.S2_Authenticated!, ); smNode.setKey( SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); mockNode.host.securityManager2 = smNode; mockNode.host.getHighestSecurityClass = () => SecurityClass.S2_Unauthenticated; // Create a security manager for the controller const smCtrlr = new SecurityManager2(); // Copy keys from the driver smCtrlr.setKey( SecurityClass.S2_AccessControl, driver.options.securityKeys!.S2_AccessControl!, ); smCtrlr.setKey( SecurityClass.S2_Authenticated, driver.options.securityKeys!.S2_Authenticated!, ); smCtrlr.setKey( SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); controller.host.securityManager2 = smCtrlr; controller.host.getHighestSecurityClass = () => SecurityClass.S2_Unauthenticated; // Respond to Nonce Get const respondToNonceGet: MockNodeBehavior = { async onControllerFrame(controller, self, frame) { if ( frame.type === MockZWaveFrameType.Request && frame.payload instanceof Security2CCNonceGet ) { const nonce = smNode.generateNonce( controller.host.ownNodeId, ); const cc = new Security2CCNonceReport(self.host, { nodeId: controller.host.ownNodeId, SOS: true, MOS: false, receiverEI: nonce, }); await self.sendToController( createMockZWaveRequestFrame(cc, { ackRequested: false, }), ); return true; } return false; }, }; mockNode.defineBehavior(respondToNonceGet); // Handle decode errors const handleInvalidCC: MockNodeBehavior = { async onControllerFrame(controller, self, frame) { if ( frame.type === MockZWaveFrameType.Request && frame.payload instanceof InvalidCC ) { if ( frame.payload.reason === ZWaveErrorCodes.Security2CC_CannotDecode || frame.payload.reason === ZWaveErrorCodes.Security2CC_NoSPAN ) { const nonce = smNode.generateNonce( controller.host.ownNodeId, ); const cc = new Security2CCNonceReport(self.host, { nodeId: controller.host.ownNodeId, SOS: true, MOS: false, receiverEI: nonce, }); await self.sendToController( createMockZWaveRequestFrame(cc, { ackRequested: false, }), ); return true; } } return false; }, }; mockNode.defineBehavior(handleInvalidCC); // Just have the node respond to all Supervision Get positively const respondToSupervisionGet: MockNodeBehavior = { async onControllerFrame(controller, self, frame) { if ( frame.type === MockZWaveFrameType.Request && frame.payload instanceof Security2CCMessageEncapsulation && frame.payload.encapsulated instanceof SupervisionCCGet ) { let cc: CommandClass = new SupervisionCCReport( self.host, { nodeId: controller.host.ownNodeId, sessionId: frame.payload.encapsulated.sessionId, moreUpdatesFollow: false, status: SupervisionStatus.Success, }, ); cc = Security2CC.encapsulate(self.host, cc); await self.sendToController( createMockZWaveRequestFrame(cc, { ackRequested: false, }), ); return true; } return false; }, }; mockNode.defineBehavior(respondToSupervisionGet); }, testBody: async (driver, node, mockController, mockNode) => { // Send a secure Basic SET to sync the SPAN await node.commandClasses.Basic.set(1); driver.driverLog.print("----------"); driver.driverLog.print("START TEST"); driver.driverLog.print("----------"); // We use supervision here, so we can ensure that the SET requests were completed // Now create a collision by having both parties send at the same time const nodeToHost = Security2CC.encapsulate( mockNode.host, new BasicCCReport(mockNode.host, { nodeId: mockController.host.ownNodeId, currentValue: 99, }), ); const p1 = mockNode.sendToController( createMockZWaveRequestFrame(nodeToHost, { ackRequested: true, }), ); const p2 = node.commandClasses.Basic.set(0); const [, p2result] = await Promise.all([p1, p2]); // Give the node a chance to respond await wait(250); // If the collision was handled gracefully, we should now have the value reported by the node const currentValue = node.getValue(BasicCCValues.currentValue.id); expect(currentValue).toBe(99); // Ensure the Basic Set causing a collision eventually gets resolved expect(p2result).toMatchObject({ status: SupervisionStatus.Success, }); }, }, ); integrationTest( "S2 Collisions: Both nodes send at the same time, unsupervised", { // debug: true, // We need the cache to skip the CC interviews and mark S2 as supported provisioningDirectory: path.join( __dirname, "fixtures/s2CollisionsUnsupervised", ), customSetup: async (driver, controller, mockNode) => { // Create a security manager for the node const smNode = new SecurityManager2(); // Copy keys from the driver smNode.setKey( SecurityClass.S2_AccessControl, driver.options.securityKeys!.S2_AccessControl!, ); smNode.setKey( SecurityClass.S2_Authenticated, driver.options.securityKeys!.S2_Authenticated!, ); smNode.setKey( SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); mockNode.host.securityManager2 = smNode; mockNode.host.getHighestSecurityClass = () => SecurityClass.S2_Unauthenticated; // Create a security manager for the controller const smCtrlr = new SecurityManager2(); // Copy keys from the driver smCtrlr.setKey( SecurityClass.S2_AccessControl, driver.options.securityKeys!.S2_AccessControl!, ); smCtrlr.setKey( SecurityClass.S2_Authenticated, driver.options.securityKeys!.S2_Authenticated!, ); smCtrlr.setKey( SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); controller.host.securityManager2 = smCtrlr; controller.host.getHighestSecurityClass = () => SecurityClass.S2_Unauthenticated; // Respond to Nonce Get const respondToNonceGet: MockNodeBehavior = { async onControllerFrame(controller, self, frame) { if ( frame.type === MockZWaveFrameType.Request && frame.payload instanceof Security2CCNonceGet ) { const nonce = smNode.generateNonce( controller.host.ownNodeId, ); const cc = new Security2CCNonceReport(self.host, { nodeId: controller.host.ownNodeId, SOS: true, MOS: false, receiverEI: nonce, }); await self.sendToController( createMockZWaveRequestFrame(cc, { ackRequested: false, }), ); return true; } return false; }, }; mockNode.defineBehavior(respondToNonceGet); // Handle decode errors const handleInvalidCC: MockNodeBehavior = { async onControllerFrame(controller, self, frame) { if ( frame.type === MockZWaveFrameType.Request && frame.payload instanceof InvalidCC ) { if ( frame.payload.reason === ZWaveErrorCodes.Security2CC_CannotDecode || frame.payload.reason === ZWaveErrorCodes.Security2CC_NoSPAN ) { const nonce = smNode.generateNonce( controller.host.ownNodeId, ); const cc = new Security2CCNonceReport(self.host, { nodeId: controller.host.ownNodeId, SOS: true, MOS: false, receiverEI: nonce, }); await self.sendToController( createMockZWaveRequestFrame(cc, { ackRequested: false, }), ); return true; } } return false; }, }; mockNode.defineBehavior(handleInvalidCC); }, testBody: async (driver, node, mockController, mockNode) => { // Send a secure Basic SET to sync the SPAN await node.commandClasses.Basic.set(1); driver.driverLog.print("----------"); driver.driverLog.print("START TEST"); driver.driverLog.print("----------"); // We use supervision here, so we can ensure that the SET requests were completed // Now create a collision by having both parties send at the same time const nodeToHost = Security2CC.encapsulate( mockNode.host, new BasicCCReport(mockNode.host, { nodeId: mockController.host.ownNodeId, currentValue: 99, }), ); const p1 = mockNode.sendToController( createMockZWaveRequestFrame(nodeToHost, { ackRequested: true, }), ); const p2 = node.commandClasses.Basic.set(0); await Promise.all([p1, p2]); // Give the node a chance to respond await wait(250); // If the collision was handled gracefully, we should now have the value reported by the node const currentValue = node.getValue(BasicCCValues.currentValue.id); expect(currentValue).toBe(99); }, }, );