Skip to content

Commit

Permalink
Fix tag encoding, wrap in struct and parse StatusReport
Browse files Browse the repository at this point in the history
  • Loading branch information
tannewt committed Jul 23, 2024
1 parent 4778dea commit 05388f1
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 6 deletions.
101 changes: 101 additions & 0 deletions circuitmatter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,12 @@ def encode_into(self, buffer):
offset += 4
if self.application_payload is not None:
if isinstance(self.application_payload, tlv.TLVStructure):
# Wrap the structure in an anonymous tag.
buffer[offset] = 0x15
offset += 1
offset = self.application_payload.encode_into(buffer, offset)
buffer[offset] = 0x18
offset += 1
else:
buffer[offset : offset + len(self.application_payload)] = (
self.application_payload
Expand Down Expand Up @@ -588,6 +593,99 @@ def __str__(self):
return "\n ".join(pieces)


class GeneralCode(enum.IntEnum):
SUCCESS = 0
"""Operation completed successfully."""

FAILURE = 1
"""Generic failure, additional details may be included in the protocol specific status."""

BAD_PRECONDITION = 2
"""Operation was rejected by the system because the system is in an invalid state."""

OUT_OF_RANGE = 3
"""A value was out of a required range"""

BAD_REQUEST = 4
"""A request was unrecognized or malformed"""

UNSUPPORTED = 5
"""An unrecognized or unsupported request was received"""

UNEXPECTED = 6
"""A request was not expected at this time"""

RESOURCE_EXHAUSTED = 7
"""Insufficient resources to process the given request"""

BUSY = 8
"""Device is busy and cannot handle this request at this time"""

TIMEOUT = 9
"""A timeout occurred"""

CONTINUE = 10
"""Context-specific signal to proceed"""

ABORTED = 11
"""Failure, may be due to a concurrency error."""

INVALID_ARGUMENT = 12
"""An invalid/unsupported argument was provided"""

NOT_FOUND = 13
"""Some requested entity was not found"""

ALREADY_EXISTS = 14
"""The sender attempted to create something that already exists"""

PERMISSION_DENIED = 15
"""The sender does not have sufficient permissions to execute the requested operations."""

DATA_LOSS = 16
"""Unrecoverable data loss or corruption has occurred."""

MESSAGE_TOO_LARGE = 17
"""Message size is larger than the recipient can handle."""


class StatusReport:
def __init__(self):
self.clear()

def clear(self):
self.general_code: GeneralCode = 0
self.protocol_id = 0
self.protocol_code = 0
self.protocol_data = None

def encode_into(self, buffer, offset=0) -> int:
struct.pack_into(
"<HIH",
buffer,
offset,
self.general_code,
self.protocol_id,
self.protocol_code,
)
offset += 8
if self.protocol_data:
buffer[offset : offset + len(self.protocol_data)] = self.protocol_data
offset += len(self.protocol_data)
return offset

def decode(self, buffer):
print(buffer.hex(" "))
self.general_code, self.protocol_id, self.protocol_code = struct.unpack_from(
"<HIH", buffer
)
self.general_code = GeneralCode(self.general_code)
self.protocol_data = buffer[8:]

def __str__(self):
return f"StatusReport: General Code: {self.general_code!r}, Protocol ID: {self.protocol_id}, Protocol Code: {self.protocol_code}, Protocol Data: {self.protocol_data.hex()}"


class SessionManager:
def __init__(self, socket):
persist_path = pathlib.Path("counters.json")
Expand Down Expand Up @@ -891,6 +989,9 @@ def process_packet(self, address, data):
print("Received CASE Sigma2 Resume")
elif protocol_opcode == SecureProtocolOpcode.STATUS_REPORT:
print("Received Status Report")
report = StatusReport()
report.decode(message.application_payload)
print(report)
elif protocol_opcode == SecureProtocolOpcode.ICD_CHECK_IN:
print("Received ICD Check-in")

Expand Down
32 changes: 28 additions & 4 deletions circuitmatter/tlv.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,13 @@ def __init__(
self.tag_length = 0
if isinstance(tag, int):
self.tag_length = 1
if tag >= 256:
raise ValueError("Context specific tag too large")
elif isinstance(tag, tuple):
self.tag_length = 8
if tag[2] < 65536:
self.tag_length = 6
else:
self.tag_length = 8
self._max_length = None

@property
Expand Down Expand Up @@ -268,11 +273,30 @@ def encode_into(self, obj: TLVStructure, buffer: bytearray, offset: int) -> int:
elif not self.nullable:
raise ValueError("Required field isn't set")

buffer[offset] = 0x00 | element_type
tag_control = 0
if self.tag is not None:
tag_control = 1
if isinstance(self.tag, tuple):
tag_control = 0b110
if self.tag[2] >= 65536:
tag_control = 0b111

buffer[offset] = tag_control << 5 | element_type
offset += 1
if self.tag:
buffer[offset] = self.tag
offset += 1
if isinstance(self.tag, int):
buffer[offset] = self.tag
offset += 1
else:
vendor_id, profile_number, tag_number = self.tag
struct.pack_into("<HH", buffer, offset, vendor_id, profile_number)
offset += 4
if tag_number >= 65536:
struct.pack_into("<I", buffer, offset, tag_number)
offset += 4
else:
struct.pack_into("<H", buffer, offset, tag_number)
offset += 2
if value is not None:
new_offset = self.encode_value_into( # type: ignore # self inference issues
value,
Expand Down
2 changes: 1 addition & 1 deletion test_data/recorded_packets.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
[["receive", 277634700589145, ["fd98:bbab:bd61:8040:14ae:fe13:c814:3bc2", 58700, 0, 0], "BAAAAHseHgbighJRIqWzsAUgIGgAABUwASCFzR7wiM4L8C3VEpSXe5UK/1tjpinMKPaEtLYTbkR9kyUCLh4kAwAoBDUFJQH0ASUCLAElA6APJAQSJAULJgYAAAQBJAcBGBg="], ["receive", 277634706760287, ["fd98:bbab:bd61:8040:14ae:fe13:c814:3bc2", 58700, 0, 0], "BAAAAHweHgbighJRIqWzsAVAIGgAAAEAAAAAAAIA"], ["receive", 277634708186275, ["::ffff:192.168.0.37", 53917, 0, 0], "BAAAAH0eHgahFRe9xqs9aAUgIWgAABUwASAQJZk9pu15Dip5poydFQr50ZInKaRJg9hFd7lFMiV0QCUCLx4kAwAoBDUFJQH0ASUCLAElA6APJAQSJAULJgYAAAQBJAcBGBg="], ["receive", 277634727394981, ["::ffff:192.168.0.37", 53917, 0, 0], "BAAAAH4eHgahFRe9xqs9aAVAIWgAAAEAAAAAAAIA"], ["receive", 277635098871160, ["::ffff:192.168.0.37", 53917, 0, 0], "BAAAAH4eHgahFRe9xqs9aAVAIWgAAAEAAAAAAAIA"], ["receive", 277635109360371, ["fd98:bbab:bd61:8040:14ae:fe13:c814:3bc2", 58700, 0, 0], "BAAAAHweHgbighJRIqWzsAVAIGgAAAEAAAAAAAIA"], ["receive", 277635473211167, ["::ffff:192.168.0.37", 53917, 0, 0], "BAAAAH4eHgahFRe9xqs9aAVAIWgAAAEAAAAAAAIA"], ["receive", 277635519678001, ["fd98:bbab:bd61:8040:14ae:fe13:c814:3bc2", 58700, 0, 0], "BAAAAHweHgbighJRIqWzsAVAIGgAAAEAAAAAAAIA"], ["receive", 277636049081875, ["::ffff:192.168.0.37", 53917, 0, 0], "BAAAAH4eHgahFRe9xqs9aAVAIWgAAAEAAAAAAAIA"], ["receive", 277636061715619, ["fd98:bbab:bd61:8040:14ae:fe13:c814:3bc2", 58700, 0, 0], "BAAAAHweHgbighJRIqWzsAVAIGgAAAEAAAAAAAIA"], ["receive", 277637055974480, ["::ffff:192.168.0.37", 53917, 0, 0], "BAAAAH4eHgahFRe9xqs9aAVAIWgAAAEAAAAAAAIA"], ["receive", 277637087415383, ["fd98:bbab:bd61:8040:14ae:fe13:c814:3bc2", 58700, 0, 0], "BAAAAHweHgbighJRIqWzsAVAIGgAAAEAAAAAAAIA"]]
[["receive", 357657266155036, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 45867, 0, 0], "BAAAAObOzQyqpsS5iZt1AwUg70sAABUwASBHe5PONLiOU6V4DJ7DZkokBgIvtdAKtWjuE8CDoN8dPyUCVUQkAwAoBDUFJQH0ASUCLAElA6APJAQRJAULJgYAAAMBJAcBGBg="], ["receive", 357657273325183, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 45867, 0, 0], "BAAAAOfOzQyqpsS5iZt1AwUi70sAABUwAUEEZZRPQKxRLWkU0CZwUU4AJT0cfikv2BmIiAUNWeFqLcoZSI8ULSVK5Re/dNmunfoSd7YojNkgN6L+GJ3TN7ad/xg="], ["receive", 357657652704410, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 45867, 0, 0], "BAAAAOfOzQyqpsS5iZt1AwUi70sAABUwAUEEZZRPQKxRLWkU0CZwUU4AJT0cfikv2BmIiAUNWeFqLcoZSI8ULSVK5Re/dNmunfoSd7YojNkgN6L+GJ3TN7ad/xg="], ["receive", 357658002105028, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 45867, 0, 0], "BAAAAOfOzQyqpsS5iZt1AwUi70sAABUwAUEEZZRPQKxRLWkU0CZwUU4AJT0cfikv2BmIiAUNWeFqLcoZSI8ULSVK5Re/dNmunfoSd7YojNkgN6L+GJ3TN7ad/xg="], ["receive", 357658622312059, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 45867, 0, 0], "BAAAAOfOzQyqpsS5iZt1AwUi70sAABUwAUEEZZRPQKxRLWkU0CZwUU4AJT0cfikv2BmIiAUNWeFqLcoZSI8ULSVK5Re/dNmunfoSd7YojNkgN6L+GJ3TN7ad/xg="], ["receive", 357659576369391, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 45867, 0, 0], "BAAAAOfOzQyqpsS5iZt1AwUi70sAABUwAUEEZZRPQKxRLWkU0CZwUU4AJT0cfikv2BmIiAUNWeFqLcoZSI8ULSVK5Re/dNmunfoSd7YojNkgN6L+GJ3TN7ad/xg="]]
63 changes: 63 additions & 0 deletions tests/test_status_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from circuitmatter import StatusReport, GeneralCode, ProtocolId


def test_example1():
report = StatusReport()
report.general_code = GeneralCode.FAILURE
report.protocol_id = ProtocolId.BDX
report.protocol_code = 0x52

buffer = bytearray(10)
assert report.encode_into(buffer) == 8
assert buffer[:8] == bytes.fromhex("01 00 02 00 00 00 52 00")


def test_example1_decode():
report = StatusReport()
report.decode(bytes.fromhex("01 00 02 00 00 00 52 00"))

assert report.general_code == GeneralCode.FAILURE
assert report.protocol_id == ProtocolId.BDX
assert report.protocol_code == 0x52


def test_example2():
report = StatusReport()
report.general_code = GeneralCode.SUCCESS
report.protocol_id = 0xFFF1AABB
report.protocol_code = 0

buffer = bytearray(10)
assert report.encode_into(buffer) == 8
assert buffer[:8] == bytes.fromhex("00 00 BB AA F1 FF 00 00")


def test_example2_decode():
report = StatusReport()
report.decode(bytes.fromhex("00 00 BB AA F1 FF 00 00"))

assert report.general_code == GeneralCode.SUCCESS
assert report.protocol_id == 0xFFF1AABB
assert report.protocol_code == 0


def test_protocol_data_example():
report = StatusReport()
report.general_code = GeneralCode.FAILURE
report.protocol_id = 0xFFF1AABB
report.protocol_code = 9921
report.protocol_data = [0x55, 0x66, 0xEE, 0xFF]

buffer = bytearray(20)
assert report.encode_into(buffer) == 12
assert buffer[:12] == bytes.fromhex("01 00 BB AA F1 FF C1 26 55 66 EE FF")


def test_protocol_data_example_decode():
report = StatusReport()
report.decode(bytes.fromhex("01 00 BB AA F1 FF C1 26 55 66 EE FF"))

assert report.general_code == GeneralCode.FAILURE
assert report.protocol_id == 0xFFF1AABB
assert report.protocol_code == 9921
assert report.protocol_data == b"\x55\x66\xee\xff"
29 changes: 28 additions & 1 deletion tests/test_tlv.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,10 +513,37 @@ def test_inner_struct_encode(self):
inner.b = -17
s.s = inner
assert (
s.encode().tobytes() == b"\x15\x02*\x00\x00\x00\x02\x01\xef\xff\xff\xff\x18"
s.encode().tobytes()
== b"\x15\x22\x2a\x00\x00\x00\x22\x01\xef\xff\xff\xff\x18"
)

def test_inner_struct_encode_empty(self):
s = OuterStruct()
s.s = InnerStruct()
assert s.encode().tobytes() == b"\x15\x18"


class FullyQualified(tlv.TLVStructure):
a = tlv.IntMember((0xADA, 0xF00, 0x123), signed=True, optional=True, octets=4)
b = tlv.IntMember((0xADA, 0xF00, 0x12345), signed=True, optional=True, octets=4)


class TestFullyQualifiedTags:
def test_decode(self):
s = FullyQualified(
b"\xc2\xda\x0a\x00\x0f\x23\x01\x2a\x00\x00\x00\xe2\xda\x0a\x00\x0f\x45\x23\x01\x00\xef\xff\xff\xff"
)
assert_type(s, FullyQualified)
assert_type(s.a, Optional[int])
assert str(s) == "{\n a = 42,\n b = -17\n}"
assert s.a == 42
assert s.b == -17

def test_encode(self):
s = FullyQualified()
s.a = 42
s.b = -17
assert (
s.encode().tobytes()
== b"\xc2\xda\x0a\x00\x0f\x23\x01\x2a\x00\x00\x00\xe2\xda\x0a\x00\x0f\x45\x23\x01\x00\xef\xff\xff\xff"
)

0 comments on commit 05388f1

Please sign in to comment.