Move adding end container into subclass. Struct requires a tag

This commit is contained in:
Scott Shawcroft 2024-09-23 15:56:46 -07:00
parent 05745b1952
commit 51ce818a6a
No known key found for this signature in database
7 changed files with 154 additions and 127 deletions

View file

@ -534,8 +534,6 @@ class Message:
unencrypted_offset = self.application_payload.encode_into(
unencrypted_buffer, unencrypted_offset
)
unencrypted_buffer[unencrypted_offset] = 0x18
unencrypted_offset += 1
elif isinstance(self.application_payload, StatusReport):
unencrypted_offset = self.application_payload.encode_into(
unencrypted_buffer, unencrypted_offset
@ -554,22 +552,18 @@ class Message:
# Encrypt the payload
if cipher is not None:
# The message may not include the source_node_id so we encode the nonce separately.
print(self.message_counter)
nonce = struct.pack(
"<BIQ", self.security_flags, self.message_counter, self.source_node_id
)
print("nonce", nonce_end - nonce_start, nonce.hex(" "))
additional = buffer[:offset]
self.payload = cipher.encrypt(
nonce, bytes(unencrypted_buffer[:unencrypted_offset]), bytes(additional)
)
print("encrypted", len(self.payload), self.payload.hex(" "))
buffer[offset : offset + len(self.payload)] = self.payload
offset += len(self.payload)
else:
offset = unencrypted_offset
print("encoded", buffer[:offset].hex(" "))
return offset
@property
@ -896,7 +890,6 @@ class GeneralCommissioningCluster(data_model.GeneralCommissioningCluster):
) -> data_model.GeneralCommissioningCluster.ArmFailSafeResponse:
response = data_model.GeneralCommissioningCluster.ArmFailSafeResponse()
response.ErrorCode = data_model.CommissioningErrorEnum.OK
print("respond", response)
return response
@ -1001,14 +994,15 @@ class CircuitMatter:
def get_report(self, cluster, path):
report = interaction_model.AttributeReportIB()
astatus = interaction_model.AttributeStatusIB()
astatus.Path = path
status = interaction_model.StatusIB()
status.Status = 0
status.ClusterStatus = 0
astatus.Status = status
report.AttributeStatus = astatus
report.AttributeData = cluster.get_attribute_data(path)
# Only add status if an error occurs
# astatus = interaction_model.AttributeStatusIB()
# astatus.Path = path
# status = interaction_model.StatusIB()
# status.Status = 0
# status.ClusterStatus = 0
# astatus.Status = status
# report.AttributeStatus = astatus
return report
def invoke(self, cluster, path, fields, command_ref):
@ -1066,7 +1060,7 @@ class CircuitMatter:
# This is Section 4.14.1.2
request, _ = pase.PBKDFParamRequest.decode(
message.application_payload[0], message.application_payload[1:-1]
message.application_payload[0], message.application_payload[1:]
)
print("PBKDF", request)
exchange.commissioning_hash = hashlib.sha256(
@ -1094,7 +1088,7 @@ class CircuitMatter:
params.salt = binascii.a2b_base64(self.nonvolatile["salt"])
response.pbkdf_parameters = params
encoded = b"\x15" + response.encode() + b"\x18"
encoded = response.encode()
exchange.commissioning_hash.update(encoded)
exchange.send(
ProtocolId.SECURE_CHANNEL,
@ -1206,6 +1200,8 @@ class CircuitMatter:
for endpoint in self._endpoints:
if path.Cluster in self._endpoints[endpoint]:
cluster = self._endpoints[endpoint][path.Cluster]
# TODO: The path object probably needs to be cloned. Otherwise we'll
# change the endpoint for all uses.
path.Endpoint = endpoint
attribute_reports.append(self.get_report(cluster, path))
else:
@ -1261,8 +1257,6 @@ class CircuitMatter:
)
else:
print(f"Cluster 0x{path.Cluster:02x} not found")
for r in invoke_responses:
print(r)
response = interaction_model.InvokeResponseMessage()
response.SuppressResponse = False
response.InvokeResponses = invoke_responses

View file

@ -36,6 +36,15 @@ class ReplaySocket:
def sendto(self, data, address):
if address is None:
raise ValueError("Address must be set")
direction, _, address, data_b64 = self.replay_data.pop(0)
if direction == "send":
decoded = binascii.a2b_base64(data_b64)
for i, b in enumerate(data):
if b != decoded[i]:
print("sent", data.hex(" "))
print("old ", decoded.hex(" "))
print(i, hex(b), hex(decoded[i]))
raise RuntimeError("Next replay packet does not match sent data")
return len(data)

View file

@ -153,16 +153,13 @@ class Cluster:
yield field_name, descriptor
def get_attribute_data(self, path) -> interaction_model.AttributeDataIB:
print("get_attribute_data", path.Attribute)
data = interaction_model.AttributeDataIB()
data.DataVersion = 0
data.Path = path
found = False
for field_name, descriptor in self._attributes():
print("maybe", field_name, descriptor)
if descriptor.id != path.Attribute:
continue
print("read", field_name, descriptor)
data.Data = descriptor.encode(getattr(self, field_name))
found = True
break

View file

@ -58,8 +58,8 @@ class AttributeStatusIB(tlv.Structure):
class AttributeReportIB(tlv.Structure):
AttributeStatus = tlv.StructMember(0, AttributeStatusIB)
AttributeData = tlv.StructMember(1, AttributeDataIB)
AttributeStatus = tlv.StructMember(0, AttributeStatusIB, optional=True)
AttributeData = tlv.StructMember(1, AttributeDataIB, optional=True)
class ReadRequestMessage(tlv.Structure):

View file

@ -106,7 +106,7 @@ class Container:
def max_length(cls):
if cls._max_length is None:
cls._max_length = sum(member.max_length for _, member in cls._members())
return cls._max_length
return cls._max_length + 2
@classmethod
def _members(cls) -> Iterable[tuple[str, Member]]:
@ -125,6 +125,9 @@ class Container:
cls._members_by_tag_cache = members
return members
def set_value(self, tag, value):
self.values[tag] = value
class Structure(Container):
def __str__(self):
@ -144,13 +147,15 @@ class Structure(Container):
def encode(self) -> memoryview:
buffer = bytearray(self.max_length())
end = self.encode_into(buffer)
buffer[0] = ElementType.STRUCTURE
end = self.encode_into(buffer, offset=1)
return memoryview(buffer)[:end]
def encode_into(self, buffer: bytearray, offset: int = 0) -> int:
for _, descriptor_class in self._members():
offset = descriptor_class.encode_into(self, buffer, offset)
return offset
buffer[offset] = ElementType.END_OF_CONTAINER
return offset + 1
@classmethod
def decode(cls, control_octet, buffer, offset=0, depth=0) -> tuple[dict, int]:
@ -168,15 +173,11 @@ class Structure(Container):
return cls.from_value(values), offset
def construct_containers(self):
print("construct_containers")
for name, member_class in self._members():
print(name, member_class)
tag = member_class.tag
if tag not in self.values:
continue
self.values[tag] = member_class.from_value(self.values[tag])
print("replaced", name, self.values[tag])
print("construct_containers done")
@classmethod
def from_value(cls, value):
@ -262,7 +263,7 @@ class Member(ABC, Generic[_T, _OPT, _NULLABLE]):
def __set__(self, obj, value):
if value is None and not self.nullable:
raise ValueError("Not nullable")
obj.values[self.tag] = value
obj.set_value(self.tag, value)
def encode_into(
self, obj: Container, buffer: bytearray, offset: int, anonymous_ok=False
@ -544,11 +545,11 @@ class StringMember(Member[AnyStr, _OPT, _NULLABLE], Generic[AnyStr, _OPT, _NULLA
nullable: _NULLABLE = False,
**kwargs,
):
self.max_value_length = max_length
length_encoding = int(math.log(max_length, 256))
self._element_type = self._base_element_type | length_encoding
self.length_format = INT_SIZE[length_encoding]
self.length_length = struct.calcsize(self.length_format)
self.max_value_length = max_length + self.length_length
super().__init__(tag, optional=optional, nullable=nullable, **kwargs)
def print(self, value):
@ -629,8 +630,7 @@ class StructMember(Member[_TLVStruct, _OPT, _NULLABLE]):
def encode_value_into(self, value, buffer: bytearray, offset: int) -> int:
offset = value.encode_into(buffer, offset)
buffer[offset] = ElementType.END_OF_CONTAINER
return offset + 1
return offset
def from_value(self, value):
return self.substruct_class.from_value(value)
@ -677,6 +677,8 @@ class ArrayMember(Member[_TLVStruct, _OPT, _NULLABLE]):
buffer[offset] = ElementType.STRUCTURE
elif isinstance(v, List):
buffer[offset] = ElementType.LIST
else:
raise NotImplementedError("Unknown type")
offset = v.encode_into(buffer, offset + 1)
buffer[offset] = ElementType.END_OF_CONTAINER
offset += 1
@ -726,7 +728,8 @@ class List(Container):
def encode(self) -> memoryview:
buffer = bytearray(self.max_length())
end = self.encode_into(buffer)
buffer[0] = ElementType.LIST
end = self.encode_into(buffer, offset=1)
return memoryview(buffer)[:end]
def encode_into(self, buffer: bytearray, offset: int = 0) -> int:
@ -741,7 +744,8 @@ class List(Container):
offset = member.encode_into(self, buffer, offset, anonymous_ok=True)
else:
raise NotImplementedError("Anonymous list member")
return offset
buffer[offset] = ElementType.END_OF_CONTAINER
return offset + 1
@classmethod
def from_value(cls, value):
@ -760,6 +764,14 @@ class List(Container):
instance.values[tag] = value
return instance
def set_value(self, tag, value):
if tag in self.values:
i = self.items.index((tag, self.values[tag]))
self.items[i] = (tag, value)
else:
self.values[tag] = value
self.items.append((tag, value))
_TLVList = TypeVar("_TLVList", bound=List)
@ -801,8 +813,7 @@ class ListMember(Member):
def encode_value_into(self, value, buffer: bytearray, offset: int) -> int:
offset = value.encode_into(buffer, offset)
buffer[offset] = ElementType.END_OF_CONTAINER
return offset + 1
return offset
def from_value(self, value):
return self.substruct_class.from_value(value)

View file

@ -1,17 +1,17 @@
["urandom", 350585853607419, 8, "xA4yE5YTpkA="]
["receive", 350587096075493, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 49091, 0, 0], "BAAAAMa1vQPUSbbPrqLtJgUggP8AABUwASD2zxwaK72yIGXgGo6eoto/w+n5f2yRavZXWHgi+PXapiUC7d0kAwAoBDUFJQH0ASUCLAElA6APJAQRJAULJgYAAAMBJAcBGBg="]
["urandom", 350587128879760, 32, "xzGpsJxnxQou/EF15WCv/oi7aedtmlZU4Q6aV6MxybA="]
["send", 350587129006309, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 49091, 0, 0], "AQAAANcK+wHUSbbPrqLtJgIhgP8AAMa1vQMVMAEg9s8cGiu9siBl4BqOnqLaP8Pp+X9skWr2V1h4Ivj12qYwAiDHMamwnGfFCi78QXXlYK/+iLtp522aVlThDppXozHJsCUDAQA1BCYBECcAADACIObgj9CEx2MyPagRHuoX1OB32N8u1aKUpNKjb4b854YkGBg="]
["receive", 350587135507153, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 49091, 0, 0], "BAAAAMe1vQPUSbbPrqLtJgUigP8AABUwAUEEFc1AB41Tr1tAfC+PmT/xEy1cglver2gjGxbxKQqOEwQwtzj5tIZk1CFEUZv5VhlQEO8FK9E+Zf1vn34PckFIURg="]
["randbelow", 350587135613023, 115792089210356248762697446949407573529996955224135760342422259061068512044369, 115473339479673884280104370450166017066816674872314297419471759872926838745841]
["send", 350587151156721, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 49091, 0, 0], "AQAAANgK+wHUSbbPrqLtJgIjgP8AAMe1vQMVMAFBBAcGjqic2zu9I/CWQ37MQ5Cq/uQwGTiRZZ6x43FGUqrqb23/8yKqDH97/lXjZhuvNkEXdJyYekesbJRmDoSx9GIwAiAhyUXSRm/Nx/uuT0x/rFn4daOLfydl7fB9o3ri4KTsThg="]
["receive", 350587151563108, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 49091, 0, 0], "BAAAAMi1vQPUSbbPrqLtJgUkgP8AABUwASAOyxB90IQ4pXVTkJGMAzxfshniG0vsE4vJxJUXpJgDOxg="]
["send", 350587151661434, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 49091, 0, 0], "AQAAANkK+wHUSbbPrqLtJgJAgP8AAMi1vQMAAAAAAAAAAA=="]
["receive", 350587151843067, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 49091, 0, 0], "AAEAAMvQfgqe1+b0ErOxGZUF3l4lhUVdlt+wCpTEaf28uQeGD6X2KPek1H52Uj2ePzDcnG28EM3DR7goMhI9+quoNZNEUASzCTDWppnMtUNZCeKOXYkadV1xURswla3q2PBBbFMclILwJRat6AB4G0oD6n9ciGcYMGdL9mwXuJR0hKE="]
["send", 350587152774505, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 49091, 0, 0], "AO3dAFApww0GutoB/WkZhRK+lHoiYxg7UjxXqyc0Vo4IvBw35itUf36ndZZ8qEjcqq3ukqUkAUcW3bs27R6Gg47aG1tgP4BNalazU8GZWxsDE36+VQSjQvF+NcipaAJT8Y4gbX98B41duYkP6GQI4M9v7Id9rlyC2AImwnGiRlTTDkkPsibLxXgVHAVzR2+BezIxPz5i+Q6wSg0tyn2R5gablGAC1NE2OzLqZkqY0lRz6khr2WNHRjf3Da6hbzdc+UPk1uI7kFZ9A9ZGYddIqBiENkUK2m5s8CDPcOeFOtsqlKxArldwMmPcUxKfklwdbCvHDyk3BR6G/DTmbS5OYWLEApSQfkIYGyc0263HwVDWvszaREkPiQw5nFoGa/cnjTt04gPyBl+SVkUJgGtq+UTszBm0t8401Jn04/rf7ekzaIlYYnjIAdBQkn6Hhg6ERrOMWN5K5WQG5vJbo4LX4r9L5SkeCsB9MLbq8KLvc/quWFPwNHYF2sHUKnT2za/rJUiRiZo+TIbwMIb9LrobUrKVSzo4Gj4SCElJCHv9z1Gdzs5pa7QOetjyZJ6817Gane0P639wm5C0fWOnu9obXha2FVB/oNIxuuiMqNA/Yi+CkQ8g3geK0D07LfL+RTcQrTIyFnCK/nCE0qCiNe+oi1rlQKo+zGCu9/fOX2ObqlO24xcYJ4ds8e+GC+eu4U6YztcqORp9hweYzHdui8J93w=="]
["receive", 350587159372072, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 49091, 0, 0], "AAEAAMzQfgptUG7fZgN3ppDYyBO7CiMe3ENiO7eXLyCv+YWjYv4="]
["receive", 350587159472783, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 49091, 0, 0], "AAEAAM3Qfgrgg3jWjNGgrTnf6GrhD/hniSA4j+7sRWnSu649VdCgKoz0ie0mLC2wunbfPDriu3ZzaQ/5H0ILqB4kVgIr2wQniwuEW1kFraee9/Asyw=="]
["send", 350587159756658, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 49091, 0, 0], "AO3dAFEpww07UbyghW+JBq253x+tXYUe/KjdCdeEUVy1HIUJ6FEQOiNSFRwVtS5+ZWpGcGGud/Yr02ZmhLtzHiH3KapX332etipkfIEbG2t84fFKJ17C9pUddLjUwEzsNiiNyX/U"]
["receive", 350587161059829, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 49091, 0, 0], "AAEAAM7QfgpRsOL0plLx7IwHLDLy/owyMK2cA8FWoBOmDf0eEVE="]
["receive", 350587161149769, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 49091, 0, 0], "AAEAAM/QfgqOO+MnXhNDVBSV19QiDStKc+ubq3myHXfyvduZdu29jZw93Lp0Kb+6kSU6OcpYqs+uubMBipiCo0w="]
["send", 350587161501002, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 49091, 0, 0], "AO3dAFIpww03V0tvzqO+Gcb4kRjJ9bhHZsZ7MR/oLOE6EakbF5ftcueaktUx+ccNFQYJUnu4YozpT7Y7p2Zs2bxa9jnZ6er12+ekgInHNq3eca5Du0e8Pyvq+h1cUJqBH1SoFhA7UnY="]
["urandom", 611538362769836, 8, "7IqPDolswXE="]
["receive", 611542342146276, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 33687, 0, 0], "BAAAALOJYA42R2d4e1fWQQUg4sIAABUwASDozjMsxicrnDj/NjeSaB9m8NX7G4LdPJnqm4uWwo7S9iUCVnMkAwAoBDUFJQH0ASUCLAElA6APJAQRJAULJgYAAAMBJAcBGBg="]
["urandom", 611542373385499, 32, "amrbDtzcWt2dJboBhSVhVAriSUlrFBxMUpW8ti+9lWw="]
["send", 611542373589975, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 33687, 0, 0], "AQAAANMJMg42R2d4e1fWQQIh4sIAALOJYA4VMAEg6M4zLMYnK5w4/zY3kmgfZvDV+xuC3TyZ6puLlsKO0vYwAiBqatsO3Nxa3Z0lugGFJWFUCuJJSWsUHExSlby2L72VbCUDAQA1BCYBECcAADACIObgj9CEx2MyPagRHuoX1OB32N8u1aKUpNKjb4b854YkGBg="]
["receive", 611542379685073, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 33687, 0, 0], "BAAAALSJYA42R2d4e1fWQQUi4sIAABUwAUEE4dM8AKRxdKyocQVO66aeobNSpjdAT+SUaM9WRRwvUQxYlDRgG6tvGzME1em13s/p3s0UxOGd8ZfjjnlP6g07Khg="]
["randbelow", 611542379785923, 115792089210356248762697446949407573529996955224135760342422259061068512044369, 18686455751714252099427219551429565272567932817770329314969969789848945100921]
["send", 611542395205834, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 33687, 0, 0], "AQAAANQJMg42R2d4e1fWQQIj4sIAALSJYA4VMAFBBKVs5C/yQ3zmpRjWWITnJFvTA5eK04zPlLpZ/xFHnrjXZ6KvD4CwYeH3XRUN/ogNaN1g9yl4WqCUyPpgTLn9jDYwAiBdQqEMicwLaMyAl13/N6aoyXZlObtk5/S9nUsm+pVWVBg="]
["receive", 611542395669018, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 33687, 0, 0], "BAAAALWJYA42R2d4e1fWQQUk4sIAABUwASDTWToRuOYxnSZIUTzy/5Fsd4ytbi9Bn8MrOw8A1v6lTRg="]
["send", 611542395771731, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 33687, 0, 0], "AQAAANUJMg42R2d4e1fWQQJA4sIAALWJYA4AAAAAAAAAAA=="]
["receive", 611542395940870, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 33687, 0, 0], "AAEAAB0yugTvmTZ+TWtppwYBzVVge27aOTCoKcz8bddeiIPpiK9FKYNhjU8NYXkThvO48PxKDSXNup6TnxzxLIr4WlRdIgDDhjxw1ZFNWCl+w6VAW22ewNv5ZmS8IVfbKBGzsKlIremKCLjZ5ru3Z2tBvmF/h+aCPP/eIyOBIwQQ+WU="]
["send", 611542396948932, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 33687, 0, 0], "AFZzAO5wmAop3tJHsU/2PUDdpaQCNFauxcgwzp3GWJuCQWdVI6MeiAmEQwgTl9caHeohNo3j3ySc+DagOwyz1BhkyV2F5L0Kdnzxc4hBrYKZaqVA/NKQy4jQ1d9/Jsp/JZWSwDUPKlmwgBYXom9jYPTJ/Q6AHrIRoUuhjj60m5VVsNdLf9G/hw8XWr4OmwduzQGRJntAoRvArhax8hzII/fnTS2UgSPUF1Xt283CHdMrdZ4oeOlPMo9V07bgDs0zvJLyV6OO1LdUxeT8g2mRKSiepjoR5mEQe+lLpYh2jDn3Y5Kt4SfzCZH7ssmwTZyoheQtUHTOyFu4DPrDQWR91ISAvK7vwrMvPd+xms/Fh8duEyRGL9mgrwLDLkBgMrFX9lRDoGBHMIJYYw39R1H6qvcE5guEXqnS8v5KSBR6Phwm"]
["receive", 611542397100839, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 33687, 0, 0], "AAEAAB4yugQRmjD6ItRCbJzoGAHAbdXYq9K3559GlLfYG4XYLic="]
["receive", 611542397196479, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 33687, 0, 0], "AAEAAB8yugQGuEaWHI2lm801cUoFP3zTOjHD8ZALr6FM9LM9m2OrWWToL8VwGuMa6NJGXohvkhXeRu3fJQl3tQqckkYk267vy/H6QbreqIA2HNb91w=="]
["send", 611542397496766, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 33687, 0, 0], "AFZzAO9wmAqY4GYHxiwxqrs+Jh4i+msLc7f5d5WaiFrIacMs89uk3eNOX42c55b5kh9ZtGCpAavifRjWJNwp1cCk9kV+9RWp"]
["receive", 611542398355386, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 33687, 0, 0], "AAEAACAyugT0CApC4YECx60j1svaX1+xPU+rgo1Mxktq7xX2TD8="]
["receive", 611542398452960, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 33687, 0, 0], "AAEAACEyugSo6dyOf3rdLZogs5AXfa0nGZyMzJb776xye5L4/n5mdxsYu8Gy0Jx/pK/vwaf4E49W39id9SJwV88="]
["send", 611542398901516, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 33687, 0, 0], "AFZzAPBwmArRIbed47XNIBhM1dGbfV2EsZbpg0DjI7maFko4C61hoB/7WEvLEbvH1aV3AdXMS5FuEcmJQUacYCnnsPZAkhAsDszflspu+kWkWecwp2rEQWy4reEDGwUWrzl+HVGLR207Zd8="]

View file

@ -19,45 +19,45 @@ from circuitmatter import tlv
class Bool(tlv.Structure):
b = tlv.BoolMember(None)
b = tlv.BoolMember(0)
class TestBool:
def test_bool_false_decode(self):
s, _ = Bool.decode(0x15, b"\x08\x18")
s, _ = Bool.decode(0x15, b"\x28\x00\x18")
assert str(s) == "{\n b = false\n}"
assert s.b is False
def test_bool_true_decode(self):
s, _ = Bool.decode(0x15, b"\x09\x18")
s, _ = Bool.decode(0x15, b"\x29\x00\x18")
assert str(s) == "{\n b = true\n}"
assert s.b is True
def test_bool_false_encode(self):
s = Bool()
s.b = False
assert s.encode().tobytes() == b"\x08"
assert s.encode().tobytes() == b"\x15\x28\x00\x18"
def test_bool_true_encode(self):
s = Bool()
s.b = True
assert s.encode().tobytes() == b"\x09"
assert s.encode().tobytes() == b"\x15\x29\x00\x18"
class SignedIntOneOctet(tlv.Structure):
i = tlv.NumberMember(None, "b")
i = tlv.NumberMember(0, "b")
class SignedIntTwoOctet(tlv.Structure):
i = tlv.NumberMember(None, "h")
i = tlv.NumberMember(0, "h")
class SignedIntFourOctet(tlv.Structure):
i = tlv.NumberMember(None, "i")
i = tlv.NumberMember(0, "i")
class SignedIntEightOctet(tlv.Structure):
i = tlv.NumberMember(None, "q")
i = tlv.NumberMember(0, "q")
# Signed Integer, 1-octet, value 42
@ -72,54 +72,58 @@ class SignedIntEightOctet(tlv.Structure):
# 03 00 90 2f 50 09 00 00 00
class TestSignedInt:
def test_signed_int_42_decode(self):
s, _ = SignedIntOneOctet.decode(0x15, b"\x00\x2a")
s, _ = SignedIntOneOctet.decode(0x15, b"\x20\x00\x2a")
assert str(s) == "{\n i = 42\n}"
assert s.i == 42
def test_signed_int_negative_17_decode(self):
s, _ = SignedIntOneOctet.decode(0x15, b"\x00\xef")
s, _ = SignedIntOneOctet.decode(0x15, b"\x20\x00\xef")
assert str(s) == "{\n i = -17\n}"
assert s.i == -17
def test_signed_int_42_two_octet_decode(self):
s, _ = SignedIntTwoOctet.decode(0x15, b"\x01\x2a\x00")
s, _ = SignedIntTwoOctet.decode(0x15, b"\x21\x00\x2a\x00")
assert str(s) == "{\n i = 42\n}"
assert s.i == 42
def test_signed_int_negative_170000_decode(self):
s, _ = SignedIntFourOctet.decode(0x15, b"\x02\xf0\x67\xfd\xff")
s, _ = SignedIntFourOctet.decode(0x15, b"\x22\x00\xf0\x67\xfd\xff")
assert str(s) == "{\n i = -170000\n}"
assert s.i == -170000
def test_signed_int_40000000000_decode(self):
s, _ = SignedIntEightOctet.decode(0x15, b"\x03\x00\x90\x2f\x50\x09\x00\x00\x00")
s, _ = SignedIntEightOctet.decode(
0x15, b"\x23\x00\x00\x90\x2f\x50\x09\x00\x00\x00"
)
assert str(s) == "{\n i = 40000000000\n}"
assert s.i == 40000000000
def test_signed_int_42_encode(self):
s = SignedIntOneOctet()
s.i = 42
assert s.encode().tobytes() == b"\x00\x2a"
assert s.encode().tobytes() == b"\x15\x20\x00\x2a\x18"
def test_signed_int_negative_17_encode(self):
s = SignedIntOneOctet()
s.i = -17
assert s.encode().tobytes() == b"\x00\xef"
assert s.encode().tobytes() == b"\x15\x20\x00\xef\x18"
def test_signed_int_42_two_octet_encode(self):
s = SignedIntTwoOctet()
s.i = 42
assert s.encode().tobytes() == b"\x01\x2a\x00"
assert s.encode().tobytes() == b"\x15\x21\x00\x2a\x00\x18"
def test_signed_int_negative_170000_encode(self):
s = SignedIntFourOctet()
s.i = -170000
assert s.encode().tobytes() == b"\x02\xf0\x67\xfd\xff"
assert s.encode().tobytes() == b"\x15\x22\x00\xf0\x67\xfd\xff\x18"
def test_signed_int_40000000000_encode(self):
s = SignedIntEightOctet()
s.i = 40000000000
assert s.encode().tobytes() == b"\x03\x00\x90\x2f\x50\x09\x00\x00\x00"
assert (
s.encode().tobytes() == b"\x15\x23\x00\x00\x90\x2f\x50\x09\x00\x00\x00\x18"
)
@pytest.mark.parametrize(
"octets,lower,upper",
@ -147,21 +151,21 @@ class TestSignedInt:
class UnsignedIntOneOctet(tlv.Structure):
i = tlv.NumberMember(None, "B")
i = tlv.NumberMember(0, "B")
# Unsigned Integer, 1-octet, value 42U
# 04 2a
class TestUnsignedInt:
def test_unsigned_int_42_decode(self):
s, _ = UnsignedIntOneOctet.decode(0x15, b"\x04\x2a")
s, _ = UnsignedIntOneOctet.decode(0x15, b"\x24\x00\x2a\x18")
assert str(s) == "{\n i = 42U\n}"
assert s.i == 42
def test_unsigned_int_42_encode(self):
s = UnsignedIntOneOctet()
s.i = 42
assert s.encode().tobytes() == b"\x04\x2a"
assert s.encode().tobytes() == b"\x15\x24\x00\x2a\x18"
@pytest.mark.parametrize(
"octets,lower,upper",
@ -193,7 +197,7 @@ class TestUnsignedInt:
s.i = v
buffer = s.encode().tobytes()
s2, _ = UnsignedIntOneOctet.decode(0x15, buffer)
s2, _ = UnsignedIntOneOctet.decode(0x15, buffer[1:])
assert s2.i == s.i
assert str(s2) == str(s)
@ -219,37 +223,38 @@ class TestUnsignedInt:
# UTF-8 String, 1-octet length, "Tschüs"
# 0c 07 54 73 63 68 c3 bc 73
class UTF8StringOneOctet(tlv.Structure):
s = tlv.UTF8StringMember(None, 16)
s = tlv.UTF8StringMember(0, 16)
class TestUTF8String:
def test_utf8_string_hello_decode(self):
s, _ = UTF8StringOneOctet.decode(0x15, b"\x0c\x06Hello!")
s, _ = UTF8StringOneOctet.decode(0x15, b"\x2c\x00\x06Hello!")
assert str(s) == '{\n s = "Hello!"\n}'
assert s.s == "Hello!"
def test_utf8_string_tschs_decode(self):
s, _ = UTF8StringOneOctet.decode(0x15, b"\x0c\x07Tsch\xc3\xbcs")
s, _ = UTF8StringOneOctet.decode(0x15, b"\x2c\x00\x07Tsch\xc3\xbcs")
assert str(s) == '{\n s = "Tschüs"\n}'
assert s.s == "Tschüs"
def test_utf8_string_hello_encode(self):
s = UTF8StringOneOctet()
s.s = "Hello!"
assert s.encode().tobytes() == b"\x0c\x06Hello!"
assert s.encode().tobytes() == b"\x15\x2c\x00\x06Hello!\x18"
def test_utf8_string_tschs_encode(self):
s = UTF8StringOneOctet()
s.s = "Tschüs"
assert s.encode().tobytes() == b"\x0c\x07Tsch\xc3\xbcs"
assert s.encode().tobytes() == b"\x15\x2c\x00\x07Tsch\xc3\xbcs\x18"
@given(v=...)
@given(v=st.text(max_size=4))
def test_roundtrip(self, v: str):
s = UTF8StringOneOctet()
print(len(v))
s.s = v
buffer = s.encode().tobytes()
s2, _ = UTF8StringOneOctet.decode(0x15, buffer)
s2, _ = UTF8StringOneOctet.decode(0x15, buffer[1:])
assert s2.s == s.s
assert str(s2) == str(s)
@ -258,27 +263,27 @@ class TestUTF8String:
# Octet String, 1-octet length, octets 00 01 02 03 04
# encoded: 10 05 00 01 02 03 04
class OctetStringOneOctet(tlv.Structure):
s = tlv.OctetStringMember(None, 16)
s = tlv.OctetStringMember(0, 16)
class TestOctetString:
def test_octet_string_decode(self):
s, _ = OctetStringOneOctet.decode(0x15, b"\x10\x05\x00\x01\x02\x03\x04")
s, _ = OctetStringOneOctet.decode(0x15, b"\x30\x00\x05\x00\x01\x02\x03\x04\x18")
assert str(s) == "{\n s = 00 01 02 03 04\n}"
assert s.s == b"\x00\x01\x02\x03\x04"
def test_octet_string_encode(self):
s = OctetStringOneOctet()
s.s = b"\x00\x01\x02\x03\x04"
assert s.encode().tobytes() == b"\x10\x05\x00\x01\x02\x03\x04"
assert s.encode().tobytes() == b"\x15\x30\x00\x05\x00\x01\x02\x03\x04\x18"
@given(v=...)
@given(v=st.binary(max_size=16))
def test_roundtrip(self, v: bytes):
s = OctetStringOneOctet()
s.s = v
buffer = s.encode().tobytes()
s2, _ = OctetStringOneOctet.decode(0x15, buffer)
s2, _ = OctetStringOneOctet.decode(0x15, buffer[1:])
assert s2.s == s.s
assert str(s2) == str(s)
@ -289,24 +294,24 @@ class TestOctetString:
class Null(tlv.Structure):
n = tlv.BoolMember(None, nullable=True)
n = tlv.BoolMember(0, nullable=True)
class NotNull(tlv.Structure):
n = tlv.BoolMember(None, nullable=True)
b = tlv.BoolMember(None)
n = tlv.BoolMember(0, nullable=True)
b = tlv.BoolMember(1)
class TestNull:
def test_null_decode(self):
s, _ = Null.decode(0x15, b"\x14")
s, _ = Null.decode(0x15, b"\x34\x00\x18")
assert str(s) == "{\n n = null\n}"
assert s.n is None
def test_null_encode(self):
s = Null()
s.n = None
assert s.encode().tobytes() == b"\x14"
assert s.encode().tobytes() == b"\x15\x34\x00\x18"
def test_nullable(self):
s = NotNull()
@ -338,64 +343,64 @@ class TestNull:
# Double precision floating point negative infinity 0b 00 00 00 00 00 00 f0 ff
# (-∞)
class FloatSingle(tlv.Structure):
f = tlv.FloatMember(None)
f = tlv.FloatMember(0)
class FloatDouble(tlv.Structure):
f = tlv.FloatMember(None, octets=8)
f = tlv.FloatMember(0, octets=8)
class TestFloatSingle:
def test_precision_float_0_0_decode(self):
s, _ = FloatSingle.decode(0x15, b"\x0a\x00\x00\x00\x00")
s, _ = FloatSingle.decode(0x15, b"\x2a\x00\x00\x00\x00\x00\x18")
assert str(s) == "{\n f = 0.0\n}"
assert s.f == 0.0
def test_precision_float_1_3_decode(self):
s, _ = FloatSingle.decode(0x15, b"\x0a\xab\xaa\xaa\x3e")
s, _ = FloatSingle.decode(0x15, b"\x2a\x00\xab\xaa\xaa\x3e\x18")
# assert str(s) == "{\n f = 0.3333333432674408\n}"
f = s.f
assert math.isclose(f, 1.0 / 3.0, rel_tol=1e-06)
def test_precision_float_17_9_decode(self):
s, _ = FloatSingle.decode(0x15, b"\x0a\x33\x33\x8f\x41")
s, _ = FloatSingle.decode(0x15, b"\x2a\x00\x33\x33\x8f\x41\x18")
assert str(s) == "{\n f = 17.899999618530273\n}"
assert math.isclose(s.f, 17.9, rel_tol=1e-06)
def test_precision_float_infinity_decode(self):
s, _ = FloatSingle.decode(0x15, b"\x0a\x00\x00\x80\x7f")
s, _ = FloatSingle.decode(0x15, b"\x2a\x00\x00\x00\x80\x7f\x18")
assert str(s) == "{\n f = inf\n}"
assert math.isinf(s.f)
def test_precision_float_negative_infinity_decode(self):
s, _ = FloatSingle.decode(0x15, b"\x0a\x00\x00\x80\xff")
s, _ = FloatSingle.decode(0x15, b"\x2a\x00\x00\x00\x80\xff\x18")
assert str(s) == "{\n f = -inf\n}"
assert math.isinf(s.f)
def test_precision_float_0_0_encode(self):
s = FloatSingle()
s.f = 0.0
assert s.encode().tobytes() == b"\x0a\x00\x00\x00\x00"
assert s.encode().tobytes() == b"\x15\x2a\x00\x00\x00\x00\x00\x18"
def test_precision_float_1_3_encode(self):
s = FloatSingle()
s.f = 1.0 / 3.0
assert s.encode().tobytes() == b"\x0a\xab\xaa\xaa\x3e"
assert s.encode().tobytes() == b"\x15\x2a\x00\xab\xaa\xaa\x3e\x18"
def test_precision_float_17_9_encode(self):
s = FloatSingle()
s.f = 17.9
assert s.encode().tobytes() == b"\x0a\x33\x33\x8f\x41"
assert s.encode().tobytes() == b"\x15\x2a\x00\x33\x33\x8f\x41\x18"
def test_precision_float_infinity_encode(self):
s = FloatSingle()
s.f = float("inf")
assert s.encode().tobytes() == b"\x0a\x00\x00\x80\x7f"
assert s.encode().tobytes() == b"\x15\x2a\x00\x00\x00\x80\x7f\x18"
def test_precision_float_negative_infinity_encode(self):
s = FloatSingle()
s.f = float("-inf")
assert s.encode().tobytes() == b"\x0a\x00\x00\x80\xff"
assert s.encode().tobytes() == b"\x15\x2a\x00\x00\x00\x80\xff\x18"
@given(v=...)
def test_roundtrip_double(self, v: float):
@ -403,7 +408,7 @@ class TestFloatSingle:
s.f = v
buffer = s.encode().tobytes()
s2, _ = FloatDouble.decode(0x15, buffer)
s2, _ = FloatDouble.decode(0x15, buffer[1:])
assert (
(math.isnan(s.f) and math.isnan(s2.f))
@ -424,8 +429,9 @@ class TestFloatSingle:
s = FloatSingle()
s.f = v
buffer = s.encode().tobytes()
print("Buffer", buffer.hex(" "))
s2, _ = FloatSingle.decode(0x15, buffer)
s2, _ = FloatSingle.decode(0x15, buffer[1:])
assert (math.isnan(s.f) and math.isnan(s2.f)) or math.isclose(
s2.f, s.f, rel_tol=1e-7, abs_tol=1e-9
@ -434,55 +440,65 @@ class TestFloatSingle:
class TestFloatDouble:
def test_precision_float_0_0_decode(self):
s, _ = FloatDouble.decode(0x15, b"\x0b\x00\x00\x00\x00\x00\x00\x00\x00")
s, _ = FloatDouble.decode(0x15, b"\x2b\x00\x00\x00\x00\x00\x00\x00\x00\x00")
assert str(s) == "{\n f = 0.0\n}"
assert s.f == 0.0
def test_precision_float_1_3_decode(self):
s, _ = FloatDouble.decode(0x15, b"\x0b\x55\x55\x55\x55\x55\x55\xd5\x3f")
s, _ = FloatDouble.decode(0x15, b"\x2b\x00\x55\x55\x55\x55\x55\x55\xd5\x3f")
# assert str(s) == "{\n f = 0.3333333333333333\n}"
f = s.f
assert math.isclose(f, 1.0 / 3.0, rel_tol=1e-06)
def test_precision_float_17_9_decode(self):
s, _ = FloatDouble.decode(0x15, b"\x0b\x66\x66\x66\x66\x66\xe6\x31\x40")
s, _ = FloatDouble.decode(0x15, b"\x2b\x00\x66\x66\x66\x66\x66\xe6\x31\x40")
assert str(s) == "{\n f = 17.9\n}"
assert math.isclose(s.f, 17.9, rel_tol=1e-06)
def test_precision_float_infinity_decode(self):
s, _ = FloatDouble.decode(0x15, b"\x0b\x00\x00\x00\x00\x00\x00\xf0\x7f")
s, _ = FloatDouble.decode(0x15, b"\x2b\x00\x00\x00\x00\x00\x00\x00\xf0\x7f")
assert str(s) == "{\n f = inf\n}"
assert math.isinf(s.f)
def test_precision_float_negative_infinity_decode(self):
s, _ = FloatDouble.decode(0x15, b"\x0b\x00\x00\x00\x00\x00\x00\xf0\xff")
s, _ = FloatDouble.decode(0x15, b"\x2b\x00\x00\x00\x00\x00\x00\x00\xf0\xff")
assert str(s) == "{\n f = -inf\n}"
assert math.isinf(s.f)
def test_precision_float_0_0_encode(self):
s = FloatDouble()
s.f = 0.0
assert s.encode().tobytes() == b"\x0b\x00\x00\x00\x00\x00\x00\x00\x00"
assert (
s.encode().tobytes() == b"\x15\x2b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18"
)
def test_precision_float_1_3_encode(self):
s = FloatDouble()
s.f = 1.0 / 3.0
assert s.encode().tobytes() == b"\x0b\x55\x55\x55\x55\x55\x55\xd5\x3f"
assert (
s.encode().tobytes() == b"\x15\x2b\x00\x55\x55\x55\x55\x55\x55\xd5\x3f\x18"
)
def test_precision_float_17_9_encode(self):
s = FloatDouble()
s.f = 17.9
assert s.encode().tobytes() == b"\x0b\x66\x66\x66\x66\x66\xe6\x31\x40"
assert (
s.encode().tobytes() == b"\x15\x2b\x00\x66\x66\x66\x66\x66\xe6\x31\x40\x18"
)
def test_precision_float_infinity_encode(self):
s = FloatDouble()
s.f = float("inf")
assert s.encode().tobytes() == b"\x0b\x00\x00\x00\x00\x00\x00\xf0\x7f"
assert (
s.encode().tobytes() == b"\x15\x2b\x00\x00\x00\x00\x00\x00\x00\xf0\x7f\x18"
)
def test_precision_float_negative_infinity_encode(self):
s = FloatDouble()
s.f = float("-inf")
assert s.encode().tobytes() == b"\x0b\x00\x00\x00\x00\x00\x00\xf0\xff"
assert (
s.encode().tobytes() == b"\x15\x2b\x00\x00\x00\x00\x00\x00\x00\xf0\xff\x18"
)
@given(v=...)
def test_roundtrip(self, v: float):
@ -490,7 +506,7 @@ class TestFloatDouble:
s.f = v
buffer = s.encode().tobytes()
s2, _ = FloatDouble.decode(0x15, buffer)
s2, _ = FloatDouble.decode(0x15, buffer[1:])
assert (
(math.isnan(s.f) and math.isnan(s2.f))
@ -506,12 +522,12 @@ class InnerStruct(tlv.Structure):
class OuterStruct(tlv.Structure):
s = tlv.StructMember(None, InnerStruct)
s = tlv.StructMember(0, InnerStruct)
class TestStruct:
def test_inner_struct_decode(self):
s, _ = OuterStruct.decode(0x15, b"\x15\x20\x00\x2a\x20\x01\xef\x18")
s, _ = OuterStruct.decode(0x15, b"\x35\x00\x20\x00\x2a\x20\x01\xef\x18\x18")
assert_type(s, OuterStruct)
assert_type(s.s, InnerStruct)
assert_type(s.s.a, Optional[int])
@ -520,7 +536,7 @@ class TestStruct:
assert s.s.b == -17
def test_inner_struct_decode_empty(self):
s, _ = OuterStruct.decode(0x15, b"\x15\x18")
s, _ = OuterStruct.decode(0x15, b"\x35\x00\x18\x18")
assert str(s) == "{\n s = {\n \n }\n}"
assert s.s.a is None
assert s.s.b is None
@ -533,13 +549,13 @@ class TestStruct:
s.s = inner
assert (
s.encode().tobytes()
== b"\x15\x22\x00\x2a\x00\x00\x00\x22\x01\xef\xff\xff\xff\x18"
== b"\x15\x35\x00\x22\x00\x2a\x00\x00\x00\x22\x01\xef\xff\xff\xff\x18\x18"
)
def test_inner_struct_encode_empty(self):
s = OuterStruct()
s.s = InnerStruct()
assert s.encode().tobytes() == b"\x15\x18"
assert s.encode().tobytes() == b"\x15\x35\x00\x18\x18"
class FullyQualified(tlv.Structure):
@ -551,7 +567,7 @@ class TestFullyQualifiedTags:
def test_decode(self):
s, _ = FullyQualified.decode(
0x15,
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",
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\x18",
)
assert_type(s, FullyQualified)
assert_type(s.a, Optional[int])
@ -565,5 +581,5 @@ class TestFullyQualifiedTags:
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"
== b"\x15\xc2\xda\x0a\x00\x0f\x23\x01\x2a\x00\x00\x00\xe2\xda\x0a\x00\x0f\x45\x23\x01\x00\xef\xff\xff\xff\x18"
)