Skip to content

Commit

Permalink
Implement TC-SC-3.6 as a Python test
Browse files Browse the repository at this point in the history
- Test TC-SC-3.6 was not feasible with chip-tool and manual
  intervention due to the complexity of validating that the
  correct sessions were established, establishing all of them,
  and ensuring all subscriptions fire as intended.

This PR:
- Adds a version of TC-SC-3.6 in Python
- Updates the Python code with minor improvements needed during
  the development of the test
- **Touches no C++ SDK code**

Testing done:

- Ran the test as it could be done by an end-user, passed on Linux
  and on ESP32 (after some changes to resources in the examples, not
  included in this PR)

To run:

- Build all-clusters app Linux:
  - `scripts/examples/gn_build_example.sh examples/all-clusters-app/linux out/debug/standalone chip_config_network_layer_ble=false`
  - In a shell, run: `clear && rm -f kvs1 && out/debug/standalone/chip-all-clusters-app --discriminator 2118 --KVS kvs1`
- Build the Python environment, activate it, then run the test
  - `./scripts/build_python.sh -m platform -i separate`
  - `. ./out/python_env/bin/activate`
  - Run the test: `rm -f admin_storage.json && python src/python_testing/TC_SC_3_6.py -m on-network -d 2118 -p 20202021`
  • Loading branch information
tcarmelveilleux committed Aug 8, 2022
1 parent 753415d commit 79c05c2
Show file tree
Hide file tree
Showing 8 changed files with 334 additions and 34 deletions.
55 changes: 44 additions & 11 deletions src/controller/python/chip/ChipDeviceCtrl.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@
from .clusters import Objects as GeneratedObjects
from .clusters.CHIPClusters import *
from . import clusters as Clusters
from .FabricAdmin import FabricAdmin
import enum
import threading
import typing
import builtins
import ipdb
import ctypes
import copy

Expand Down Expand Up @@ -133,7 +133,7 @@ class DiscoveryFilterType(enum.IntEnum):
class ChipDeviceController():
activeList = set()

def __init__(self, opCredsContext: ctypes.c_void_p, fabricId: int, nodeId: int, adminVendorId: int, paaTrustStorePath: str = "", useTestCommissioner: bool = False):
def __init__(self, opCredsContext: ctypes.c_void_p, fabricId: int, nodeId: int, adminVendorId: int, paaTrustStorePath: str = "", useTestCommissioner: bool = False, fabricAdmin: FabricAdmin = None, name: str = None):
self.state = DCState.NOT_INITIALIZED
self.devCtrl = None
self._ChipStack = builtins.chipStack
Expand All @@ -150,19 +150,24 @@ def __init__(self, opCredsContext: ctypes.c_void_p, fabricId: int, nodeId: int,
opCredsContext), pointer(devCtrl), fabricId, nodeId, adminVendorId, ctypes.c_char_p(None if len(paaTrustStorePath) == 0 else str.encode(paaTrustStorePath)), useTestCommissioner)
)

self.nodeId = nodeId
self._nodeId = nodeId

if res != 0:
raise self._ChipStack.ErrorToException(res)

self.devCtrl = devCtrl
self._fabricAdmin = fabricAdmin
self._fabricId = fabricId
self._adminIndex = fabricAdmin.adminIndex

if name is None:
self._name = "adminIndex(%x)/fabricId(0x%016X)/nodeId(0x%016X)" % (fabricAdmin.adminIndex, fabricId, nodeId)
else:
self._name = name

self._Cluster = ChipClusters(builtins.chipStack)
self._Cluster.InitLib(self._dmLib)

def GetNodeId(self):
return self.nodeId

def HandleCommissioningComplete(nodeid, err):
if err != 0:
print("Failed to commission: {}".format(err))
Expand All @@ -174,7 +179,7 @@ def HandleCommissioningComplete(nodeid, err):
self._ChipStack.commissioningCompleteEvent.set()
self._ChipStack.completeEvent.set()

def HandleKeyExchangeComplete(err):
def HandlePASEEstablishmentComplete(err):
if err != 0:
print("Failed to establish secure session to device: {}".format(err))
self._ChipStack.callbackRes = self._ChipStack.ErrorToException(
Expand All @@ -183,7 +188,7 @@ def HandleKeyExchangeComplete(err):
print("Established secure session with Device")

if self.state != DCState.COMMISSIONING:
# During Commissioning, HandleKeyExchangeComplete will also be called,
# During Commissioning, HandlePASEEstablishmentComplete will also be called,
# in this case the async operation should be marked as finished by
# HandleCommissioningComplete instead this function.
self.state = DCState.IDLE
Expand All @@ -194,10 +199,10 @@ def HandleKeyExchangeComplete(err):
if err != 0:
HandleCommissioningComplete(0, err)

self.cbHandleKeyExchangeCompleteFunct = _DevicePairingDelegate_OnPairingCompleteFunct(
HandleKeyExchangeComplete)
self.cbHandlePASEEstablishmentCompleteFunct = _DevicePairingDelegate_OnPairingCompleteFunct(
HandlePASEEstablishmentComplete)
self._dmLib.pychip_ScriptDevicePairingDelegate_SetKeyExchangeCallback(
self.devCtrl, self.cbHandleKeyExchangeCompleteFunct)
self.devCtrl, self.cbHandlePASEEstablishmentCompleteFunct)

self.cbHandleCommissioningCompleteFunct = _DevicePairingDelegate_OnCommissioningCompleteFunct(
HandleCommissioningComplete)
Expand All @@ -209,6 +214,33 @@ def HandleKeyExchangeComplete(err):

ChipDeviceController.activeList.add(self)

@property
def fabricAdmin(self) -> FabricAdmin:
return self._fabricAdmin

@property
def nodeId(self) -> int:
return self._nodeId

@property
def fabricId(self) -> int:
return self._fabricId

@property
def adminIndex(self) -> int:
return self._adminIndex

@property
def name(self) -> str:
return self._name

@name.setter
def name(self, new_name: str):
self._name = new_name

def GetNodeId(self) -> int:
return self.nodeId

def Shutdown(self):
''' Shuts down this controller and reclaims any used resources, including the bound
C++ constructor instance in the SDK.
Expand Down Expand Up @@ -423,6 +455,7 @@ def CommissionWithCode(self, setupPayload: str, nodeid: int):
return self._ChipStack.commissioningEventRes == 0

def CommissionIP(self, ipaddr: str, setupPinCode: int, nodeid: int):
""" DEPRECATED, DO NOT USE! Use `CommissionOnNetwork` or `CommissionWithCode` """
self.CheckIsActive()

# IP connection will run through full commissioning, so we need to wait
Expand Down
17 changes: 14 additions & 3 deletions src/controller/python/chip/FabricAdmin.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from typing import *
from ctypes import *
from rich.pretty import pprint
import ipdb
import json
import logging
import builtins
Expand Down Expand Up @@ -102,7 +101,7 @@ def __init__(self, vendorId: int, adminIndex: int = None, fabricId: int = 1):
raise ValueError(
f"Invalid VendorID ({vendorId}) provided!")

self.vendorId = vendorId
self._vendorId = vendorId
self._fabricId = fabricId

if (adminIndex is None):
Expand Down Expand Up @@ -160,7 +159,7 @@ def NewController(self, nodeId: int = None, paaTrustStorePath: str = "", useTest
f"Allocating new controller with FabricId: 0x{self._fabricId:016X}, NodeId: 0x{nodeId:016X}")

controller = ChipDeviceCtrl.ChipDeviceController(
self.closure, self._fabricId, nodeId, self.vendorId, paaTrustStorePath, useTestCommissioner)
self.closure, self._fabricId, nodeId, self.vendorId, paaTrustStorePath, useTestCommissioner, fabricAdmin=self)
return controller

def ShutdownAll():
Expand Down Expand Up @@ -200,3 +199,15 @@ def Shutdown(self, deleteFromStorage: bool = True):

def __del__(self):
self.Shutdown(False)

@property
def vendorId(self) -> int:
return self._vendorId

@property
def fabricId(self) -> int:
return self._fabricId

@property
def adminIndex(self) -> int:
return self._adminIndex
6 changes: 3 additions & 3 deletions src/controller/python/chip/clusters/Attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ def UpdateCachedData(self):


class SubscriptionTransaction:
def __init__(self, transaction: 'AsyncReadTransaction', subscriptionId, devCtrl):
def __init__(self, transaction: AsyncReadTransaction, subscriptionId, devCtrl):
self._onResubscriptionAttemptedCb = DefaultResubscriptionAttemptedCallback
self._onAttributeChangeCb = DefaultAttributeChangeCallback
self._onEventChangeCb = DefaultEventChangeCallback
Expand Down Expand Up @@ -760,9 +760,9 @@ def _handleDone(self):
if not self._future.done():
if self._resultError:
if self._subscription_handler:
self._subscription_handler.OnErrorCb(chipError, self._subscription_handler)
self._subscription_handler.OnErrorCb(self._resultError, self._subscription_handler)
else:
self._future.set_exception(chip.exceptions.ChipStackError(chipError))
self._future.set_exception(chip.exceptions.ChipStackError(self._resultError))
else:
self._future.set_result(AsyncReadTransaction.ReadResponse(
attributes=self._cache.attributeCache, events=self._events))
Expand Down
1 change: 0 additions & 1 deletion src/controller/python/chip/storage/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from typing import *
from ctypes import *
from rich.pretty import pprint
import ipdb
import json
import logging
import base64
Expand Down
26 changes: 12 additions & 14 deletions src/controller/python/chip/utils/CommissioningBuildingBlocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,24 +115,22 @@ async def CreateControllersOnFabric(fabricAdmin: FabricAdmin, adminDevCtrl: Chip


async def AddNOCForNewFabricFromExisting(commissionerDevCtrl, newFabricDevCtrl, existingNodeId, newNodeId):
''' Perform sequence to commission new frabric using existing commissioned fabric.
''' Perform sequence to commission new fabric using existing commissioned fabric.
Args:
commissionerDevCtrl (ChipDeviceController): Already commissioned device controller used
to commission a new fabric on `newFabricDevCtrl`.
newFabricDevCtrl (ChipDeviceController): New device controller which is used for the new
fabric we are establishing.
existingNodeId (int): Node ID of the server we are establishing a CASE session on the
existing fabric that we will used to perform AddNOC.
newNodeId (int): Node ID that we would like to server to used on the new fabric being
added.
existingNodeId (int): Node ID of the target where an AddNOC needs to be done for a new fabric.
newNodeId (int): Node ID to use for the target node on the new fabric.
Return:
bool: True if successful, False otherwise.
'''

resp = await commissionerDevCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(60), timedRequestTimeoutMs=1000)
# TODO: Why the timedREquestTimeoutMs ?
resp = await commissionerDevCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(60), timedRequestTimeoutMs=10000)
if resp.errorCode is not generalCommissioning.Enums.CommissioningError.kOk:
return False

Expand All @@ -141,20 +139,20 @@ async def AddNOCForNewFabricFromExisting(commissionerDevCtrl, newFabricDevCtrl,
chainForAddNOC = newFabricDevCtrl.IssueNOCChain(csrForAddNOC, newNodeId)
if chainForAddNOC.rcacBytes is None or chainForAddNOC.icacBytes is None or chainForAddNOC.nocBytes is None or chainForAddNOC.ipkBytes is None:
# Expiring the failsafe timer in an attempt to clean up.
await commissionerDevCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0), timedRequestTimeoutMs=1000)
await commissionerDevCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0), timedRequestTimeoutMs=10000)
return False

await commissionerDevCtrl.SendCommand(existingNodeId, 0, opCreds.Commands.AddTrustedRootCertificate(chainForAddNOC.rcacBytes))
resp = await commissionerDevCtrl.SendCommand(existingNodeId, 0, opCreds.Commands.AddNOC(chainForAddNOC.nocBytes, chainForAddNOC.icacBytes, chainForAddNOC.ipkBytes, newFabricDevCtrl.GetNodeId(), 0xFFF1))
if resp.statusCode is not opCreds.Enums.OperationalCertStatus.kSuccess:
# Expiring the failsafe timer in an attempt to clean up.
await commissionerDevCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0), timedRequestTimeoutMs=1000)
await commissionerDevCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0), timedRequestTimeoutMs=10000)
return False

resp = await newFabricDevCtrl.SendCommand(newNodeId, 0, generalCommissioning.Commands.CommissioningComplete())
if resp.errorCode is not generalCommissioning.Enums.CommissioningError.kOk:
# Expiring the failsafe timer in an attempt to clean up.
await commissionerDevCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0), timedRequestTimeoutMs=1000)
await commissionerDevCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0), timedRequestTimeoutMs=10000)
return False

if not await _IsNodeInFabricList(newFabricDevCtrl, newNodeId):
Expand All @@ -179,20 +177,20 @@ async def UpdateNOC(devCtrl, existingNodeId, newNodeId):
bool: True if successful, False otherwise.
"""
resp = await devCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(600), timedRequestTimeoutMs=1000)
resp = await devCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(600), timedRequestTimeoutMs=10000)
if resp.errorCode is not generalCommissioning.Enums.CommissioningError.kOk:
return False
csrForUpdateNOC = await devCtrl.SendCommand(
existingNodeId, 0, opCreds.Commands.CSRRequest(CSRNonce=os.urandom(32), isForUpdateNOC=True))
chainForUpdateNOC = devCtrl.IssueNOCChain(csrForUpdateNOC, newNodeId)
if chainForUpdateNOC.rcacBytes is None or chainForUpdateNOC.icacBytes is None or chainForUpdateNOC.nocBytes is None or chainForUpdateNOC.ipkBytes is None:
await devCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0), timedRequestTimeoutMs=1000)
await devCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0), timedRequestTimeoutMs=10000)
return False

resp = await devCtrl.SendCommand(existingNodeId, 0, opCreds.Commands.UpdateNOC(chainForUpdateNOC.nocBytes, chainForUpdateNOC.icacBytes))
if resp.statusCode is not opCreds.Enums.OperationalCertStatus.kSuccess:
# Expiring the failsafe timer in an attempt to clean up.
await devCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0), timedRequestTimeoutMs=1000)
await devCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0), timedRequestTimeoutMs=10000)
return False

# Forget our session since the peer deleted it
Expand All @@ -201,7 +199,7 @@ async def UpdateNOC(devCtrl, existingNodeId, newNodeId):
resp = await devCtrl.SendCommand(newNodeId, 0, generalCommissioning.Commands.CommissioningComplete())
if resp.errorCode is not generalCommissioning.Enums.CommissioningError.kOk:
# Expiring the failsafe timer in an attempt to clean up.
await devCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0), timedRequestTimeoutMs=1000)
await devCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0), timedRequestTimeoutMs=10000)
return False

if not await _IsNodeInFabricList(devCtrl, newNodeId):
Expand Down
Loading

0 comments on commit 79c05c2

Please sign in to comment.