Get through attestation

This commit is contained in:
Scott Shawcroft 2024-09-25 16:39:07 -07:00
parent 391fc0df27
commit 672c8643ea
No known key found for this signature in database
6 changed files with 334 additions and 59 deletions

View file

@ -5,11 +5,11 @@ import enum
import hashlib import hashlib
import pathlib import pathlib
import json import json
import os
import struct import struct
import time import time
import cryptography import cryptography
import ecdsa
from typing import Optional from typing import Optional
@ -18,6 +18,18 @@ from . import interaction_model
from . import session from . import session
from . import tlv from . import tlv
TEST_CERTS = pathlib.Path(
"/home/tannewt/repos/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/"
)
TEST_PAI_CERT_DER = TEST_CERTS / "Chip-Test-PAI-FFF1-8000-Cert.der"
TEST_PAI_CERT_PEM = TEST_CERTS / "Chip-Test-PAI-FFF1-8000-Cert.pem"
TEST_DAC_CERT_DER = TEST_CERTS / "Chip-Test-DAC-FFF1-8000-0000-Cert.der"
TEST_DAC_CERT_PEM = TEST_CERTS / "Chip-Test-DAC-FFF1-8000-0000-Cert.pem"
TEST_DAC_KEY_DER = TEST_CERTS / "Chip-Test-DAC-FFF1-8000-0000-Key.der"
TEST_DAC_KEY_PEM = TEST_CERTS / "Chip-Test-DAC-FFF1-8000-0000-Key.pem"
TEST_CD_CERT_DER = pathlib.Path("certification_declaration.der")
__version__ = "0.0.0" __version__ = "0.0.0"
# Section 4.11.2 # Section 4.11.2
@ -192,9 +204,9 @@ class MessageReceptionState:
class MessageCounter: class MessageCounter:
def __init__(self, starting_value=None): def __init__(self, starting_value=None, random_source=None):
if starting_value is None: if starting_value is None:
starting_value = os.urandom(4) starting_value = random_source.urandom(4)
starting_value = struct.unpack("<I", starting_value)[0] starting_value = struct.unpack("<I", starting_value)[0]
starting_value >>= 4 starting_value >>= 4
starting_value += 1 starting_value += 1
@ -305,7 +317,7 @@ class UnsecuredSessionContext:
class SecureSessionContext: class SecureSessionContext:
def __init__(self, socket, local_session_id): def __init__(self, random_source, socket, local_session_id):
self.session_type = None self.session_type = None
"""Records whether the session was established using CASE or PASE.""" """Records whether the session was established using CASE or PASE."""
self.session_role_initiator = False self.session_role_initiator = False
@ -320,7 +332,7 @@ class SecureSessionContext:
"""Encrypts data in messages sent from the session establishment responder to the initiator.""" """Encrypts data in messages sent from the session establishment responder to the initiator."""
self.shared_secret = None self.shared_secret = None
"""Computed during the CASE protocol execution and re-used when CASE session resumption is implemented.""" """Computed during the CASE protocol execution and re-used when CASE session resumption is implemented."""
self.local_message_counter = MessageCounter() self.local_message_counter = MessageCounter(random_source=random_source)
"""Secure Session Message Counter for outbound messages.""" """Secure Session Message Counter for outbound messages."""
self.message_reception_state = None self.message_reception_state = None
"""Provides tracking for the Secure Session Message Counter of the remote""" """Provides tracking for the Secure Session Message Counter of the remote"""
@ -736,7 +748,7 @@ class StatusReport:
class SessionManager: class SessionManager:
def __init__(self, socket): def __init__(self, random_source, socket):
persist_path = pathlib.Path("counters.json") persist_path = pathlib.Path("counters.json")
if persist_path.exists(): if persist_path.exists():
self.nonvolatile = json.loads(persist_path.read_text()) self.nonvolatile = json.loads(persist_path.read_text())
@ -745,17 +757,22 @@ class SessionManager:
self.nonvolatile["check_in_counter"] = None self.nonvolatile["check_in_counter"] = None
self.nonvolatile["group_encrypted_data_message_counter"] = None self.nonvolatile["group_encrypted_data_message_counter"] = None
self.nonvolatile["group_encrypted_control_message_counter"] = None self.nonvolatile["group_encrypted_control_message_counter"] = None
self.unencrypted_message_counter = MessageCounter() self.unencrypted_message_counter = MessageCounter(random_source=random_source)
self.group_encrypted_data_message_counter = MessageCounter( self.group_encrypted_data_message_counter = MessageCounter(
self.nonvolatile["group_encrypted_data_message_counter"] self.nonvolatile["group_encrypted_data_message_counter"],
random_source=random_source,
) )
self.group_encrypted_control_message_counter = MessageCounter( self.group_encrypted_control_message_counter = MessageCounter(
self.nonvolatile["group_encrypted_control_message_counter"] self.nonvolatile["group_encrypted_control_message_counter"],
random_source=random_source,
)
self.check_in_counter = MessageCounter(
self.nonvolatile["check_in_counter"], random_source=random_source
) )
self.check_in_counter = MessageCounter(self.nonvolatile["check_in_counter"])
self.unsecured_session_context = {} self.unsecured_session_context = {}
self.secure_session_contexts = ["reserved"] self.secure_session_contexts = ["reserved"]
self.socket = socket self.socket = socket
self.random = random_source
def _increment(self, value): def _increment(self, value):
return (value + 1) % 0xFFFFFFFF return (value + 1) % 0xFFFFFFFF
@ -836,7 +853,7 @@ class SessionManager:
session_id = self.secure_session_contexts.index(None) session_id = self.secure_session_contexts.index(None)
self.secure_session_contexts[session_id] = SecureSessionContext( self.secure_session_contexts[session_id] = SecureSessionContext(
self.socket, session_id self.random, self.socket, session_id
) )
return self.secure_session_contexts[session_id] return self.secure_session_contexts[session_id]
@ -887,41 +904,115 @@ class GeneralCommissioningCluster(data_model.GeneralCommissioningCluster):
self.basic_commissioning_info = basic_commissioning_info self.basic_commissioning_info = basic_commissioning_info
def arm_fail_safe( def arm_fail_safe(
self, args: data_model.GeneralCommissioningCluster.ArmFailSafe self, session, args: data_model.GeneralCommissioningCluster.ArmFailSafe
) -> data_model.GeneralCommissioningCluster.ArmFailSafeResponse: ) -> data_model.GeneralCommissioningCluster.ArmFailSafeResponse:
response = data_model.GeneralCommissioningCluster.ArmFailSafeResponse() response = data_model.GeneralCommissioningCluster.ArmFailSafeResponse()
response.ErrorCode = data_model.CommissioningErrorEnum.OK response.ErrorCode = data_model.CommissioningErrorEnum.OK
return response return response
def set_regulatory_config( def set_regulatory_config(
self, args: data_model.GeneralCommissioningCluster.SetRegulatoryConfig self, session, args: data_model.GeneralCommissioningCluster.SetRegulatoryConfig
) -> data_model.GeneralCommissioningCluster.SetRegulatoryConfigResponse: ) -> data_model.GeneralCommissioningCluster.SetRegulatoryConfigResponse:
response = data_model.GeneralCommissioningCluster.SetRegulatoryConfigResponse() response = data_model.GeneralCommissioningCluster.SetRegulatoryConfigResponse()
response.ErrorCode = data_model.CommissioningErrorEnum.OK response.ErrorCode = data_model.CommissioningErrorEnum.OK
return response return response
class AttestationElements(tlv.Structure):
certification_declaration = tlv.OctetStringMember(0x01, max_length=400)
attestation_nonce = tlv.OctetStringMember(0x02, max_length=32)
timestamp = tlv.IntMember(0x03, signed=False, octets=4)
firmware_information = tlv.OctetStringMember(0x04, max_length=16, optional=True)
"""Used for secure boot. We don't support it."""
class NOCSRElements(tlv.Structure):
csr = tlv.OctetStringMember(0x01, max_length=1024)
CSRNonce = tlv.OctetStringMember(0x02, max_length=32)
# Skip vendor reserved
class NodeOperationalCredentialsCluster(data_model.NodeOperationalCredentialsCluster): class NodeOperationalCredentialsCluster(data_model.NodeOperationalCredentialsCluster):
def __init__(self):
self.dac_key = ecdsa.keys.SigningKey.from_der(
TEST_DAC_KEY_DER.read_bytes(), hashfunc=hashlib.sha256
)
self.new_key_for_update = False
def certificate_chain_request( def certificate_chain_request(
self, args: data_model.NodeOperationalCredentialsCluster.CertificateChainRequest self,
session,
args: data_model.NodeOperationalCredentialsCluster.CertificateChainRequest,
) -> data_model.NodeOperationalCredentialsCluster.CertificateChainResponse: ) -> data_model.NodeOperationalCredentialsCluster.CertificateChainResponse:
response = ( response = (
data_model.NodeOperationalCredentialsCluster.CertificateChainResponse() data_model.NodeOperationalCredentialsCluster.CertificateChainResponse()
) )
if args.CertificateType == data_model.CertificateChainTypeEnum.PAI: if args.CertificateType == data_model.CertificateChainTypeEnum.PAI:
print("PAI") print("PAI")
response.Certificate = TEST_PAI_CERT_DER.read_bytes()
elif args.CertificateType == data_model.CertificateChainTypeEnum.DAC: elif args.CertificateType == data_model.CertificateChainTypeEnum.DAC:
print("DAC") print("DAC")
response.Certificate = b"" response.Certificate = TEST_DAC_CERT_DER.read_bytes()
return response return response
def attestation_request( def attestation_request(
self, args: data_model.NodeOperationalCredentialsCluster.AttestationRequest self,
session,
args: data_model.NodeOperationalCredentialsCluster.AttestationRequest,
) -> data_model.NodeOperationalCredentialsCluster.AttestationResponse: ) -> data_model.NodeOperationalCredentialsCluster.AttestationResponse:
print("attestation") print("attestation")
elements = AttestationElements()
elements.certification_declaration = TEST_CD_CERT_DER.read_bytes()
elements.attestation_nonce = args.AttestationNonce
elements.timestamp = int(time.time())
elements = elements.encode()
print("elements", len(elements), elements[:3].hex(" "))
print(
"challeng",
len(session.attestation_challenge),
session.attestation_challenge[:3].hex(" "),
)
attestation_tbs = elements.tobytes() + session.attestation_challenge
response = data_model.NodeOperationalCredentialsCluster.AttestationResponse() response = data_model.NodeOperationalCredentialsCluster.AttestationResponse()
response.AttestationElements = b"" response.AttestationElements = elements
response.AttestationSignature = b"" response.AttestationSignature = self.dac_key.sign_deterministic(
attestation_tbs,
hashfunc=hashlib.sha256,
sigencode=ecdsa.util.sigencode_string,
)
return response
def csr_request(
self, session, args: data_model.NodeOperationalCredentialsCluster.CsrRequest
) -> data_model.NodeOperationalCredentialsCluster.CsrResponse:
# Section 6.4.6.1
# CSR stands for Certificate Signing Request. A NOCSR is a Node Operational Certificate Signing Request
self.new_key_for_update = args.IsForUpdateNOC
# class CSRRequest(tlv.Structure):
# CSRNonce = tlv.OctetStringMember(0, 32)
# IsForUpdateNOC = tlv.BoolMember(1, optional=True, default=False)
# Generate a new key pair.
new_key_csr = b"TODO"
# Create a CSR to reply back with. Sign it with the new private key.
elements = NOCSRElements()
elements.csr = new_key_csr
elements.CSRNonce = args.CSRNonce
elements = elements.encode()
nocsr_tbs = elements.tobytes() + session.attestation_challenge
# class CSRResponse(tlv.Structure):
# NOCSRElements = tlv.OctetStringMember(0, RESP_MAX)
# AttestationSignature = tlv.OctetStringMember(1, 64)
response = data_model.NodeOperationalCredentialsCluster.CsrResponse()
response.NOCSRElements = elements
response.AttestationSignature = self.dac_key.sign_deterministic(
nocsr_tbs, hashfunc=hashlib.sha256, sigencode=ecdsa.util.sigencode_string
)
return response return response
@ -933,7 +1024,7 @@ class CircuitMatter:
random_source, random_source,
state_filename, state_filename,
vendor_id=0xFFF1, vendor_id=0xFFF1,
product_id=0, product_id=0x8000,
): ):
self.socketpool = socketpool self.socketpool = socketpool
self.mdns_server = mdns_server self.mdns_server = mdns_server
@ -963,7 +1054,7 @@ class CircuitMatter:
self.socket.bind((UDP_IP, self.UDP_PORT)) self.socket.bind((UDP_IP, self.UDP_PORT))
self.socket.setblocking(False) self.socket.setblocking(False)
self.manager = SessionManager(self.socket) self.manager = SessionManager(self.random, self.socket)
print(f"Listening on UDP port {self.UDP_PORT}") print(f"Listening on UDP port {self.UDP_PORT}")
@ -1039,10 +1130,10 @@ class CircuitMatter:
# report.AttributeStatus = astatus # report.AttributeStatus = astatus
return report return report
def invoke(self, cluster, path, fields, command_ref): def invoke(self, session, cluster, path, fields, command_ref):
print("invoke", path) print("invoke", path)
response = interaction_model.InvokeResponseIB() response = interaction_model.InvokeResponseIB()
cdata = cluster.invoke(path, fields) cdata = cluster.invoke(session, path, fields)
if cdata is None: if cdata is None:
cstatus = interaction_model.CommandStatusIB() cstatus = interaction_model.CommandStatusIB()
cstatus.CommandPath = path cstatus.CommandPath = path
@ -1216,6 +1307,9 @@ class CircuitMatter:
elif protocol_opcode == SecureProtocolOpcode.ICD_CHECK_IN: elif protocol_opcode == SecureProtocolOpcode.ICD_CHECK_IN:
print("Received ICD Check-in") print("Received ICD Check-in")
elif message.protocol_id == ProtocolId.INTERACTION_MODEL: elif message.protocol_id == ProtocolId.INTERACTION_MODEL:
secure_session_context = self.manager.secure_session_contexts[
message.session_id
]
if protocol_opcode == InteractionModelOpcode.READ_REQUEST: if protocol_opcode == InteractionModelOpcode.READ_REQUEST:
print("Received Read Request") print("Received Read Request")
read_request, _ = interaction_model.ReadRequestMessage.decode( read_request, _ = interaction_model.ReadRequestMessage.decode(
@ -1272,7 +1366,12 @@ class CircuitMatter:
cluster = self._endpoints[endpoint][path.Cluster] cluster = self._endpoints[endpoint][path.Cluster]
path.Endpoint = endpoint path.Endpoint = endpoint
invoke_responses.append( invoke_responses.append(
self.invoke(cluster, path, invoke.CommandFields) self.invoke(
secure_session_context,
cluster,
path,
invoke.CommandFields,
)
) )
else: else:
print(f"Cluster 0x{path.Cluster:02x} not found") print(f"Cluster 0x{path.Cluster:02x} not found")
@ -1281,6 +1380,7 @@ class CircuitMatter:
cluster = self._endpoints[path.Endpoint][path.Cluster] cluster = self._endpoints[path.Endpoint][path.Cluster]
invoke_responses.append( invoke_responses.append(
self.invoke( self.invoke(
secure_session_context,
cluster, cluster,
path, path,
invoke.CommandFields, invoke.CommandFields,

View file

@ -0,0 +1,128 @@
# This file should only be needed when generating certificates.
import hashlib
from . import tlv
from .data_model import Enum8
import ecdsa
from ecdsa import der
import pathlib
class CertificationType(Enum8):
DEVELOPMENT_AND_TEST = 0
PROVISIONAL = 1
OFFICIAL = 2
class CertificationDeclaration(tlv.Structure):
format_version = tlv.IntMember(0, signed=False, octets=1)
vendor_id = tlv.IntMember(1, signed=False, octets=2)
product_id_array = tlv.ArrayMember(
2, tlv.IntMember(0, signed=False, octets=2), max_length=100
)
device_type_id = tlv.IntMember(3, signed=False, octets=4)
certificate_id = tlv.UTF8StringMember(4, max_length=19)
security_level = tlv.IntMember(5, signed=False, octets=1)
security_information = tlv.IntMember(6, signed=False, octets=2)
version_number = tlv.IntMember(7, signed=False, octets=2)
certification_type = tlv.EnumMember(8, CertificationType)
dac_origin_vendor_id = tlv.IntMember(9, signed=False, octets=2, optional=True)
dac_origin_product_id = tlv.IntMember(10, signed=False, octets=2, optional=True)
authorized_paa_list = tlv.ArrayMember(
11, tlv.OctetStringMember(None, max_length=20), optional=True, max_length=10
)
def encode_set(*encoded_pieces):
total_len = sum([len(p) for p in encoded_pieces])
return b"\x31" + der.encode_length(total_len) + b"".join(encoded_pieces)
def generate_certificates(
vendor_id=0xFFF1, product_id=0x8000, device_type=22, prefix=None
):
declaration = CertificationDeclaration()
declaration.format_version = 1 # Always 1
declaration.vendor_id = vendor_id
declaration.product_id_array = [product_id]
declaration.device_type_id = 0x1234 # device_type
declaration.certificate_id = "ZIG20141ZB330001-24" # "CSA00000SWC00000-00"
declaration.security_level = 0 # Always 0
declaration.security_information = 0 # Always 0
declaration.version_number = 0x2694 # 1 # Always 1
declaration.certification_type = CertificationType.DEVELOPMENT_AND_TEST
declaration = declaration.encode()
for i in range(0, len(declaration), 16):
print(f"{i:08x}", declaration[i : i + 16].hex(" "))
# From: https://github.com/project-chip/matter.js/blob/main/packages/protocol/src/certificate/CertificationDeclarationManager.ts
# NIST256p is the same as secp256r1
private_key = ecdsa.keys.SigningKey.from_string(
b"\xae\xf3\x48\x41\x16\xe9\x48\x1e\xc5\x7b\xe0\x47\x2d\xf4\x1b\xf4\x99\x06\x4e\x50\x24\xad\x86\x9e\xca\x5e\x88\x98\x02\xd4\x80\x75",
curve=ecdsa.curves.NIST256p,
hashfunc=hashlib.sha256,
)
print(private_key.to_string().hex().upper())
subject_key_identifier = b"\x62\xfa\x82\x33\x59\xac\xfa\xa9\x96\x3e\x1c\xfa\x14\x0a\xdd\xf5\x04\xf3\x71\x60"
signature = private_key.sign_deterministic(
declaration,
hashfunc=hashlib.sha256,
sigencode=ecdsa.util.sigencode_der_canonize,
)
print("signature", signature.hex(" "))
certification_declaration = []
# version
certification_declaration.append(der.encode_integer(3))
# Digest algorithm
certification_declaration.append(
encode_set(der.encode_sequence(der.encode_oid(2, 16, 840, 1, 101, 3, 4, 2, 1)))
)
# encap content info
encap_content_info = []
# content type
encap_content_info.append(der.encode_oid(1, 2, 840, 113549, 1, 7, 1))
# content
encap_content_info.append(
der.encode_constructed(0, der.encode_octet_string(declaration))
)
certification_declaration.append(der.encode_sequence(*encap_content_info))
signer_info = []
# version
signer_info.append(der.encode_integer(3))
# subject key identifier
signer_info.append(
b"\x80"
+ der.encode_length(len(subject_key_identifier))
+ subject_key_identifier
)
# digest algorithm
signer_info.append(
der.encode_sequence(der.encode_oid(2, 16, 840, 1, 101, 3, 4, 2, 1))
)
# signature algorithm
signer_info.append(der.encode_sequence(der.encode_oid(1, 2, 840, 10045, 4, 3, 2)))
# signature
signer_info.append(der.encode_octet_string(signature))
certification_declaration.append(encode_set(der.encode_sequence(*signer_info)))
signed_data = []
signed_data.append(der.encode_oid(1, 2, 840, 113549, 1, 7, 2))
cd = der.encode_sequence(*certification_declaration)
signed_data.append(der.encode_constructed(0, cd))
cms_signed = der.encode_sequence(*signed_data)
return cms_signed
if __name__ == "__main__":
cd = generate_certificates()
pathlib.Path("certification_declaration.der").write_bytes(cd)
for i in range(0, len(cd), 16):
print(f"{i:08x}", cd[i : i + 16].hex(" "))
print(cd.hex(" "))

View file

@ -172,7 +172,9 @@ class Cluster:
if not field_name.startswith("_") and isinstance(descriptor, Command): if not field_name.startswith("_") and isinstance(descriptor, Command):
yield field_name, descriptor yield field_name, descriptor
def invoke(self, path, fields) -> Optional[interaction_model.CommandDataIB]: def invoke(
self, session, path, fields
) -> Optional[interaction_model.CommandDataIB]:
found = False found = False
for field_name, descriptor in self._commands(): for field_name, descriptor in self._commands():
if descriptor.command_id != path.Command: if descriptor.command_id != path.Command:
@ -183,7 +185,7 @@ class Cluster:
print(arg) print(arg)
command = getattr(self, field_name) command = getattr(self, field_name)
if callable(command): if callable(command):
result = command(arg) result = command(session, arg)
else: else:
print(field_name, "not implemented") print(field_name, "not implemented")
return None return None
@ -458,7 +460,7 @@ class NodeOperationalCredentialsCluster(Cluster):
IsForUpdateNOC = tlv.BoolMember(1, optional=True, default=False) IsForUpdateNOC = tlv.BoolMember(1, optional=True, default=False)
class CSRResponse(tlv.Structure): class CSRResponse(tlv.Structure):
CSR = tlv.OctetStringMember(0, RESP_MAX) NOCSRElements = tlv.OctetStringMember(0, RESP_MAX)
AttestationSignature = tlv.OctetStringMember(1, 64) AttestationSignature = tlv.OctetStringMember(1, 64)
class AddNOC(tlv.Structure): class AddNOC(tlv.Structure):

View file

@ -241,7 +241,8 @@ def compute_session_keys(Ke, secure_session_context):
tag_length=session.CRYPTO_AEAD_MIC_LENGTH_BYTES, tag_length=session.CRYPTO_AEAD_MIC_LENGTH_BYTES,
) )
secure_session_context.attestation_challenge = keys[ secure_session_context.attestation_challenge = keys[
2 * session.CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES : 2 * session.CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES : 3
* session.CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES
] ]

View file

@ -434,11 +434,43 @@ class NumberMember(Member[_NT, _OPT, _NULLABLE], Generic[_NT, _OPT, _NULLABLE]):
return f"{value}{unsigned}" return f"{value}{unsigned}"
def encode_element_type(self, value): def encode_element_type(self, value):
# We don't adjust our encoding based on value size. We always use the bytes needed for the if self.integer:
# format. bit_length = value.bit_length()
if self.signed:
type = ElementType.SIGNED_INT
else:
type = ElementType.UNSIGNED_INT
length = 0 # in power of two
if bit_length <= 8:
length = 0
elif bit_length <= 16:
length = 1
elif bit_length <= 32:
length = 2
else:
length = 3
return type | length
return self._element_type return self._element_type
def encode_value_into(self, value, buffer, offset) -> int: def encode_value_into(self, value, buffer, offset) -> int:
if self.integer:
bit_length = value.bit_length()
format_string = None
if bit_length <= 8:
format_string = "<b" if self.signed else "<B"
length = 1
elif bit_length <= 16:
format_string = "<h" if self.signed else "<H"
length = 2
elif bit_length <= 32:
format_string = "<i" if self.signed else "<I"
length = 4
else:
format_string = "<q" if self.signed else "<Q"
length = 8
struct.pack_into(format_string, buffer, offset, value)
return offset + length
# Float
struct.pack_into(self.format, buffer, offset, value) struct.pack_into(self.format, buffer, offset, value)
return offset + self.max_value_length return offset + self.max_value_length
@ -640,14 +672,16 @@ class ArrayMember(Member[_TLVStruct, _OPT, _NULLABLE]):
def __init__( def __init__(
self, self,
tag, tag,
substruct_class: Type[_TLVStruct], substruct_class: Type[_TLVStruct, Member],
*, *,
max_length: Optional[int] = None,
optional: _OPT = False, optional: _OPT = False,
nullable: _NULLABLE = False, nullable: _NULLABLE = False,
**kwargs, **kwargs,
): ):
self.substruct_class = substruct_class self.substruct_class = substruct_class
self.max_value_length = 1280 self.max_value_length = 1280
self.max_items = max_length
super().__init__(tag, optional=optional, nullable=nullable, **kwargs) super().__init__(tag, optional=optional, nullable=nullable, **kwargs)
@staticmethod @staticmethod
@ -677,8 +711,11 @@ class ArrayMember(Member[_TLVStruct, _OPT, _NULLABLE]):
buffer[offset] = ElementType.STRUCTURE buffer[offset] = ElementType.STRUCTURE
elif isinstance(v, List): elif isinstance(v, List):
buffer[offset] = ElementType.LIST buffer[offset] = ElementType.LIST
else: elif isinstance(self.substruct_class, Member):
raise NotImplementedError("Unknown type") buffer[offset] = self.substruct_class.encode_element_type(v)
print(offset, hex(buffer[offset]))
offset = self.substruct_class.encode_value_into(v, buffer, offset + 1)
continue
offset = v.encode_into(buffer, offset + 1) offset = v.encode_into(buffer, offset + 1)
buffer[offset] = ElementType.END_OF_CONTAINER buffer[offset] = ElementType.END_OF_CONTAINER
return offset + 1 return offset + 1

View file

@ -1,27 +1,34 @@
["urandom", 683495367840819, 8, "TVCaLwZCw7U="] ["urandom", 784666670915434, 4, "gTyCfg=="]
["receive", 683498821765516, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "BAAAAF8dUQHg9sDgbF2ERwUgjxgAABUwASASKWbFjMO2TX2TttCy5g/kYva9Xkq3bUgRNk2ZJLC+MCUCVLskAwAoBDUFJQH0ASUCLAElA6APJAQRJAULJgYAAAMBJAcBGBg="] ["urandom", 784666670932045, 4, "WEtNXg=="]
["urandom", 683498851412134, 32, "MXZ+qUCRDQzGtLd/BdEFclIVyeARZatikL292J53wec="] ["urandom", 784666670944279, 4, "Im0D9Q=="]
["send", 683498851505099, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AQAAAJ0RXQ7g9sDgbF2ERwIhjxgAAF8dUQEVMAEgEilmxYzDtk19k7bQsuYP5GL2vV5Kt21IETZNmSSwvjAwAiAxdn6pQJENDMa0t38F0QVyUhXJ4BFlq2KQvb3YnnfB5yUDAQA1BCYBECcAADACIObgj9CEx2MyPagRHuoX1OB32N8u1aKUpNKjb4b854YkGBg="] ["urandom", 784666670954137, 4, "q3+7MQ=="]
["receive", 683498857474653, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "BAAAAGAdUQHg9sDgbF2ERwUijxgAABUwAUEE20SFW+IKRC2BOynyHg8nHPTv3+LVnM+u4ETTCvbrvnTgkJQNNj/qtVQNZWLUPqJlGYTqUqMqQtXrIkE5M4Ih6Bg="] ["urandom", 784666670967192, 8, "GdHmWrzZGms="]
["randbelow", 683498857567458, 115792089210356248762697446949407573529996955224135760342422259061068512044369, 55163913805809046006520747928954323885679067894514874675221401915371371566658] ["receive", 784671745464646, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "BAAAANR1RwbAzDLm0ZLOIQUgmg0AABUwASD/PukDdOXeqJ1IBdpHgqsgOMm3wxKzsceqrEXbVN2XnyUCvzMkAwAoBDUFJQH0ASUCLAElA6APJAQRJAULJgYAAAMBJAcBGBg="]
["send", 683498872841814, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AQAAAJ4RXQ7g9sDgbF2ERwIjjxgAAGAdUQEVMAFBBAbpQBc797XYmoP1v/VvwWAkH1y/0bDLthCBm0x0+NeykQt9xIvt80/Bs/ho3rZp+4j+S19V7CvxDJBpB47VKPowAiBwLzDgwDToYO6xBjEMzXZ7MVUWXPf90Xa9lWnufbGeaxg="] ["urandom", 784671771475666, 32, "R9etlJW4WJ2hI+MXNURAUgqg0EAvyRTyMrcduh1jOSE="]
["receive", 683498873254503, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "BAAAAGEdUQHg9sDgbF2ERwUkjxgAABUwASB8b9Xw7GZRd3GHmsZJ3Nv29JDKXZbinVKGP2m8Qt61Xxg="] ["urandom", 784671771496185, 4, "SqSOEg=="]
["send", 683498873329966, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AQAAAJ8RXQ7g9sDgbF2ERwJAjxgAAGEdUQEAAAAAAAAAAA=="] ["send", 784671771593809, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AQAAAMoj6AfAzDLm0ZLOIQIhmg0AANR1RwYVMAEg/z7pA3Tl3qidSAXaR4KrIDjJt8MSs7HHqqxF21Tdl58wAiBH162UlbhYnaEj4xc1REBSCqDQQC/JFPIytx26HWM5ISQDATUEJQEQJzACIObgj9CEx2MyPagRHuoX1OB32N8u1aKUpNKjb4b854YkGBg="]
["receive", 683498873509204, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AAEAADB0XwokT96ZD3xGZKllpiUod7YhF1weUtaXchVJGbNRBI7DUPwBeY2uJgsA7Bor4qb+XsCPzxzdenS8hxQax5z7FCBOY6a5SSQi4mryj30rTt0SjrN6KdIknnTYQks8GXeMaz7VlOrdF2CplNRXF+AGxS1CXxiqr/kuhAw4WFE="] ["receive", 784671777601546, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "BAAAANV1RwbAzDLm0ZLOIQUimg0AABUwAUEEEqRKQ9IIhbFzmFRT5DujfzWbge9+63W++o8ir/oUfxBKkLElBbBaT+n96w7pMk6pxi/JfmJwouj3RjZYy4O4MRg="]
["send", 683498874159352, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AFS7AME8eQ7ekgBSkCMyEut7Gwn6MYQenzBIWdNgAio6brAJTddb2DqSjWPUS1NQ2Nh6wwipFd1KRLH/F3ruQ5/+dSXcoPlV4J8T0Jm8eqx0o6DayguwFAyu1N5yiZk5bl5GmxYlEFlUi0VPOMcPPzYnqNbsYIgOAO63XcfBHNJsA5vCNsaKnqAktWHtzXmXtZTvZgEHJGJD8LIhZ6lM8wd1Ui3zFlB4GCG7dWjgn13z+TSbgHHHyt1dnj7a/uNxMwrWrQgSnkL/tzrBL0xSwAiVuwtBZpGdab85icqUkQeL6OZdYk21Oz9MVjHuGPrXq3npNX76/wgP3+rAeHc+r1Pt/qEd+KHbG4G+OHUTlIHygGth57dUDM5rum5AdW3/8YTx+tAncedBybrAlikzDh+SYpztfAQG"] ["randbelow", 784671777703077, 115792089210356248762697446949407573529996955224135760342422259061068512044369, 26649162366983277512998315276067044341880723378171385531045348526206345387847]
["receive", 683498875161114, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AAEAADF0XwrZIgz9knLzOrJkDSf9kwXRYOEpfY5UfzmZM7/qdko="] ["send", 784671787820468, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AQAAAMsj6AfAzDLm0ZLOIQIjmg0AANV1RwYVMAFBBMSdK7qIGwnaU3L8sov92kGKTqOhK56kBKAj55TYu3mZ9mLXCQrXPeZ/38c/YMxeIJ3EAfN/bOR6ec35cqwV0yAwAiAiuC1nE3Ubp++vlT/OYtqaqGoFdt/Usottde5xkgwMShg="]
["receive", 683498875255833, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AAEAADJ0Xwr/eCKktmaP1Uc1+Zp5aNJM9lEmVebne+lDI+DNiP2XImSTH8ARBUcbF9HLDK1/4dT55AGOts4iZ0ZNOZFVXAru45pVapgT8bN8uxPYgQ=="] ["receive", 784671788261229, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "BAAAANZ1RwbAzDLm0ZLOIQUkmg0AABUwASCsVcEv3Eh8OPfsmxLISxCVu7qwCbMN7+LL3jXTvSIkkRg="]
["send", 683498875486027, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AFS7AMI8eQ4P2pPGapfOg39gOtTZgx8gbquT4vchxWodZJvZeTFpbBUvommihSJ/3+D7sTifa9gz+fM9rdyWF1rd4Cx7EyM="] ["send", 784671788334538, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AQAAAMwj6AfAzDLm0ZLOIQJAmg0AANZ1RwYAAAAAAAAAAA=="]
["receive", 683498875778780, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AAEAADN0XwoUf68e6LmehobmSAeEE7+bJ9hopGfmhjlTakDo8ug="] ["receive", 784671788501062, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJPvywjQvaLnorG7I/AQoh4IJIg9SEd3owBKpVZd2KNZwHF5nlhQ8BhHzr/wpTFRUXtHo0kznWwiReoKTEnjb2e0lKSaGoAP5UADc8o+/IaJl5rvUneuijc9G9HIpcH4faBYMPQiOustDPLjUyDGobn/WYmPs29V1fle1Mizu5Y="]
["receive", 683498875892114, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AAEAADR0XwrxT37Olu9HyEQPuSGLVT3EjolLh8dB3hIb4YO/Q/vxakfp1Qs7D8fPhLUjI1jjph5HfD2ogeuQe1M="] ["send", 784671789179181, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AL8zAEbqKAGkyTFFEYXsJR7xYOlDV6nG3ES3MXEEYJHxjkfLv/u19GYfxnFvWDylvm+NbKnprXNebZiVdveov7t1fuaYovmxjv+nZ0RfQBkrabKLERGg5VKxeVoQxFYuIhqRj4iaXJAC3PQjmFA9Lrw6t3VNgcmG1yHa1fY5210krbhRPkxSPBFkbFxRDz+ige2uvRTzdANY1tI5ZhfiYYfC3LwaJsuuSuxhwIXbFoE/5qu8Zp/nAKGZme5PtrBhZcJDMOjKcsfK1Qgyn4kDTUiUMBSIBB2QGuoUf0db6R44fbcNlVvShYI="]
["send", 683498876146515, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AFS7AMM8eQ7tgujEMBfFaIlrN/xCl8Y4a2JH5UqEYxuRQeJHjMpRBm6WTBFnXXA5KuInASn40rPnidJjVMgnXJvaUeNbqwsM2tIu"] ["receive", 784671790132390, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJTvywgs9CHUeFUMwzTra9wAscaZOCzTg6O62BGTYcVAuHo="]
["receive", 683498876473603, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AAEAADV0XwqZojBtHTLIdbFe56V1UIE4WaooRa60ax+r6P31Jhxj1n075g/a3tNUOEfyBkmQJSgUH0MQxSz84D1YFtaZAw=="] ["receive", 784671790226888, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJXvywiLBDIrxesC/ZX4yeRwt9gndvFwbE8ip2lWnOCxh1u0yv3FKPUVW6+0Ks1uqo6aOJ9FLvGHeyidHLhA2wL1yXSikgvl6s8PDVoniSPXSg=="]
["send", 683498876708616, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AFS7AMQ8eQ64jIF0YadOHTE9nqad5MkK1by0uhBSadiviVXlc03a1WLgBkySoX6214T1XIwpMTxWrZfVaV17z+S3jL2okeSrmdDE"] ["send", 784671790449899, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AL8zAEfqKAGsoFJIDBcl+c0GSsGQHaX5ZSkQLeprcL862VIIkH7a35ywtoJCtDFEOEJISHst2s0HB8NBkg=="]
["receive", 683498877025054, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AAEAADZ0Xwo9WH3ur9aGpocl4AKUtZkAFiAK87+d4cQqMjAHsUt5kTWAtvekb1R0w+/kYsC5QwVklDk4lvQ="] ["receive", 784671794339509, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJbvywiVXApjl2O2rCpEp6tCMdZsxXJ8Fg9hz1I3wbfwk3Y="]
["send", 683498877240551, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AFS7AMU8eQ5vFsc6CWDDdoCuIls1wkgctrxuFrnBzZRCtLNK9Qt3HRb1C6vSRNJ0pGU27NuN6l2k9mEHCy0WNhERx8oxUP39"] ["receive", 784671794460868, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJfvywhxaDeamkfBt6JM14rzvD6822cZ8/A2oFzI/B4cjRr+rVN4Xo6BJ/4XSWtexshQvDyDgAfufoyfaBs="]
["receive", 683498877583799, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AAEAADd0XwrDKFXAh2biGtXh+eG+a37SSK7iHHclvobcxFZJT3TFpKlMw5y75O8h1yrySPNt+RKjWKqx13c="] ["send", 784671794721990, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AL8zAEjqKAEuoNxQBRLD1VXDDfxtRJk9II/gIrTGAtPcUPEC4bSY+8oIfHmiCkjjl3r9E3ESLv8LaAMp4ncUqorFrA=="]
["send", 683498877788546, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AFS7AMY8eQ6Is+Atiw2ABb7FL5iy1L6mM5g/k7414s1u2n/lLreAXNCJWDu+dhXcgeK8jXO+nGMOa+PDR8lpj4Lwrxi0MP/h"] ["receive", 784671795072291, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJjvywjZRYWm/kdLVbc/vtoKHymQDD1edYM1hHd3rU60iykpJB/Mr2hDRYYc9/zgJcqL/DlpqUaLEpXrJlEYN4teYA=="]
["receive", 683498878095526, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AAEAADh0Xwq1tLR8JSXMAh/4eBGVh9nvr6c7D5xraOqx3CTV4Usdv049V7JfXqJVAppEJvHMOXk+ZCUyObv60Q2+lgvaj+lGhA6W/b+5ztZVo8OJIcZ9Rll1FgrwTA=="] ["send", 784671795307716, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AL8zAEnqKAHlAQA5+esg7SoD5Jcsx413iVa2qW5mPR765RLosKM15P7MhfPU6VISFU4N6Wi7ejm4wQp2bIlXPItFdg=="]
["send", 683498878325730, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AFS7AMc8eQ4DBvpgfxkI8u77TH5UJBeFjkKJgW4W1SxT6Gfp2ZKvNNJOjEDmt/G/vWgzZlvcUq9jKCqQdTkn2MlT0aSwItbPLjJ1"] ["receive", 784671795554070, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJnvywhA/bnhfPrxP7AHJa6AbuGIQ2/rp1frP/aWI5lqQp2y33YL++YHMZPV9cZ/jaPoFEXltq1LaAw="]
["receive", 683498878665963, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AAEAADl0Xwoh7zyfoLawEVHK/jEfWmf+jEPDbe5cme3DeR/UBcQz1OhT4f8oHNQk6nPIt+Ss0mNHWS7Fapss/z0="] ["send", 784671795904602, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AL8zAErqKAGDdc+1QQxQeLDWjhtSBX88F+bdVCnUkQ07c2psS1hMwTbbF592r6YncrDegkKmGp4o8gEHccr5MYAccNlKo+6GHx6m3i7mUOd9uYAFQ/8HylwXl+/2JoKGJdBjHQn34p322B4v6i7EEdr6t59W/UYSKz8cUtKr11rNPbI/w8u3teOHPmfCAo3WhOkjYnbBodSeoGvCX5Bwi1WB7xtyVVvcWZfm8d66Elq6ECjuWJk2bRUzfSuem6+l+kvwE6SbCEsUswz8Ny6LeUy9F0mISWFSzHxFKteKlFwjUUtV+WNVIqKPOlDCas39NkoyCl9+jBKc2CYdLQ2ifbrizGoQ3Q7hckAmpcl2Di/O0p/GOmOt/x9HIUnrPI6i3NlLKQYL9TVXwI9bUwGq5pbxtmP/TeiU3y2CEX8gEjrKMV7PcY7IsSuO2WQIHh2x/AT/dKdPYqQ1Il6tgQnnXJZ/5C9JYQBiBL1xmUTNKt4vmUtuW3uW7B/r8/ICkiZGyBpyvN+l+7lfRJGAs/riIbgFbUPdKrQfOpxvKFaD1S1HzD2Dc92X5f37j4tgBs4xerod9bBbIWRH+3aARmSHsoL8vPay86HHUAj+z6z1sI/ZyOGkThf9UDcIr0d3hKw0TltboG90Rtycrs0pOJTmZ2kKE5epzhbV3urjnVAESeeIAC94NR+Z9PeNkUq5djHMw8qdHsFukwQW"]
["send", 683498878871692, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AFS7AMg8eQ5q8qqBOCg+y9xQcPIrn3pfJVHbKBSkMMzzOtyYKIMD/ZK4O0TLX8EqzIdOtmdET05jajUN5yWSG6rWZqQcaKiygGUc"] ["receive", 784671796157078, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJrvywgWRzA9jTjmdG0PHw4W34ADp1z14TYE0VAdelkqjoH6pCdbjbk+Woe4GAkq8nuV/8Dv1qg25Ws="]
["send", 784671796492802, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AL8zAEvqKAGSmI/Hf2oUaTztMFwWf1Wr7UzcVAw00fbDY5I5JZv0GekEl89ttbbqxiqX01LOvqmaegYEyB5IR9xLTN2qV1DHvfLfVxxeRTh/w1RnVondC9DU+Tg2NDL+d0modgJy+mtrvANUaIjKVy/80J5LjG//NMP1aw61S3dr+eqp7ClE/rIFU3qwC4Gkk9M78XnzyTQTJ7Iwke6CmPkFaSAZRkCmRi1ps1x58InVKXdzz2h9Fj/9meP+BfJMgbQ57+XCTYyBKZsaDNyRevHdBXdVfFvYOARiPXtg5GiZQtGBBLsf8Ucrguo0CMKSxwfMWK82OPFoquGE8Dg23tqU4aCRBYK3Me6P94cU7QS/Ikg6zJmofCFbsS5VRjVljxxBrLcRRjC+40DUTkb20LR58421FZR8CbPUSAFoe7tG1nrg75OL4/4o2BGr5wC2s1XDfwq9noxvNNum+vQnRsdmS95yypSO+9QZp5Fy7dRQjLK6JMRRwwqUyN4AhnV8uOLhsZ+nhgBBmiJ0ZSV4yM3OYme11iuLFvoeeM2nVNXobfFXfXqItOui7Fwtm69rMUyXV1jEpPuWIJV5IVWTYp2XrtqhLVOgBgP7xte0dlZOalCcBXGqFLEwz5TYiTMVBpZmTYHX+bPrXnfpfGHMuuKt/TNtYYxbPFxhb6EwpDT2TYZQweh5mAvP5y+69WA5wuKaPlioGR4IE8vIbs9Pe8waBNb9YHH3jjP9UTEQ"]
["receive", 784671796763933, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJvvywhXIkAFnDC5PCH22NnVWxSNyPNJq7RYhdW518I67TbrGubnXwiOpqJ/spqjy31aVfGAyTGZb0yuROzcPBU9HCDtTNZ2PdygZwCgERNPgsFKu1bzgEUc3g=="]
["send", 784671797646439, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AL8zAEzqKAGZCnaekdC8CSrDLJFqRBq+EUvWc9/SlYjbcCyf4hxYXajISL9LOh6m1PAngaYvF/xaGwiTNMRULgp7NUYK/QeqcbQWQz9mKgDcgwspqG0PzqerpemTMSNfrqDF4LTWG6xjHkHjgp8xDWSXgfFF7sjtwz77VhLUIAKdiY2rmI0YzL3kXaaNr9qsJb5D4tVK2hfInfKN8gokigctcoR03gNm3gcaGlH3X39VgiD/PXQSY0rFQgKEndL9ifhvCK32PZDJnW1NgL/kpHmgnoUdgy5widWEaxj1Al+sRqEEKxJgpf7GIbmF7Wg+tMFPAC/2hq6LGjg/lplXvqezDc0mfbS/x+3B+N8lYPd+n56TQ3WnKjEJX4FsZe6vT8QHUqt8eUFVvMsQvNuPcn7ITCeM80qXF4lEXgX3aOOn79jp3kjj8dWD6z1PsG+QSNI3Mm3QtUW8QIDwbvD5Io0bme/Ui9qfXJwweYIfzMfyrNLORlo0sKqGwVMX/nOwvYxXnvq3NXOzwGbx9o1b0WO8vqa4SRKeTXJWeswQ"]
["receive", 784671799529853, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJzvywh6llkCvigUZMBX8vDqbW1YJEOmGfMR7PGU9XI7EgIR+yHPqksJ0VLs3Qc86cFy2ZC7PbyTC1pbNBI8dAoYbl8BC9TfygOTYx+YA1pJc7LEhLaKih2DUQ=="]
["send", 784671799737184, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AL8zAE3qKAEjEkjttuN4wJXi4DEWpedfLtARZkkDdIDPRcIGT2CGQCDJF+ekuQwRMsHcQqYo9bmq3PKGnelEUw=="]
["receive", 784671799972889, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJ3vywjHE+kKkA4WvpHpLMVas8pnKJk/32kYtfqTDIpRK6GCg+WAoJYC29i5GwwqdR2YPbdFAhvn0UILgM0="]
["send", 784671800183566, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AL8zAE7qKAFiLxKjMFToUcwYnxa0BIcm5HfRzM7T//WcWiyZO3OPshiawMakEDQLqN8V3wVakown/waF8LP65Iujuw=="]