Skip to content

Commit

Permalink
Move adding end container into subclass. Struct requires a tag
Browse files Browse the repository at this point in the history
  • Loading branch information
tannewt committed Sep 23, 2024
1 parent 05745b1 commit 51ce818
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 127 deletions.
30 changes: 12 additions & 18 deletions circuitmatter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,8 +534,6 @@ def encode_into(self, buffer, cipher=None):
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
Expand All @@ -554,22 +552,18 @@ def encode_into(self, buffer, cipher=None):
# 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
Expand Down Expand Up @@ -896,7 +890,6 @@ def arm_fail_safe(
) -> data_model.GeneralCommissioningCluster.ArmFailSafeResponse:
response = data_model.GeneralCommissioningCluster.ArmFailSafeResponse()
response.ErrorCode = data_model.CommissioningErrorEnum.OK
print("respond", response)
return response


Expand Down Expand Up @@ -1001,14 +994,15 @@ def process_packets(self):

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):
Expand Down Expand Up @@ -1066,7 +1060,7 @@ def process_packet(self, address, data):

# 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(
Expand Down Expand Up @@ -1094,7 +1088,7 @@ def process_packet(self, address, data):
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,
Expand Down Expand Up @@ -1206,6 +1200,8 @@ def process_packet(self, address, data):
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:
Expand Down Expand Up @@ -1261,8 +1257,6 @@ def process_packet(self, address, data):
)
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
Expand Down
9 changes: 9 additions & 0 deletions circuitmatter/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ def recvfrom_into(self, buffer, nbytes=None):
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)


Expand Down
3 changes: 0 additions & 3 deletions circuitmatter/data_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,16 +153,13 @@ def _attributes(cls) -> Iterable[tuple[str, Attribute]]:
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
Expand Down
4 changes: 2 additions & 2 deletions circuitmatter/interaction_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
41 changes: 26 additions & 15 deletions circuitmatter/tlv.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def __init__(self):
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]]:
Expand All @@ -125,6 +125,9 @@ def _members_by_tag(cls) -> dict[int, tuple[str, Member]]:
cls._members_by_tag_cache = members
return members

def set_value(self, tag, value):
self.values[tag] = value


class Structure(Container):
def __str__(self):
Expand All @@ -144,13 +147,15 @@ def __str__(self):

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]:
Expand All @@ -168,15 +173,11 @@ def decode(cls, control_octet, buffer, offset=0, depth=0) -> tuple[dict, int]:
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):
Expand Down Expand Up @@ -262,7 +263,7 @@ def __set__(
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
Expand Down Expand Up @@ -544,11 +545,11 @@ def __init__(
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):
Expand Down Expand Up @@ -629,8 +630,7 @@ def encode_element_type(self, value):

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)
Expand Down Expand Up @@ -677,6 +677,8 @@ def encode_value_into(self, value, buffer: bytearray, offset: int) -> int:
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
Expand Down Expand Up @@ -726,7 +728,8 @@ def __str__(self):

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:
Expand All @@ -741,7 +744,8 @@ def encode_into(self, buffer: bytearray, offset: int = 0) -> int:
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):
Expand All @@ -760,6 +764,14 @@ def from_value(cls, value):
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)

Expand Down Expand Up @@ -801,8 +813,7 @@ def encode_element_type(self, value):

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)
Expand Down
34 changes: 17 additions & 17 deletions test_data/recorded_packets.jsonl
Original file line number Diff line number Diff line change
@@ -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="]
Loading

0 comments on commit 51ce818

Please sign in to comment.