Skip to content

Commit

Permalink
Commissioning works from chip-tool!
Browse files Browse the repository at this point in the history
  • Loading branch information
tannewt committed Oct 2, 2024
1 parent e913c36 commit 7b05d28
Show file tree
Hide file tree
Showing 18 changed files with 277 additions and 99 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ This will start up MDNS via avahi for discovery by the commissioner and then rep

## Running a Matter commissioner

### chip-tool

The de facto standard implementation of Matter is open source as well. It is written in C++ and has many dependencies. It implements all of the different facets of the specification.

We use this implementation via [ESP Matter](https://github.com/espressif/esp-matter) (tested on commit 9350d9d5f948d3b7c61c8659c4d6990d0ff00ea4) to run an introspectable (aka debug printable) commissioner.
Expand Down Expand Up @@ -69,7 +71,11 @@ This will look up commissionable devices on the network via MDNS and then start

Logs can be added into the chip sources to understand what is happening on the commissioner side. To rebuild, I've had to run `bash install.sh` again.

###Generate a certificate declaration
### Apple Home

The Apple Home app can also discover and (attempt to) commission the device. Tap Add Accessory and the CircuitMatter device will show up as a nearby Matter Accessory. Tap it and then enter the setup code `67202583`. This will start the commissioning process from Apple Home.

## Generate a certificate declaration

Each Matter device has its own certificate to capture whether a device has been certified. (CircuitMatter declares itself as an uncertified test/development/hobby device.) This is referred to the Certificate Declaration (CD). CircuitMatter can generate a CD for you and the remaining certificates are loaded from the projectchip repositorry. The certificate generated by CircuitMatter is signed by a test private key.

Expand Down
27 changes: 25 additions & 2 deletions circuitmatter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ def __init__(
self.socket.setblocking(False)

self._endpoints = {}
self._next_endpoint = 0
self._descriptor = data_model.DescriptorCluster()
self._descriptor.PartsList = []
self.add_cluster(0, self._descriptor)
basic_info = data_model.BasicInformationCluster()
basic_info.vendor_id = vendor_id
basic_info.product_id = product_id
Expand Down Expand Up @@ -106,8 +110,15 @@ def start_commissioning(self):
def add_cluster(self, endpoint, cluster):
if endpoint not in self._endpoints:
self._endpoints[endpoint] = {}
self._descriptor.PartsList.append(endpoint)
self._next_endpoint = max(self._next_endpoint, endpoint + 1)
self._endpoints[endpoint][cluster.CLUSTER_ID] = cluster

def add_device(self, device):
self._endpoints[self._next_endpoint] = {}
self._descriptor.PartsList.append(self._next_endpoint)
self._next_endpoint += 1

def process_packets(self):
while True:
try:
Expand Down Expand Up @@ -148,6 +159,7 @@ def invoke(self, session, cluster, path, fields, command_ref):
status = interaction_model.StatusIB()
if cdata is None:
status.Status = interaction_model.StatusCode.UNSUPPORTED_COMMAND
print("UNSUPPORTED_COMMAND")
else:
status.Status = cdata
cstatus.Status = status
Expand Down Expand Up @@ -303,7 +315,7 @@ def process_packet(self, address, data):
sigma1, _ = case.Sigma1.decode(
message.application_payload[0], message.application_payload[1:]
)
response = self.manager.reply_to_sigma1(sigma1)
response = self.manager.reply_to_sigma1(exchange, sigma1)

opcode = SecureProtocolOpcode.STATUS_REPORT
if isinstance(response, case.Sigma2Resume):
Expand All @@ -322,7 +334,18 @@ def process_packet(self, address, data):
sigma3, _ = case.Sigma3.decode(
message.application_payload[0], message.application_payload[1:]
)
print(sigma3)
protocol_code = self.manager.reply_to_sigma3(exchange, sigma3)

error_status = session.StatusReport()
general_code = session.GeneralCode.FAILURE
if (
protocol_code
== session.SecureChannelProtocolCode.SESSION_ESTABLISHMENT_SUCCESS
):
general_code = session.GeneralCode.SUCCESS
error_status.general_code = general_code
error_status.protocol_id = ProtocolId.SECURE_CHANNEL
error_status.protocol_code = protocol_code
exchange.send(
ProtocolId.SECURE_CHANNEL,
SecureProtocolOpcode.STATUS_REPORT,
Expand Down
8 changes: 8 additions & 0 deletions circuitmatter/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

import circuitmatter as cm

from circuitmatter.device_types.lighting import extended_color


class ReplaySocket:
def __init__(self, replay_data):
Expand Down Expand Up @@ -208,6 +210,10 @@ def socket(self, *args, **kwargs):
return RecordingSocket(self.record_file, socket.socket(*args, **kwargs))


class NeoPixel(extended_color.ExtendedColorLight):
pass


def run(replay_file=None):
if replay_file:
replay_lines = []
Expand All @@ -225,6 +231,8 @@ def run(replay_file=None):
matter = cm.CircuitMatter(
socketpool, mdns_server, random_source, "test_data/device_state.json"
)
led = NeoPixel()
matter.add_device(led)
while True:
matter.process_packets()

Expand Down
12 changes: 6 additions & 6 deletions circuitmatter/case.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ class Sigma1(tlv.Structure):


class Sigma2TbsData(tlv.Structure):
responderNOC = tlv.OctetStringMember(1, 32)
responderNOC = tlv.OctetStringMember(1, crypto.CERTIFICATE_SIZE)
responderICAC = tlv.OctetStringMember(2, crypto.CERTIFICATE_SIZE, optional=True)
responderEphPubKey = tlv.OctetStringMember(3, crypto.PUBLIC_KEY_SIZE_BYTES)
initiatorEphPubKey = tlv.OctetStringMember(4, crypto.PUBLIC_KEY_SIZE_BYTES)


class Sigma2TbeData(tlv.Structure):
responderNOC = tlv.OctetStringMember(1, 32)
responderNOC = tlv.OctetStringMember(1, crypto.CERTIFICATE_SIZE)
responderICAC = tlv.OctetStringMember(2, crypto.CERTIFICATE_SIZE, optional=True)
signature = tlv.OctetStringMember(3, 64)
signature = tlv.OctetStringMember(3, crypto.GROUP_SIZE_BYTES * 2)
resumptionID = tlv.OctetStringMember(4, 16)


Expand All @@ -42,16 +42,16 @@ class Sigma2(tlv.Structure):


class Sigma3TbsData(tlv.Structure):
initiatorNOC = tlv.OctetStringMember(1, 32)
initiatorNOC = tlv.OctetStringMember(1, crypto.CERTIFICATE_SIZE)
initiatorICAC = tlv.OctetStringMember(2, crypto.CERTIFICATE_SIZE, optional=True)
initiatorEphPubKey = tlv.OctetStringMember(3, crypto.PUBLIC_KEY_SIZE_BYTES)
responderEphPubKey = tlv.OctetStringMember(4, crypto.PUBLIC_KEY_SIZE_BYTES)


class Sigma3TbeData(tlv.Structure):
initiatorNOC = tlv.OctetStringMember(1, 32)
initiatorNOC = tlv.OctetStringMember(1, crypto.CERTIFICATE_SIZE)
initiatorICAC = tlv.OctetStringMember(2, crypto.CERTIFICATE_SIZE, optional=True)
signature = tlv.OctetStringMember(3, 64)
signature = tlv.OctetStringMember(3, crypto.GROUP_SIZE_BYTES * 2)


class Sigma3(tlv.Structure):
Expand Down
24 changes: 19 additions & 5 deletions circuitmatter/clusters/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ def set_regulatory_config(
response.ErrorCode = data_model.CommissioningErrorEnum.OK
return response

def commissioning_complete(
self, session
) -> data_model.GeneralCommissioningCluster.CommissioningCompleteResponse:
response = (
data_model.GeneralCommissioningCluster.CommissioningCompleteResponse()
)
response.ErrorCode = data_model.CommissioningErrorEnum.OK
print("Commissioning done!")
return response


class AttestationElements(tlv.Structure):
certification_declaration = tlv.OctetStringMember(0x01, max_length=400)
Expand Down Expand Up @@ -94,6 +104,7 @@ def __init__(self, group_key_manager, random_source, mdns_server, port):

self.root_certs = []
self.compressed_fabric_ids = []
self.noc_keys = []

self.mdns_server = mdns_server
self.port = port
Expand Down Expand Up @@ -268,11 +279,16 @@ def add_noc(
noc_struct.ICAC = args.ICACValue
self.nocs.append(noc_struct)

# Get the root cert public key so we can create the compressed fabric id.
root_cert, _ = crypto.MatterCertificate.decode(
self.pending_root_cert[0], memoryview(self.pending_root_cert)[1:]
)

# Store the fabric
new_fabric = (
data_model.NodeOperationalCredentialsCluster.FabricDescriptorStruct()
)
new_fabric.RootPublicKey = self.pending_root_cert
new_fabric.RootPublicKey = root_cert.ec_pub_key
new_fabric.VendorID = args.AdminVendorId
new_fabric.FabricID = noc.subject.matter_fabric_id
new_fabric.NodeID = noc.subject.matter_node_id
Expand All @@ -292,10 +308,8 @@ def add_noc(

self.commissioned_fabrics += 1

# Get the root cert public key so we can create the compressed fabric id.
root_cert, _ = crypto.MatterCertificate.decode(
self.pending_root_cert[0], memoryview(self.pending_root_cert)[1:]
)
self.noc_keys.append(self.pending_signing_key)

self.root_certs.append(root_cert)
fabric_id = struct.pack(">Q", noc.subject.matter_fabric_id)
self.compressed_fabric_ids.append(
Expand Down
7 changes: 5 additions & 2 deletions circuitmatter/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,11 @@ class MatterCertificate(tlv.Structure):
signature = tlv.OctetStringMember(11, GROUP_SIZE_BYTES * 2)


def Hash(message) -> bytes:
return hashlib.sha256(message).digest()
def Hash(*message) -> bytes:
h = hashlib.sha256()
for m in message:
h.update(m)
return h.digest()


def HMAC(key, message) -> bytes:
Expand Down
14 changes: 10 additions & 4 deletions circuitmatter/data_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ def get_attribute_data(self, path) -> interaction_model.AttributeDataIB:
for field_name, descriptor in self._attributes():
if descriptor.id != path.Attribute:
continue
print("reading", field_name)
value = getattr(self, field_name)
print("encoding anything", value)
data.Data = descriptor.encode(value)
Expand All @@ -180,12 +181,15 @@ def invoke(
if descriptor.command_id != path.Command:
continue

arg = descriptor.request_type.from_value(fields)
print("invoke", self, field_name, descriptor)
print(arg)
command = getattr(self, field_name)
if callable(command):
result = command(session, arg)
if descriptor.request_type is not None:
arg = descriptor.request_type.from_value(fields)
print(arg)
result = command(session, arg)
else:
result = command(session)
else:
print(field_name, "not implemented")
return None
Expand Down Expand Up @@ -386,7 +390,9 @@ class SetRegulatoryConfig(tlv.Structure):
0x02, SetRegulatoryConfig, 0x03, SetRegulatoryConfigResponse
)

commissioning_complete = Command(0x04, None, 0x05, CommissioningResponse)
CommissioningCompleteResponse = CommissioningResponse

commissioning_complete = Command(0x04, None, 0x05, CommissioningCompleteResponse)


class NetworkCommissioningCluster(Cluster):
Expand Down
Empty file.
Empty file.
5 changes: 5 additions & 0 deletions circuitmatter/device_types/lighting/color_temperature.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .dimmable import DimmableLight


class ColorTemperatureLight(DimmableLight):
DEVICE_TYPE_ID = 0x010C
5 changes: 5 additions & 0 deletions circuitmatter/device_types/lighting/dimmable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .on_off import OnOffLight


class DimmableLight(OnOffLight):
DEVICE_TYPE_ID = 0x0101
5 changes: 5 additions & 0 deletions circuitmatter/device_types/lighting/extended_color.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .color_temperature import ColorTemperatureLight


class ExtendedColorLight(ColorTemperatureLight):
DEVICE_TYPE_ID = 0x010D
2 changes: 2 additions & 0 deletions circuitmatter/device_types/lighting/on_off.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class OnOffLight:
DEVICE_TYPE_ID = 0x0100
1 change: 1 addition & 0 deletions circuitmatter/exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def send(self, protocol_id, protocol_opcode, application_payload=None):
self.send_standalone_time = None
message.acknowledged_message_counter = self.pending_acknowledgement
self.pending_acknowledgement = None
message.source_node_id = self.session.local_node_id
message.protocol_id = protocol_id
message.protocol_opcode = protocol_opcode
message.exchange_id = self.exchange_id
Expand Down
14 changes: 1 addition & 13 deletions circuitmatter/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def encode_into(self, buffer, cipher=None):
nonce_start = 3
nonce_end = nonce_start + 1 + 4
offset += 8
if self.source_node_id > 0:
if self.flags & (1 << 2):
struct.pack_into("<Q", buffer, offset, self.source_node_id)
offset += 8
nonce_end += 8
Expand Down Expand Up @@ -201,18 +201,6 @@ def encode_into(self, buffer, cipher=None):

return offset

@property
def source_node_id(self):
return self._source_node_id

@source_node_id.setter
def source_node_id(self, value):
self._source_node_id = value
if value > 0:
self.flags |= 1 << 2
else:
self.flags &= ~(1 << 2)

@property
def destination_node_id(self):
return self._destination_node_id
Expand Down
Loading

0 comments on commit 7b05d28

Please sign in to comment.