Start subscribe support and report chunking

This commit is contained in:
Scott Shawcroft 2024-10-10 16:26:49 -07:00
parent edb2996cbf
commit aa514ac3d9
No known key found for this signature in database
17 changed files with 652 additions and 260 deletions

View file

@ -6,12 +6,12 @@ import json
import time
from . import case
from .clusters import core
from . import data_model
from . import interaction_model
from .message import Message
from .protocol import InteractionModelOpcode, ProtocolId, SecureProtocolOpcode
from . import session
from .device_types.utility.root_node import RootNode
__version__ = "0.0.0"
@ -56,38 +56,17 @@ class CircuitMatter:
self._endpoints = {}
self._next_endpoint = 0
self._descriptor = data_model.DescriptorCluster()
self._descriptor.PartsList = []
self._descriptor.ServerList = []
self.add_cluster(0, self._descriptor)
basic_info = data_model.BasicInformationCluster()
basic_info.vendor_id = vendor_id
basic_info.product_id = product_id
basic_info.product_name = "CircuitMatter"
self.add_cluster(0, basic_info)
access_control = data_model.AccessControlCluster()
self.add_cluster(0, access_control)
group_keys = core.GroupKeyManagementCluster()
self.add_cluster(0, group_keys)
network_info = data_model.NetworkCommissioningCluster()
ethernet = data_model.NetworkCommissioningCluster.NetworkInfoStruct()
ethernet.NetworkID = "enp13s0".encode("utf-8")
ethernet.Connected = True
network_info.networks = [ethernet]
network_info.connect_max_time_seconds = 10
self.add_cluster(0, network_info)
general_commissioning = core.GeneralCommissioningCluster()
self.add_cluster(0, general_commissioning)
noc = core.NodeOperationalCredentialsCluster(
group_keys, random_source, self.mdns_server, self.UDP_PORT
self.root_node = RootNode(
random_source, self.mdns_server, self.UDP_PORT, vendor_id, product_id
)
self.add_cluster(0, noc)
self.add_device(self.root_node)
self.vendor_id = vendor_id
self.product_id = product_id
self.manager = session.SessionManager(self.random, self.socket, noc)
self.manager = session.SessionManager(
self.random, self.socket, self.root_node.noc
)
print(f"Listening on UDP port {self.UDP_PORT}")
@ -127,16 +106,28 @@ class CircuitMatter:
if endpoint not in self._endpoints:
self._endpoints[endpoint] = {}
if endpoint > 0:
self._descriptor.PartsList.append(endpoint)
self.root_node.descriptor.PartsList.append(endpoint)
self._next_endpoint = max(self._next_endpoint, endpoint + 1)
if endpoint == 0:
self._descriptor.ServerList.append(cluster.CLUSTER_ID)
self._endpoints[endpoint][cluster.CLUSTER_ID] = cluster
def add_device(self, device):
self._endpoints[self._next_endpoint] = {}
if self._next_endpoint > 0:
self._descriptor.PartsList.append(self._next_endpoint)
self.root_node.descriptor.PartsList.append(self._next_endpoint)
device.descriptor = data_model.DescriptorCluster()
device_type = data_model.DescriptorCluster.DeviceTypeStruct()
device_type.DeviceType = device.DEVICE_TYPE_ID
device_type.Revision = device.REVISION
device.descriptor.DeviceTypeList = [device_type]
device.descriptor.PartsList = [self._next_endpoint]
device.descriptor.ServerList = []
device.descriptor.ClientList = []
for server in device.servers:
device.descriptor.ServerList.append(server.CLUSTER_ID)
self.add_cluster(self._next_endpoint, server)
self.add_cluster(self._next_endpoint, device.descriptor)
self._next_endpoint += 1
def process_packets(self):
@ -203,6 +194,32 @@ class CircuitMatter:
return response
def read_attribute_path(self, path):
attribute_reports = []
if path.Endpoint is None:
endpoints = self._endpoints
else:
endpoints = [path.Endpoint]
# Wildcard so we get it from every endpoint.
for endpoint in endpoints:
if path.Cluster is None:
clusters = self._endpoints[endpoint].values()
else:
if path.Cluster not in self._endpoints[endpoint]:
print(
f"Cluster 0x{path.Cluster:02x} not found on endpoint {endpoint}"
)
continue
clusters = [self._endpoints[endpoint][path.Cluster]]
for cluster in clusters:
# TODO: The path object probably needs to be cloned. Otherwise we'll
# change the endpoint for all uses.
path.Endpoint = endpoint
path.Cluster = cluster.CLUSTER_ID
attribute_reports.extend(self.get_report(cluster, path))
return attribute_reports
def process_packet(self, address, data):
# Print the received data and the address of the sender
# This is section 4.7.2
@ -268,11 +285,8 @@ class CircuitMatter:
encoded = response.encode()
exchange.commissioning_hash.update(encoded)
exchange.send(
ProtocolId.SECURE_CHANNEL,
SecureProtocolOpcode.PBKDF_PARAM_RESPONSE,
response,
)
print(response)
exchange.send(response)
elif protocol_opcode == SecureProtocolOpcode.PBKDF_PARAM_RESPONSE:
print("Received PBKDF Parameter Response")
@ -293,9 +307,7 @@ class CircuitMatter:
)
exchange.cA = cA
exchange.Ke = Ke
exchange.send(
ProtocolId.SECURE_CHANNEL, SecureProtocolOpcode.PASE_PAKE2, pake2
)
exchange.send(pake2)
elif protocol_opcode == SecureProtocolOpcode.PASE_PAKE2:
print("Received PASE PAKE2")
raise NotImplementedError("Implement SPAKE2+ prover")
@ -316,11 +328,7 @@ class CircuitMatter:
error_status.protocol_code = (
session.SecureChannelProtocolCode.INVALID_PARAMETER
)
exchange.send(
ProtocolId.SECURE_CHANNEL,
SecureProtocolOpcode.STATUS_REPORT,
error_status,
)
exchange.send(error_status)
else:
exchange.session.session_timestamp = time.monotonic()
status_ok = session.StatusReport()
@ -329,11 +337,7 @@ class CircuitMatter:
status_ok.protocol_code = (
session.SecureChannelProtocolCode.SESSION_ESTABLISHMENT_SUCCESS
)
exchange.send(
ProtocolId.SECURE_CHANNEL,
SecureProtocolOpcode.STATUS_REPORT,
status_ok,
)
exchange.send(status_ok)
# Fully initialize the secure session context we'll use going
# forwards.
@ -349,16 +353,7 @@ class CircuitMatter:
)
response = self.manager.reply_to_sigma1(exchange, sigma1)
opcode = SecureProtocolOpcode.STATUS_REPORT
if isinstance(response, case.Sigma2Resume):
opcode = SecureProtocolOpcode.CASE_SIGMA2_RESUME
elif isinstance(response, case.Sigma2):
opcode = SecureProtocolOpcode.CASE_SIGMA2
exchange.send(
ProtocolId.SECURE_CHANNEL,
opcode,
response,
)
exchange.send(response)
elif protocol_opcode == SecureProtocolOpcode.CASE_SIGMA2:
print("Received CASE Sigma2")
elif protocol_opcode == SecureProtocolOpcode.CASE_SIGMA3:
@ -378,17 +373,14 @@ class CircuitMatter:
error_status.general_code = general_code
error_status.protocol_id = ProtocolId.SECURE_CHANNEL
error_status.protocol_code = protocol_code
exchange.send(
ProtocolId.SECURE_CHANNEL,
SecureProtocolOpcode.STATUS_REPORT,
error_status,
)
exchange.send(error_status)
elif protocol_opcode == SecureProtocolOpcode.CASE_SIGMA2_RESUME:
print("Received CASE Sigma2 Resume")
elif protocol_opcode == SecureProtocolOpcode.STATUS_REPORT:
print("Received Status Report")
report = session.StatusReport()
report.decode(message.application_payload)
print(report)
# Acknowledge the message because we have no further reply.
if message.exchange_flags & session.ExchangeFlags.R:
@ -410,37 +402,25 @@ class CircuitMatter:
)
attribute_reports = []
for path in read_request.AttributeRequests:
if path.Endpoint is None:
# Wildcard so we get it from every endpoint.
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
print(path.Endpoint)
print(path)
attribute_reports.extend(self.get_report(cluster, path))
else:
print(
f"Cluster 0x{path.Cluster:02x} not found on endpoint {endpoint}"
)
else:
if path.Cluster in self._endpoints[path.Endpoint]:
cluster = self._endpoints[path.Endpoint][path.Cluster]
attribute_reports.extend(self.get_report(cluster, path))
else:
print(f"Cluster 0x{path.Cluster:02x} not found at all")
# attribute_reports.append(
# self._build_attribute_error(path, interaction_model.StatusCode.UNSUPPORTED_CLUSTER)
# )
print("read", path)
attribute_reports.extend(self.read_attribute_path(path))
response = interaction_model.ReportDataMessage()
response.AttributeReports = attribute_reports
exchange.send(
ProtocolId.INTERACTION_MODEL,
InteractionModelOpcode.REPORT_DATA,
response,
exchange.send(response)
elif protocol_opcode == InteractionModelOpcode.WRITE_REQUEST:
print("Received Write Request")
write_request, _ = interaction_model.WriteRequestMessage.decode(
message.application_payload[0], message.application_payload[1:]
)
print(write_request)
write_responses = []
for request in write_request.WriteRequests:
path = request.Path
if path.Cluster in self._endpoints[path.Endpoint]:
cluster = self._endpoints[path.Endpoint][path.Cluster]
print(cluster)
write_responses.append(cluster.set_attribute(request))
elif protocol_opcode == InteractionModelOpcode.INVOKE_REQUEST:
print("Received Invoke Request")
invoke_request, _ = interaction_model.InvokeRequestMessage.decode(
@ -482,11 +462,8 @@ class CircuitMatter:
response = interaction_model.InvokeResponseMessage()
response.SuppressResponse = False
response.InvokeResponses = invoke_responses
exchange.send(
ProtocolId.INTERACTION_MODEL,
InteractionModelOpcode.INVOKE_RESPONSE,
response,
)
print("sending invoke response", response)
exchange.send(response)
elif protocol_opcode == InteractionModelOpcode.INVOKE_RESPONSE:
print("Received Invoke Response")
elif protocol_opcode == InteractionModelOpcode.SUBSCRIBE_REQUEST:
@ -494,21 +471,24 @@ class CircuitMatter:
subscribe_request, _ = interaction_model.SubscribeRequestMessage.decode(
message.application_payload[0], message.application_payload[1:]
)
error_status = session.StatusReport()
error_status.general_code = session.GeneralCode.UNSUPPORTED
error_status.protocol_id = ProtocolId.SECURE_CHANNEL
exchange.send(
ProtocolId.SECURE_CHANNEL,
SecureProtocolOpcode.STATUS_REPORT,
error_status,
)
print(subscribe_request)
attribute_reports = []
for path in subscribe_request.AttributeRequests:
attribute_reports.extend(self.read_attribute_path(path))
response = interaction_model.ReportDataMessage()
response.AttributeReports = attribute_reports
exchange.send(response)
final_response = interaction_model.SubscribeResponseMessage()
final_response.SubscriptionId = exchange.exchange_id
final_response.MaxInterval = subscribe_request.MaxIntervalCeiling
exchange.queue(final_response)
elif protocol_opcode == InteractionModelOpcode.STATUS_RESPONSE:
print("Received Status Response")
print(message)
status_response, _ = interaction_model.StatusResponseMessage.decode(
message.application_payload[0], message.application_payload[1:]
)
print(status_response)
print(
f"Received Status Response on {message.session_id}/{message.exchange_id} ack {message.acknowledged_message_counter}: {status_response.Status!r}"
)
else:
print(message)
print("application payload", message.application_payload.hex(" "))

View file

@ -10,7 +10,7 @@ import time
import circuitmatter as cm
from circuitmatter.device_types.lighting import extended_color
from circuitmatter.device_types.lighting import on_off
class ReplaySocket:
@ -221,7 +221,7 @@ class RecordingSocketPool:
return RecordingSocket(self.record_file, socket.socket(*args, **kwargs))
class NeoPixel(extended_color.ExtendedColorLight):
class NeoPixel(on_off.OnOffLight):
pass

View file

@ -1,9 +1,16 @@
from . import crypto
from . import protocol
from . import session
from . import tlv
class Sigma1(tlv.Structure):
class CASEMessage(tlv.Structure):
PROTOCOL_ID = protocol.ProtocolId.SECURE_CHANNEL
class Sigma1(CASEMessage):
PROTOCOL_OPCODE = protocol.SecureProtocolOpcode.CASE_SIGMA1
initiatorRandom = tlv.OctetStringMember(1, 32)
initiatorSessionId = tlv.IntMember(2, signed=False, octets=2)
destinationId = tlv.OctetStringMember(3, crypto.HASH_LEN_BYTES)
@ -31,7 +38,8 @@ class Sigma2TbeData(tlv.Structure):
resumptionID = tlv.OctetStringMember(4, 16)
class Sigma2(tlv.Structure):
class Sigma2(CASEMessage):
PROTOCOL_OPCODE = protocol.SecureProtocolOpcode.CASE_SIGMA2
responderRandom = tlv.OctetStringMember(1, 32)
responderSessionId = tlv.IntMember(2, signed=False, octets=2)
responderEphPubKey = tlv.OctetStringMember(3, crypto.PUBLIC_KEY_SIZE_BYTES)
@ -54,11 +62,13 @@ class Sigma3TbeData(tlv.Structure):
signature = tlv.OctetStringMember(3, crypto.GROUP_SIZE_BYTES * 2)
class Sigma3(tlv.Structure):
class Sigma3(CASEMessage):
PROTOCOL_OPCODE = protocol.SecureProtocolOpcode.CASE_SIGMA3
encrypted3 = tlv.OctetStringMember(1, Sigma3TbeData.max_length())
class Sigma2Resume(tlv.Structure):
class Sigma2Resume(CASEMessage):
PROTOCOL_OPCODE = protocol.SecureProtocolOpcode.CASE_SIGMA2_RESUME
resumptionID = tlv.OctetStringMember(1, 16)
sigma2ResumeMIC = tlv.OctetStringMember(2, 16)
responderSessionID = tlv.IntMember(3, signed=False, octets=2)

View file

@ -101,8 +101,9 @@ class NodeOperationalCredentialsCluster(data_model.NodeOperationalCredentialsClu
self.nocs = []
self.fabrics = []
self.commissioned_fabrics = 0
self.supported_fabrics = 10
self.commissioned_fabrics = 0
self.trusted_root_certificates = []
self.root_certs = []
self.compressed_fabric_ids = []
@ -305,6 +306,8 @@ class NodeOperationalCredentialsCluster(data_model.NodeOperationalCredentialsClu
self.noc_keys.append(self.pending_signing_key)
self.trusted_root_certificates.append(self.pending_root_cert)
self.root_certs.append(root_cert)
fabric_id = struct.pack(">Q", noc.subject.matter_fabric_id)
self.compressed_fabric_ids.append(
@ -327,6 +330,24 @@ class NodeOperationalCredentialsCluster(data_model.NodeOperationalCredentialsClu
response.StatusCode = data_model.NodeOperationalCertStatusEnum.OK
return response
def remove_fabric(
self,
session,
args: data_model.NodeOperationalCredentialsCluster.RemoveFabric,
) -> data_model.NodeOperationalCredentialsCluster.NOCResponse:
index = args.FabricIndex
self.commissioned_fabrics -= 1
self.noc_keys[index] = None
self.root_certs[index] = None
self.compressed_fabric_ids[index] = None
self.fabrics[index] = None
self.nocs[index] = None
response = data_model.NodeOperationalCredentialsCluster.NOCResponse()
response.StatusCode = data_model.NodeOperationalCertStatusEnum.OK
return response
class GroupKeyManagementCluster(data_model.GroupKeyManagementCluster):
def __init__(self):

View file

@ -0,0 +1,5 @@
from circuitmatter.data_model import Cluster
class Identify(Cluster):
CLUSTER_ID = 0x0003

View file

@ -0,0 +1,30 @@
from circuitmatter import data_model
from circuitmatter import tlv
class StartUpOnOffEnum(data_model.Enum8):
OFF = 0
ON = 1
TOGGLE = 2
class OnOff(data_model.Cluster):
CLUSTER_ID = 0x0006
OnOff = data_model.BoolAttribute(0x0000, default=False)
GlobalSceneControl = data_model.BoolAttribute(0x4000, default=True)
OnTime = data_model.NumberAttribute(0x4001, signed=False, bits=16, default=0)
OffWaitTime = data_model.NumberAttribute(0x4002, signed=False, bits=16, default=0)
StartUpOnOff = data_model.EnumAttribute(0x4003, StartUpOnOffEnum)
off = data_model.Command(0x00, None)
on = data_model.Command(0x01, None)
toggle = data_model.Command(0x02, None)
class OffWithEffect(tlv.Structure):
EffectIdentifier = tlv.EnumMember(0, 0)
EffectVariant = tlv.EnumMember(1, 0, default=0)
off_with_effect = data_model.Command(0x40, OffWithEffect)
on_with_recall_global_scene = data_model.Command(0x41, None)
on_with_timed_off = data_model.Command(0x42, None)

View file

@ -54,9 +54,25 @@ class List(tlv.ArrayMember):
class Attribute:
def __init__(self, _id, default=None):
def __init__(
self,
_id,
default=None,
optional=False,
feature=0,
C_changes_omitted=False,
F_fixed=False,
N_nonvolatile=False,
P_reportable=False,
Q_quieter_reporting=False,
S_scene=False,
X_nullable=False,
):
self.id = _id
self.default = default
self.optional = optional
self.feature = feature
self.nullable = X_nullable
def __get__(self, instance, cls):
v = instance._attribute_values.get(self.id, None)
@ -66,22 +82,28 @@ class Attribute:
def __set__(self, instance, value):
old_value = instance._attribute_values.get(self.id, None)
print("set old_value", old_value)
if old_value == value:
return
instance._attribute_values[self.id] = value
instance.data_version += 1
print("set new version", instance.data_version)
def encode(self, value):
def encode(self, value) -> bytes:
if value is None and self.nullable:
return b"\x14" # No tag, NULL
return self._encode(value)
def _encode(self, value):
raise NotImplementedError()
class NumberAttribute(Attribute):
def __init__(self, _id, *, signed, bits, default=None):
def __init__(self, _id, *, signed, bits, **kwargs):
self.signed = signed
self.bits = bits
self.id = _id
self.default = default
super().__init__(_id, default=default)
super().__init__(_id, **kwargs)
@staticmethod
def encode_number(value, *, signed=True) -> bytes:
@ -107,30 +129,35 @@ class NumberAttribute(Attribute):
return struct.pack(format_string, type | length, value)
def encode(self, value) -> bytes:
def _encode(self, value) -> bytes:
return NumberAttribute.encode_number(value, signed=self.signed)
class FeatureMap(NumberAttribute):
def __init__(self):
super().__init__(0xFFFC, signed=False, bits=32, default=0)
class EnumAttribute(NumberAttribute):
def __init__(self, _id, enum_type, default=None):
def __init__(self, _id, enum_type, **kwargs):
self.enum_type = enum_type
bits = 8 if issubclass(enum_type, Enum8) else 16
super().__init__(_id, signed=False, bits=bits, default=default)
super().__init__(_id, signed=False, bits=bits, **kwargs)
class ListAttribute(Attribute):
def __init__(self, _id, element_type):
def __init__(self, _id, element_type, **kwargs):
self.tlv_type = tlv.ArrayMember(None, element_type)
super().__init__(_id)
self._element_type = element_type
# Copy the default list so we don't accidentally share it with another
# cluster of the same type.
if "default" in kwargs and isinstance(kwargs["default"], list):
kwargs["default"] = list(kwargs["default"])
super().__init__(_id, **kwargs)
def encode(self, value) -> bytes:
def _encode(self, value) -> bytes:
return self.tlv_type.encode(value)
def element_from_value(self, value):
if issubclass(self._element_type, tlv.Container):
return self._element_type.from_value(value)
return value
class BoolAttribute(Attribute):
def encode(self, value) -> bytes:
@ -150,21 +177,24 @@ class StructAttribute(Attribute):
class OctetStringAttribute(Attribute):
def __init__(self, _id, min_length, max_length):
def __init__(self, _id, min_length, max_length, **kwargs):
self.min_length = min_length
self.max_length = max_length
super().__init__(_id)
self.member = tlv.OctetStringMember(None, max_length=max_length)
super().__init__(_id, **kwargs)
def encode(self, value):
return self.member.encode(value)
class UTF8StringAttribute(Attribute):
def __init__(self, _id, min_length=0, max_length=1200, default=None):
def __init__(self, _id, min_length=0, max_length=1200, **kwargs):
self.min_length = min_length
self.max_length = max_length
self.member = tlv.UTF8StringMember(None, max_length=max_length)
super().__init__(_id, default=default)
super().__init__(_id, **kwargs)
def encode(self, value):
print(repr(value))
return self.member.encode(value)
@ -173,7 +203,13 @@ class BitmapAttribute(Attribute):
class Command:
def __init__(self, command_id, request_type, response_id, response_type):
def __init__(
self,
command_id,
request_type,
response_id=None,
response_type=interaction_model.StatusCode,
):
self.command_id = command_id
self.request_type = request_type
self.response_id = response_id
@ -181,13 +217,16 @@ class Command:
class Cluster:
feature_map = FeatureMap()
feature_map = NumberAttribute(0xFFFC, signed=False, bits=32, default=0)
def __init__(self):
self._attribute_values = {}
# Use random since this isn't for security or replayability.
self.data_version = random.randint(0, 0xFFFFFFFF)
def __contains__(self, descriptor_id):
return descriptor_id in self._attribute_values
@classmethod
def _attributes(cls) -> Iterable[tuple[str, Attribute]]:
for superclass in cls.__mro__:
@ -202,8 +241,19 @@ class Cluster:
for field_name, descriptor in self._attributes():
if path.Attribute is not None and descriptor.id != path.Attribute:
continue
if descriptor.feature and not (self.feature_map & descriptor.feature):
continue
value = getattr(self, field_name)
print("reading", self, field_name, "->", value)
print(
"reading",
f"EP{path.Endpoint}",
type(self).__name__,
field_name,
"->",
value,
)
if value is None and descriptor.optional:
continue
data = interaction_model.AttributeDataIB()
data.DataVersion = 0
attribute_path = interaction_model.AttributePathIB()
@ -219,6 +269,43 @@ class Cluster:
print("not found", path.Attribute)
return replies
def set_attribute(self, attribute_data) -> interaction_model.AttributeStatusIB:
status_code = interaction_model.StatusCode.SUCCESS
for field_name, descriptor in self._attributes():
path = attribute_data.Path
if path.Attribute is not None and descriptor.id != path.Attribute:
continue
has_list_index = False
for entry in path:
if (
isinstance(entry, tuple)
and entry[0] == interaction_model.AttributePathIB.ListIndex
):
has_list_index = True
break
# value =
value = attribute_data.Data
print("writing", self, field_name, "->", value, "?", has_list_index)
if has_list_index:
if not isinstance(descriptor, ListAttribute):
status_code = interaction_model.StatusCode.UNSUPPORTED_WRITE
break
list_ = getattr(self, field_name)
if not isinstance(list_, list):
status_code = interaction_model.StatusCode.UNSUPPORTED_WRITE
break
list_.append(descriptor.element_from_value(value))
else:
setattr(self, field_name, value)
astatus = interaction_model.AttributeStatusIB()
astatus.Path = attribute_data.Path
status = interaction_model.StatusIB()
status.Status = status_code
status.ClusterStatus = 0
astatus.Status = status
return astatus
@classmethod
def _commands(cls) -> Iterable[tuple[str, Command]]:
for superclass in cls.__mro__:
@ -238,14 +325,28 @@ class Cluster:
command = getattr(self, field_name)
if callable(command):
if descriptor.request_type is not None:
arg = descriptor.request_type.from_value(fields)
result = command(session, arg)
try:
arg = descriptor.request_type.from_value(fields)
except ValueError:
return interaction_model.StatusCode.INVALID_COMMAND
try:
result = command(session, arg)
except Exception as e:
print(e)
return interaction_model.StatusCode.FAILURE
else:
result = command(session)
try:
result = command(session)
except Exception as e:
print(e)
return interaction_model.StatusCode.FAILURE
else:
print(field_name, "not implemented")
return None
if descriptor.response_type is not None:
return interaction_model.StatusCode.UNSUPPORTED_COMMAND
if descriptor.response_type is interaction_model.StatusCode:
if result is None:
return interaction_model.StatusCode.SUCCESS
return result
elif descriptor.response_type is not None:
cdata = interaction_model.CommandDataIB()
response_path = interaction_model.CommandPathIB()
response_path.Endpoint = path.Endpoint
@ -255,8 +356,7 @@ class Cluster:
if result:
cdata.CommandFields = descriptor.response_type.encode(result)
return cdata
else:
return result
return result
if not found:
print("not found", path.Command)
return None
@ -266,8 +366,8 @@ class DescriptorCluster(Cluster):
CLUSTER_ID = 0x001D
class DeviceTypeStruct(tlv.Structure):
devtype_id = tlv.IntMember(0, signed=False, octets=4)
revision = tlv.IntMember(1, signed=False, octets=2, minimum=1)
DeviceType = DeviceTypeId(0)
Revision = Uint16(1, minimum=1)
DeviceTypeList = ListAttribute(0x0000, DeviceTypeStruct)
ServerList = ListAttribute(0x0001, ClusterId())
@ -307,16 +407,16 @@ class AccessControlCluster(Cluster):
CLUSTER_ID = 0x001F
class AccessControlEntryStruct(tlv.Structure):
Privilege = tlv.EnumMember(0, AccessControlEntryPrivilegeEnum)
AuthMode = tlv.EnumMember(1, AccessControlEntryAuthModeEnum)
Subjects = List(2, Uint64())
Targets = List(3, AccessControlTargetStruct)
Privilege = tlv.EnumMember(1, AccessControlEntryPrivilegeEnum)
AuthMode = tlv.EnumMember(2, AccessControlEntryAuthModeEnum)
Subjects = List(3, Uint64())
Targets = List(4, AccessControlTargetStruct, nullable=True)
class AccessControlExtensionStruct(tlv.Structure):
Data = tlv.OctetStringMember(1, max_length=128)
ACL = ListAttribute(0x0000, AccessControlEntryStruct)
Extension = ListAttribute(0x0001, AccessControlExtensionStruct)
Extension = ListAttribute(0x0001, AccessControlExtensionStruct, optional=True)
SubjectsPerAccessControlEntry = NumberAttribute(
0x0002, signed=False, bits=16, default=4
)
@ -450,8 +550,8 @@ class GroupKeyManagementCluster(Cluster):
class KeySetWrite(tlv.Structure):
GroupKeySet = tlv.StructMember(0, GroupKeySetStruct)
group_key_map = ListAttribute(0, GroupKeyMapStruct)
group_table = ListAttribute(1, GroupInfoMapStruct)
group_key_map = ListAttribute(0, GroupKeyMapStruct, default=[])
group_table = ListAttribute(1, GroupInfoMapStruct, default=[])
max_groups_per_fabric = NumberAttribute(2, signed=False, bits=16, default=0)
max_group_keys_per_fabric = NumberAttribute(3, signed=False, bits=16, default=1)
@ -579,17 +679,43 @@ class NetworkCommissioningCluster(Cluster):
NetworkID = tlv.OctetStringMember(0, min_length=1, max_length=32)
Connected = tlv.BoolMember(1)
max_networks = NumberAttribute(0, signed=False, bits=8)
max_networks = NumberAttribute(0, signed=False, bits=8, default=1, F_fixed=True)
networks = ListAttribute(1, NetworkInfoStruct)
scan_max_time_seconds = NumberAttribute(2, signed=False, bits=8)
connect_max_time_seconds = NumberAttribute(3, signed=False, bits=8)
interface_enabled = BoolAttribute(4)
last_network_status = EnumAttribute(5, NetworkCommissioningStatus)
last_network_id = OctetStringAttribute(6, min_length=1, max_length=32)
last_connect_error_value = NumberAttribute(7, signed=True, bits=32)
supported_wifi_bands = ListAttribute(8, WifiBandEnum)
supported_thread_features = BitmapAttribute(9)
thread_version = NumberAttribute(10, signed=False, bits=16)
scan_max_time_seconds = NumberAttribute(
2,
signed=False,
bits=8,
feature=FeatureBitmap.WIFI_NETWORK_INTERFACE
| FeatureBitmap.THREAD_NETWORK_INTERFACE,
F_fixed=True,
)
connect_max_time_seconds = NumberAttribute(
3,
signed=False,
bits=8,
feature=FeatureBitmap.WIFI_NETWORK_INTERFACE
| FeatureBitmap.THREAD_NETWORK_INTERFACE,
F_fixed=True,
)
interface_enabled = BoolAttribute(4, default=True, N_nonvolatile=True)
last_network_status = EnumAttribute(5, NetworkCommissioningStatus, X_nullable=True)
last_network_id = OctetStringAttribute(
6, min_length=1, max_length=32, X_nullable=True
)
last_connect_error_value = NumberAttribute(7, signed=True, bits=32, X_nullable=True)
supported_wifi_bands = ListAttribute(
8, WifiBandEnum, feature=FeatureBitmap.WIFI_NETWORK_INTERFACE, F_fixed=True
)
supported_thread_features = BitmapAttribute(
9, feature=FeatureBitmap.THREAD_NETWORK_INTERFACE, F_fixed=True
)
thread_version = NumberAttribute(
10,
signed=False,
bits=16,
feature=FeatureBitmap.THREAD_NETWORK_INTERFACE,
F_fixed=True,
)
class CertificateChainTypeEnum(Enum8):
@ -683,11 +809,13 @@ class NodeOperationalCredentialsCluster(Cluster):
class AddTrustedRootCertificate(tlv.Structure):
RootCACertificate = tlv.OctetStringMember(0, 400)
nocs = ListAttribute(0, NOCStruct)
fabrics = ListAttribute(1, FabricDescriptorStruct)
supported_fabrics = NumberAttribute(2, signed=False, bits=8)
commissioned_fabrics = NumberAttribute(3, signed=False, bits=8)
trusted_root_certificates = ListAttribute(4, tlv.OctetStringMember(None, 400))
nocs = ListAttribute(0, NOCStruct, N_nonvolatile=True, C_changes_omitted=True)
fabrics = ListAttribute(1, FabricDescriptorStruct, N_nonvolatile=True)
supported_fabrics = NumberAttribute(2, signed=False, bits=8, F_fixed=True)
commissioned_fabrics = NumberAttribute(3, signed=False, bits=8, N_nonvolatile=True)
trusted_root_certificates = ListAttribute(
4, tlv.OctetStringMember(None, 400), N_nonvolatile=True, C_changes_omitted=True
)
current_fabric_index = NumberAttribute(5, signed=False, bits=8, default=0)
attestation_request = Command(0x00, AttestationRequest, 0x01, AttestationResponse)

View file

@ -1,2 +1,26 @@
from circuitmatter.clusters.general.identify import Identify
from circuitmatter.clusters.general.on_off import OnOff
class OnOffLight:
DEVICE_TYPE_ID = 0x0100
REVISION = 3
def __init__(self):
self.servers = []
self._identify = Identify()
self.servers.append(self._identify)
self._on_off = OnOff()
self._on_off.on = self.on
self._on_off.off = self.off
self.servers.append(self._on_off)
def on(self, session):
print("on!")
self._on_off.on_off = True
def off(self, session):
print("off!")
self._on_off.on_off = False

View file

@ -0,0 +1,41 @@
from circuitmatter import data_model
from circuitmatter.clusters import core
class RootNode:
DEVICE_TYPE_ID = 0x0011
REVISION = 2
def __init__(self, random_source, mdns_server, port, vendor_id, product_id):
self.servers = []
basic_info = data_model.BasicInformationCluster()
basic_info.vendor_id = vendor_id
basic_info.product_id = product_id
basic_info.product_name = "CircuitMatter"
self.servers.append(basic_info)
access_control = data_model.AccessControlCluster()
self.servers.append(access_control)
group_keys = core.GroupKeyManagementCluster()
self.servers.append(group_keys)
network_info = data_model.NetworkCommissioningCluster()
network_info.feature_map = (
data_model.NetworkCommissioningCluster.FeatureBitmap.WIFI_NETWORK_INTERFACE
)
ethernet = data_model.NetworkCommissioningCluster.NetworkInfoStruct()
ethernet.NetworkID = "enp13s0".encode("utf-8")
ethernet.Connected = True
network_info.networks = [ethernet]
network_info.connect_max_time_seconds = 10
network_info.last_network_status = (
data_model.NetworkCommissioningCluster.NetworkCommissioningStatus.SUCCESS
)
network_info.last_network_id = ethernet.NetworkID
self.servers.append(network_info)
general_commissioning = core.GeneralCommissioningCluster()
self.servers.append(general_commissioning)
self.noc = core.NodeOperationalCredentialsCluster(
group_keys, random_source, mdns_server, port
)
self.servers.append(self.noc)

View file

@ -2,6 +2,7 @@ import time
from .message import Message, ExchangeFlags, ProtocolId
from .protocol import SecureProtocolOpcode
from .interaction_model import ChunkedMessage
# Section 4.12.8
MRP_MAX_TRANSMISSIONS = 5
@ -38,10 +39,17 @@ class Exchange:
"""When to next resend the message that hasn't been acked"""
self.pending_retransmission = None
"""Message that we've attempted to send but hasn't been acked"""
self.pending_payloads = []
def send(
self, protocol_id, protocol_opcode, application_payload=None, reliable=True
self,
application_payload=None,
protocol_id=None,
protocol_opcode=None,
reliable=True,
):
if self.pending_retransmission is not None:
raise RuntimeError("Cannot send a message while waiting for an ack.")
message = Message()
message.exchange_flags = ExchangeFlags(0)
if self.initiator:
@ -55,26 +63,44 @@ class Exchange:
message.exchange_flags |= ExchangeFlags.R
self.pending_retransmission = message
message.source_node_id = self.session.local_node_id
if protocol_id is None:
protocol_id = application_payload.PROTOCOL_ID
message.protocol_id = protocol_id
if protocol_opcode is None:
protocol_opcode = application_payload.PROTOCOL_OPCODE
message.protocol_opcode = protocol_opcode
message.exchange_id = self.exchange_id
message.application_payload = application_payload
if isinstance(application_payload, ChunkedMessage):
chunk = memoryview(bytearray(1280))[:1200]
offset = application_payload.encode_into(chunk)
print(chunk[:offset].hex())
if application_payload.MoreChunkedMessages:
self.pending_payloads.insert(0, application_payload)
message.application_payload = chunk[:offset]
else:
message.application_payload = application_payload
self.session.send(message)
def send_standalone(self):
if self.pending_retransmission is not None:
self.session.send(self.pending_retransmission)
return
if self.pending_payloads:
self.send(self.pending_payloads.pop(0))
return
self.send(
ProtocolId.SECURE_CHANNEL,
SecureProtocolOpcode.MRP_STANDALONE_ACK,
None,
protocol_id=ProtocolId.SECURE_CHANNEL,
protocol_opcode=SecureProtocolOpcode.MRP_STANDALONE_ACK,
reliable=False,
)
def queue(self, payload):
self.pending_payloads.append(payload)
def receive(self, message) -> bool:
"""Process the message and return if the packet should be dropped."""
# Section 4.12.5.2.1
print(message)
if message.exchange_flags & ExchangeFlags.A:
if message.acknowledged_message_counter is None:
# Drop messages that are missing an acknowledgement counter.
@ -86,6 +112,7 @@ class Exchange:
):
# Drop messages that have the wrong acknowledgement counter.
return True
print("acknowledged", message.acknowledged_message_counter)
self.pending_retransmission = None
self.next_retransmission_time = None

View file

@ -1,5 +1,6 @@
import enum
from . import protocol
from . import tlv
@ -126,10 +127,37 @@ class AttributeReportIB(tlv.Structure):
class InteractionModelMessage(tlv.Structure):
PROTOCOL_ID = protocol.ProtocolId.INTERACTION_MODEL
InteractionModelRevision = tlv.IntMember(0xFF, signed=False, octets=1, default=11)
class ChunkedMessage(InteractionModelMessage):
"""Chunked messages take multiple encodes or decodes before they are complete."""
def encode_into(self, buffer: memoryview, offset: int = 0) -> int:
# Leave room for MoreChunkedMessages, SupressResponse, and InteractionModelRevision.
buffer[0] = tlv.ElementType.STRUCTURE
offset += 1
subbuffer = buffer[: -2 * 2 - 3 - 1]
del self.MoreChunkedMessages
for name, descriptor_class in self._members():
try:
print("encoding", name, "at offset", offset)
offset = descriptor_class.encode_into(self, subbuffer, offset)
except tlv.ArrayEncodingError as e:
print("splitting", name, f"[{e.index}:] offset {offset}")
offset = e.offset
tag = descriptor_class.tag
self.values[tag] = self.values[tag][e.index :]
self.MoreChunkedMessages = True
buffer[offset] = tlv.ElementType.END_OF_CONTAINER
return offset + 1
class ReadRequestMessage(InteractionModelMessage):
PROTOCOL_OPCODE = protocol.InteractionModelOpcode.READ_REQUEST
AttributeRequests = tlv.ArrayMember(0, AttributePathIB)
EventRequests = tlv.ArrayMember(1, EventPathIB)
EventFilters = tlv.ArrayMember(2, EventFilterIB)
@ -137,6 +165,21 @@ class ReadRequestMessage(InteractionModelMessage):
DataVersionFilters = tlv.ArrayMember(4, DataVersionFilterIB)
class WriteRequestMessage(ChunkedMessage):
PROTOCOL_OPCODE = protocol.InteractionModelOpcode.WRITE_REQUEST
SuppressResponse = tlv.BoolMember(0, optional=True)
TimedRequest = tlv.BoolMember(1)
WriteRequests = tlv.ArrayMember(2, AttributeDataIB)
MoreChunkedMessages = tlv.BoolMember(3, optional=True)
class WriteResponseMessage(InteractionModelMessage):
PROTOCOL_OPCODE = protocol.InteractionModelOpcode.WRITE_RESPONSE
WriteResponses = tlv.ArrayMember(0, AttributeStatusIB)
class EventStatusIB(tlv.Structure):
Path = tlv.StructMember(0, EventPathIB)
Status = tlv.StructMember(1, StatusIB)
@ -161,7 +204,9 @@ class EventReportIB(tlv.Structure):
EventData = tlv.StructMember(1, EventDataIB)
class ReportDataMessage(InteractionModelMessage):
class ReportDataMessage(ChunkedMessage):
PROTOCOL_OPCODE = protocol.InteractionModelOpcode.REPORT_DATA
SubscriptionId = tlv.IntMember(0, signed=False, octets=4, optional=True)
AttributeReports = tlv.ArrayMember(1, AttributeReportIB, optional=True)
EventReports = tlv.ArrayMember(2, EventReportIB, optional=True)
@ -193,18 +238,24 @@ class InvokeResponseIB(tlv.Structure):
class InvokeRequestMessage(InteractionModelMessage):
PROTOCOL_OPCODE = protocol.InteractionModelOpcode.INVOKE_REQUEST
SuppressResponse = tlv.BoolMember(0)
TimedRequest = tlv.BoolMember(1)
InvokeRequests = tlv.ArrayMember(2, CommandDataIB)
class InvokeResponseMessage(InteractionModelMessage):
PROTOCOL_OPCODE = protocol.InteractionModelOpcode.INVOKE_RESPONSE
SuppressResponse = tlv.BoolMember(0)
InvokeResponses = tlv.ArrayMember(1, InvokeResponseIB)
MoreChunkedMessages = tlv.BoolMember(2, optional=True)
class SubscribeRequestMessage(InteractionModelMessage):
PROTOCOL_OPCODE = protocol.InteractionModelOpcode.SUBSCRIBE_REQUEST
KeepSubscriptions = tlv.BoolMember(0)
MinIntervalFloor = tlv.IntMember(1, signed=False, octets=2)
MaxIntervalCeiling = tlv.IntMember(2, signed=False, octets=2)
@ -216,4 +267,13 @@ class SubscribeRequestMessage(InteractionModelMessage):
class StatusResponseMessage(InteractionModelMessage):
PROTOCOL_OPCODE = protocol.InteractionModelOpcode.STATUS_RESPONSE
Status = tlv.EnumMember(0, StatusCode)
class SubscribeResponseMessage(InteractionModelMessage):
PROTOCOL_OPCODE = protocol.InteractionModelOpcode.SUBSCRIBE_RESPONSE
SubscriptionId = tlv.IntMember(0, signed=False, octets=4)
MaxInterval = tlv.IntMember(2, signed=False, octets=2)

View file

@ -1,4 +1,5 @@
from . import crypto
from . import protocol
from . import tlv
from . import session
@ -11,6 +12,10 @@ from ecdsa.ellipticcurve import AbstractPoint, Point, PointJacobi
from ecdsa.curves import NIST256p
class PASEMessage(tlv.Structure):
PROTOCOL_ID = protocol.ProtocolId.SECURE_CHANNEL
# pbkdfparamreq-struct => STRUCTURE [ tag-order ]
# {
# initiatorRandom
@ -23,7 +28,9 @@ from ecdsa.curves import NIST256p
# [4] : BOOLEAN,
# initiatorSessionParams [5, optional] : session-parameter-struct
# }
class PBKDFParamRequest(tlv.Structure):
class PBKDFParamRequest(PASEMessage):
PROTOCOL_OPCODE = protocol.SecureProtocolOpcode.PBKDF_PARAM_REQUEST
initiatorRandom = tlv.OctetStringMember(1, 32)
initiatorSessionId = tlv.IntMember(2, signed=False, octets=2)
passcodeId = tlv.IntMember(3, signed=False, octets=2)
@ -55,7 +62,8 @@ class Crypto_PBKDFParameterSet(tlv.Structure):
# [4] : Crypto_PBKDFParameterSet,
# responderSessionParams [5, optional] : session-parameter-struct
# }
class PBKDFParamResponse(tlv.Structure):
class PBKDFParamResponse(PASEMessage):
PROTOCOL_OPCODE = protocol.SecureProtocolOpcode.PBKDF_PARAM_RESPONSE
initiatorRandom = tlv.OctetStringMember(1, 32)
responderRandom = tlv.OctetStringMember(2, 32)
responderSessionId = tlv.IntMember(3, signed=False, octets=2)
@ -65,16 +73,19 @@ class PBKDFParamResponse(tlv.Structure):
)
class PAKE1(tlv.Structure):
class PAKE1(PASEMessage):
PROTOCOL_OPCODE = protocol.SecureProtocolOpcode.PASE_PAKE1
pA = tlv.OctetStringMember(1, crypto.PUBLIC_KEY_SIZE_BYTES)
class PAKE2(tlv.Structure):
class PAKE2(PASEMessage):
PROTOCOL_OPCODE = protocol.SecureProtocolOpcode.PASE_PAKE2
pB = tlv.OctetStringMember(1, crypto.PUBLIC_KEY_SIZE_BYTES)
cB = tlv.OctetStringMember(2, crypto.HASH_LEN_BYTES)
class PAKE3(tlv.Structure):
class PAKE3(PASEMessage):
PROTOCOL_OPCODE = protocol.SecureProtocolOpcode.PASE_PAKE3
cA = tlv.OctetStringMember(1, crypto.HASH_LEN_BYTES)

View file

@ -107,6 +107,9 @@ class SecureChannelProtocolCode(enum.IntEnum):
class StatusReport:
PROTOCOL_ID = protocol.ProtocolId.SECURE_CHANNEL
PROTOCOL_OPCODE = protocol.SecureProtocolOpcode.STATUS_REPORT
def __init__(self):
self.clear()

View file

@ -131,6 +131,10 @@ class Container:
def set_value(self, tag, value):
self.values[tag] = value
def delete_value(self, tag):
if tag in self.values:
del self.values[tag]
class Structure(Container):
def __str__(self):
@ -251,6 +255,8 @@ class Member(ABC, Generic[_T, _OPT, _NULLABLE]):
) -> _T: ...
def __get__(self, obj, objtype=None):
if obj is None:
return self.tag
if self.tag in obj.values:
return obj.values[self.tag]
return self._default
@ -272,6 +278,11 @@ class Member(ABC, Generic[_T, _OPT, _NULLABLE]):
raise ValueError("Not nullable")
obj.set_value(self.tag, value)
def __delete__(self, obj):
if not self.optional:
raise ValueError("Not optional")
obj.delete_value(self.tag)
def encode(self, value):
buffer = memoryview(bytearray(self.max_length))
end = self._encode_value_into(value, buffer, 0, anonymous_ok=True)
@ -301,7 +312,9 @@ class Member(ABC, Generic[_T, _OPT, _NULLABLE]):
# Value is None and the field is optional so skip it.
return offset
elif not self.nullable:
raise ValueError(f"{self._name} isn't set")
raise ValueError(
f"{self._name} ({type(self).__name__}) isn't set and not nullable or optional"
)
tag_control = 0
if self.tag is not None:
@ -380,7 +393,14 @@ class Member(ABC, Generic[_T, _OPT, _NULLABLE]):
"Return string representation of `value`"
...
def from_value(cls, value):
def from_value(self, value):
if value is None:
if not self.nullable:
raise ValueError("Member not nullable")
return None
return self._from_value(value)
def _from_value(self, value):
return value
@ -717,10 +737,16 @@ class StructMember(Member[_TLVStruct, _OPT, _NULLABLE]):
offset = value.encode_into(buffer, offset)
return offset
def from_value(self, value):
def _from_value(self, value):
return self.substruct_class.from_value(value)
class ArrayEncodingError(Exception):
def __init__(self, index, offset):
self.index = index
self.offset = offset
class ArrayMember(Member[_TLVStruct, _OPT, _NULLABLE]):
def __init__(
self,
@ -758,22 +784,29 @@ class ArrayMember(Member[_TLVStruct, _OPT, _NULLABLE]):
def encode_element_type(self, value):
return ElementType.ARRAY
def encode_value_into(self, value, buffer: bytearray, offset: int) -> int:
for v in value:
def encode_value_into(self, value, buffer: memoryview, offset: int) -> int:
subbuffer = buffer[:-1]
for i, v in enumerate(value):
if isinstance(v, Structure):
buffer[offset] = ElementType.STRUCTURE
elif isinstance(v, List):
buffer[offset] = ElementType.LIST
elif isinstance(self.substruct_class, Member):
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)
offset = self.substruct_class.encode_value_into(
v, subbuffer, offset + 1
)
continue
offset = v.encode_into(buffer, offset + 1)
try:
offset = v.encode_into(buffer, offset + 1)
except (ValueError, IndexError):
# If we run out of room, mark our end and raise an exception.
buffer[offset] = ElementType.END_OF_CONTAINER
raise ArrayEncodingError(i - 1, offset + 1)
buffer[offset] = ElementType.END_OF_CONTAINER
return offset + 1
def from_value(self, value):
def _from_value(self, value):
for i in range(len(value)):
value[i] = self.substruct_class.from_value(value[i])
return value
@ -857,8 +890,14 @@ class List(Container):
i = self.items.index((tag, self.values[tag]))
self.items[i] = (tag, value)
else:
self.values[tag] = value
self.items.append((tag, value))
self.values[tag] = value
def delete_value(self, tag):
for item in self.items:
if item[0] == tag:
self.items.remove(item)
del self.values[tag]
_TLVList = TypeVar("_TLVList", bound=List)
@ -903,7 +942,7 @@ class ListMember(Member):
offset = value.encode_into(buffer, offset)
return offset
def from_value(self, value):
def _from_value(self, value):
return self.substruct_class.from_value(value)

View file

@ -1,66 +1,79 @@
["urandom", 85046090416534, 4, "4IO8vw=="]
["urandom", 85046090434497, 4, "qy6AXw=="]
["urandom", 85046090444045, 4, "Nyig8g=="]
["urandom", 85046090453824, 4, "drAwgg=="]
["urandom", 85046097552057, 8, "x7lfor1ndC8="]
["receive", 85052824193293, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "BAAAADBBag7kypolx2h2jgUg4ukAABUwASBL9wjf+vfsHVfhvMHn1i2t/tU9Ygtg850gOeMEFNpwvCUCDzwkAwAoBDUFJQH0ASUCLAElA6APJAQRJAULJgYAAAMBJAcBGBg="]
["urandom", 85052824490293, 32, "iH6SzwMwfZqZgRp9C74zX/MTD1RfzzRenrj4F8sZUTw="]
["urandom", 85052824508277, 4, "KoQtLQ=="]
["send", 85052824730065, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AQAAAEDI+wvkypolx2h2jgYh4ukAADBBag4VMAEgS/cI3/r37B1X4bzB59Ytrf7VPWILYPOdIDnjBBTacLwwAiCIfpLPAzB9mpmBGn0LvjNf8xMPVF/PNF6euPgXyxlRPCQDATUEJQEQJzACIObgj9CEx2MyPagRHuoX1OB32N8u1aKUpNKjb4b854YkGBg="]
["receive", 85052973269607, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "BAAAADFBag7kypolx2h2jgci4ukAAEDI+wsVMAFBBFk1C3wXWT68BqpoOHlZg1dzVYcIBb5WDlxKGp63sQa6trJ7Pdg5owflFUQNevZBYH18z7/HsL6nIEldBW96KKMY"]
["randbelow", 85052973433606, 115792089210356248762697446949407573529996955224135760342422259061068512044369, 108631079982384723248405167285597273137560953659345445474004269921692677910752]
["send", 85052983277236, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AQAAAEHI+wvkypolx2h2jgYj4ukAADFBag4VMAFBBOdTpIWqGo46gRjbkRpBFTFRbIwCbt5ZQ2HlEol9VVwRnXwxrutMXajB1uOt+FQHuGfqp6O190ig4OHxI7D4IvMwAiAFA0GOYlifwN/Nmg0a9txeIqEj+HxDPMO4m00zXQQNTBg="]
["receive", 85053001749013, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "BAAAADJBag7kypolx2h2jgck4ukAAEHI+wsVMAEgGdNOs6Iszjhw4bcdvQJprmP46QFlXugHVO8ltcBxKR0Y"]
["send", 85053001863118, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AQAAAELI+wvkypolx2h2jgZA4ukAADJBag4AAAAAAAAAAA=="]
["receive", 85053004350648, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "BAAAADNBag7kypolx2h2jgMQ4ukAAELI+ws="]
["receive", 85053004744230, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAAA6xGgW/bplcUR61VbDRCeruJfMEvcDebUARFfnBR9hp64UyMT13pehNKgHQ6seIl0/d5gVEe0T8biCWTlH/TK6gUIoVhXtlidDPCoy2nsn6ym/uhVno/ZN6SNkQNvYa1eymOsstw+IYUzK+NksXE1Y2E1txPAduqzBlcojJ9L8="]
["send", 85053005705172, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AA88AETY0gIvZudOc4piWLUFCNRMQPZc4uKwsn/PQyGwtzZgu3lJwEzZxcdSDe/ze7Y8H+gOuhwSHIaszvmom4caZZqLficBdOrk/KfK8+a/G4Wwm2CeIYK9lzKzq50FKjcKMDhi+DjLM244JV76Lww6SjlgUwtn9X1Fz2aD29POyfRpVa3UCzfmuBBgFPAj0DX7gfCPg8n//gjIexXXBNB5Ycdj6QMpUxyIu/FVq+wWzgvd879Y+fjKJhhPrdwzhvknSPLyzItMyEQsruhWC/FrTTokadVXFYC/0c9hprK+lI5ZwuYEY/ea8/o="]
["receive", 85053009604805, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAAA+xGgVysgoGaUbEobIk8tAS2rRDwR76Fi2/Xl5gzGdicdTYcEgR"]
["receive", 85053009748756, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAABCxGgXcvgZUxJjyj4tR9dBG1PTXUJJoPVbscmSug/KSPdq+duFaqPxnyAhn+J2Ix9igld8kR4M3SbtH4+SwL3orchi1BA=="]
["send", 85053010015499, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AA88AEXY0gLt+G3sd42d6a2HYfAkmU4RTyn5CEICc4XrgG8tEcXS/iVU4f83tAYA3ARthO8aO+ui5vVBeePxgw=="]
["receive", 85053012830136, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAABGxGgU0ED9vU10HQHxHKGxhAkA/b88f3Wl02Y52OF4XWnP9CQpv"]
["receive", 85053012964690, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAABKxGgW+Z6Y0n4FJfuVAkifLsoe3u6ff8ICVdqtmy+hSOQuRbrJc0DAyK2I2Ms7mKeqpgDj+uhlkJ+buglM="]
["send", 85053013285354, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AA88AEbY0gK809zPVpenH98v87gf4MAEGRS/K0+EOaiHLNX4GcsxLdprTteXZcsqTRmQxcZkK6BAC882aZ4yxOmAgUjLqg=="]
["receive", 85053016324845, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAABOxGgUnPCey+aJo72ETBYsmbclM/G5zeDiCBD0HINKAHc671vq4DkdzsPYcmvdfrL//8iNnES+wCYtmysqnbek53Q=="]
["send", 85053016622666, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AA88AEfY0gKZBKxnFrRGSdh6iOSJ4ZTWTlV5S9nzTHiyfklWwO4c8c78iGWjiYlnwLp5EJOCQsVc2cxUnSTh0aj7WvgBvQ=="]
["receive", 85053016651381, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAABSxGgWmXWPg361fITe5i8Uk8pUotRIE7yPwcVfBwQ=="]
["receive", 85053419555740, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAABOxGgUnPCey+aJo72ETBYsmbclM/G5zeDiCBD0HINKAHc671vq4DkdzsPYcmvdfrL//8iNnES+wCYtmysqnbek53Q=="]
["send", 85053419985140, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AA88AEjY0gJ63PajTASOzOfUGVizJ7bSRL49m/w7HCtK05EKLgNX2HBOZFDOjauJcp53S9836jc/BuHd7PbB29KpD4ZhBw=="]
["receive", 85053425530916, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAABWxGgW2vmwVTw+ziFHWUqG20hZypZ9Nr0gYJNUU9n4VMABqIzSdh/4xdKGAK5lwA+KU8j+KMiI2C8o="]
["send", 85053425905923, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AA88AEnY0gJTSJ/Q06U0/hD3CPU0s/7ceoKI3anYqhKF5wiltLQZBxbCtUeb8q1u4rEW3wkXdxs/8e2m/B0xMrBDwaK0QaZ4sdTFvc1ahOtbXT6BlijRVMAl4dsBYGf80+dTdIEccVaNags7WoNZRTm0uKyMCwi34nfou24R1Pd3xG/NcfSzufrI6Jruzp/JjGW+pxDf2CYXR7vHkInunr1tOUteo3iBdR9+9txXaOP45NkuAzK86Dx7ttxXWNHZdRooFUqi3NPvwQKwWbIrV7wbC3oom64n+KYyKOyvllWjjMVKj4H4b9KYWHjWHE0KHhcWmEW/vHOmg4OYStwWV3shshQQZTvB8h7jDPZyugStIdXIjHYyR4at3nqgPCeHnxCMxXUGL9E4KXlIuYZVCetzsJxKWkirpyVofT3TGu4MC7Le2dgZk6F7tyuH3N1z4sWkBHEG7GoFgFk0BnKawXsrX1R54fKd5kYeF5szC9Vw7fKCzl4F+tSQzqU7d4WU2CTj48IpMWYqb6VVbtdddZbEM09M7V7mpoymqp/hs8qZ4GDoMN5kmmxcrZG4ul0tYPimLDzCxZtYN8NuoKGQ6G9ijOatKwSrDGWmbA/e+w4x6WkcgcyjZvIf5JFLHNTBXoz6B+stXnazCYwexHhpczoNr6yd+C/j/T+i9IMhtyDHP5Ndo5DCjucibUvVNORS06Njtg1PEkbeC4nF"]
["receive", 85053426820477, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAABaxGgV6PByDTtT69iDzPY0JujHRyUy8C6DGnHmhJQ=="]
["receive", 85053438963242, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAABexGgWPO/Fv5jmgoZTCaqdnAQR7GsVARGmljohhe3kqB5llwkR+NHn0lL9n0SKFS+sgncjKwr/6H4s="]
["send", 85053439307992, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AA88AErY0gKAk358zF8Cidd+uF0BMjIY4u1mXapmC2dVsFnvJgau9rEhX8v0wPBaIdp8HE4O2gm8K674NOm2j2JgELnliHD7DRVDor+kXIeiF03Bp5PQLOrnucDnA/WHf4pHOieo0ysYeXZiT0tb4WsqetR74i3xeu+Sf0CfX6TwXa/9OIuptxMsTVdGGma6cHMfLXkYDuEw650immWca5r511dQSlozJL8ty88I1qs70hwF9h8AqQ85RG7gdNLirfyR+rr/XNmaqEekcgkO8zxDLAI4y02045P8x8oIkyConENZlc6zQ0e7M8y+LNuMGdKnFRZ65o2L97r9GQjhqOBo/wViKAP+kiTrTD368j1bmE3dvrs1O105rN3loPc2biF3o9hFk+jz+HpjGN15D/K5E7wMPaNpPkZ6xTWbL0kJg4fqB3KcXF5qkdoQojeQzmKJO057lQbiwbmzpLJsFgruhW4kdrAxMHI8huuLMs+1E+8Iu3JOT6519u11z6sgTxHZ10hGAzZ4JsDtl0NfZP0wqVFhkvaO7MA0ICmzbWDi0dQugrfe1IVaPBGAl3xQUdBfpicu5Tvui9x2naFqtn///chx6rg1tnTmH5HbX/EL0JEOZndcRdV3hW8jJtmSI5wwgQ9YlWQg1mo72DS0Rw+mRr/pq1DdEOTZEDoehtqVZililfT6lWZF7cm9GnyN/A5msDMaus3+ce9mtdRWuWsgeERgpq8MG0OaePFiqTBp"]
["receive", 85053439340473, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAABixGgUdX37zDgK4JK+Hp7v6Ltq9ba6TYXY+FOFRFA=="]
["receive", 85053448066465, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAABmxGgUkvnGmhXIt8qwZbra+JtkbamXW38UXbJ/5kOeQR6Gs1SPvNM9TBZhFT3NsqjfUsCnglCh53W7z1pNTNt28WEau5AlKwIpJjuvSMIvPumGOdKoIm58gTg=="]
["send", 85053448948157, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AA88AEvY0gLsWnJK+T20soEJvjkM29FCsOCwLll8MGD1rniuNoUEjhT+V0+tryWlpZEZwtjuE16PmxyRZI1/8zvHbp+D4jzJNPPm85NTYhkMniIea+ONHkkNbRf6iq/X/0EAwjOPS/2J5Zu7prMLGkPlTwITegYza3UuwhSC/8y6nH9EmofJAiku2ToLgM9aiS2WILVu41BtYsP8FbFhZZq+dnseCdBpMTkjtoNwANvfnTIpR68KUTVXiDNALawf+yW2HgU0oMwzZ+yFjltNubTl9L7A77CbtnrNmPo9iRcwANYQyivPz6VbMY2R1BcUJNG1NLQD7NFBH0jetAfci3D28yM1tHRgY2o2RCvOvk1QF0Ku7a2X4WYBBgKfWjmbcIWF3ANFK5Z8py4iu30msGn4UYg9xv2//lPvf2OV75lrrS6Y+D0G9jQXDFaj42FnvdJGrQKAS2oz2g0L8bgC92FSGbPdRilC7abUdVDTnOb/tQjYHJ1eHeG8bM/CSnqKMAD6y+ksLdPmz/9R+Mj87OtS4KzsKbBveILZxXOH4gc="]
["receive", 85053448981851, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAABqxGgXH7RUFrtoZGhM5GgnUWiqXM/namJZY8/t8vw=="]
["receive", 85053504624532, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAABuxGgWM2n5OoEOiMUezkXfD+Y7hmopN/K794XFoc9bD6gQE3LnLeSg+8bb227i1L3ipjU9CLpn09XNO4D9f/mdyKrTfBkwaRwETHkHniR6VRgf3WuCsrwYcUg=="]
["urandom", 85053504807297, 33, "4Lcgq+4mQ6YDk8kZnw/V6TH6nQuWDsOpY4IUevIBAIAW"]
["send", 85053506399438, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AA88AEzY0gIkSsGvG8vpMCNOxETUzcMElQP3Jqi5x6MuyxZ8xEWFymxLNbIjGSxHuk/vncfVIyUSOvmrA9/6ySl0AI5mtocn3ORmtdLlAwynSQdvn9r608JOmqO3UArSbSo69rxWPrvcet6cVCKEYEOmw+iN2d/VSfRebtyr1wcO3nvS66XCOF81GERt8o7RF+Mh9dnqeDAeE89ZHN4O4fD8plfqH210MooW1zM1Mb6xtGK2q7NUncWtJrIQjejhCKF3snwwAB73c0cuUS6q3lntwRVWr2ZqAPhqUwTX9nfCbBI++NVj90FtvzYuiyPBRMyQ1Wazn+LpGiicrCQeoRE0kFCWKVKhHDhKcioWjWgPDGevBH7/g/erp7DRy3Met0F7p9e4CLzPCzLEjmO7181i1LKovmImubq+mnl9MJo+fhajmoEuyb6d7n3Olh37ixN3aZnBsYd/d+VlVf7KrK3xjLVGtRowHaqMORAlpvbxUkkjDJSArTEobVwdAG0wL43njneUQOro2uKc"]
["receive", 85053506479259, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAAByxGgVhkBbPgPQ+uFr/gFeiFgUiY19L1up/aigPqQ=="]
["receive", 85053557026402, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAAB2xGgUVcStdX3Eb5qU0pIGHMTVJDa4wfXBs3UbTY2rnLo2hZwUdF4/dHOmOeKTCguzUw8dr82aNArSqzItMlyTw6MPjFweAQOfDBqoiEf7MmtvT+y/bhkFsH0dL6cMwbxjPPxkPsElkjUFRQ2oK59UpNbyOBwPUlJGmHLA7zVxcSA2x8FQOoym/dAVKPN4+z+yIymFEfVVwpKHoNSd5Z6k9Q6nCEOwO86C/9ThIfD2Ulou1Zyn7ckTTos9f3WnIf4A3FhYad+qG2Aviunt0cZULq8Q80kfSeqoxWn+jrPHSK2OiFJoXXobNCfU4pJ9WSOsP0N3WAbKwMJoxlPwy+TSYZLlJ4LC8U2lH9K9bulPA9EQKvW6UgUD0g3LnOM71T5VCqPRVgBgJPRhrSfVwqwDASg=="]
["send", 85053557362516, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AA88AE3Y0gIM2TTcNqd2vdFc6qDuH50CdsvIlrmd6jS70xbz1v4SkP+aLoPBNZSGW6YYtaPUbsk5n3BTAX7DQidxfQ=="]
["receive", 85053557396390, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAAB6xGgX+E04cAQa9itUq7yImUSIjmVRlwmA76JAFFg=="]
["receive", 85053589209130, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAAB+xGgVJ4McbX0mCecKh1PzbyBoK3x0fTcK/pqS0Dxeg+K7ejd/C873MJtZU0fNQDGGfZFDBduXNrG87irOATEcTpLvPxgJKF93+qX2YCICEuuA70QxP19FO1UcsGzeR/GWuDEDh8NkpF267UKX8b+OAJ0WZ0iXk/Ehmdv6PpiLnfyl+qulyjf6tyv+NX8466H5pgv/aU8sEFcAA+WhQpVtRrffMlRhYkBLR6kPtHfyzfoN/Y3eie4VihGZTJWaeYOpmvIdqWVrspet2/NhDrs7M5RWRfuOSemuK++xFcUpzQessg6MDT+CSYDPUw2z8OZ0DvfuThGnMgbyOVaIGXY5uNolgen9YIsqi6LWsYYsIboTxFSgnlTZWfPCyaoeAaf36Zls0eoiP2IKt8MiGWJ3aHzDhqtA/N/NHEQt6Kv637AnzlFD/KIiNT0sWwKzMK7iObsGB3g=="]
["send", 85053590087597, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AA88AE7Y0gKyCxk6ujVQR/uaFAMFDFJMZxKMBKuN1qf3zQBvhBCAPD1wKvnPoDPy50V2wo97iU8Qz3YPf0lNGxCVwg=="]
["receive", 85053590125598, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAACCxGgVxCdxgV/VXIxwGJwDNPfrqkWACVXpYr2+x8g=="]
["receive", 85053595767866, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAEAACGxGgXO+XvwZiCACbOuPpUfUGiYfP/NLyn5ObVBlA=="]
["receive", 85054718839082, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "BAAAADRBag5C1agNl2G7DAUw7ekAABUwASAtDq6lTH8/67aXJNcNKjvV3caTIiTpXpdzFZPB8CUppyUCEDwwAyCQplNI4rtpFNU2UkvG6/MhXRsO97IM0h1zblk4sjmLDDAEQQRc/w/paBjO5Uqttmk2uSwGw2hoH0ZMdsNOicVSw4IPzK2YCZluC3Bfa3OgBCRXLIr12jQkzEQIe8TQsLgfPiNmNQUlAfQBJQIsASUDoA8kBBEkBQsmBgAAAwEkBwEYGA=="]
["urandom", 85054719085587, 4, "IUE+rQ=="]
["urandom", 85054719103491, 16, "uzE8tKETTC4RwfLKW/smZw=="]
["urandom", 85054719120232, 33, "a6jfE33LC5iwrg4YQn4UI4cDDRjm6WnqmvN8ZX6jOX3h"]
["urandom", 85054721251822, 32, "WLiUijdVyR4f5jTi/S6l8srwc1ctCtIF2946+x50w8Y="]
["send", 85054721625817, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AQAAAEPI+wtC1agNl2G7DAYx7ekAADRBag4VMAEgWLiUijdVyR4f5jTi/S6l8srwc1ctCtIF2946+x50w8YkAgIwA0EE1ISZ4BZ2lyTgfvc4ja7L6ZQkwPyWKiKs59KN5degPYlG7FSk89NQ9ZX5fgGm+hRckc6lL6sw7vuB1P2ECCHoXTEEYwE6rHCryBV/Bkx2EUx2FKTyTkOFN0ae2GVqpT3eoNVdTeOwEQBt1EnMCJSbAMbf/geVqTgn6WckvgGwjRI4eeQ9t4WbOfPwubKUDFIxov3+vRX2tOzUG+jpCxP4JZyaYRIeLSRUP5UrFRu/enmjAXLkom5jsKbB4bVaLjRiXxqRAEOZ5dI7zSyyFDgmtRk8uRJGicfnGgUnP0Y26+QpLy4QkDOJVgGGOWnAA429xUoNsAfevt1o6pjOACrdaU3EeGBNHk3yesX8wlWXvQmbGB1sDcf2oAc4p3r15uBdutJ0NS+QTNZLH8FGThdd/OPsjxFXKAaL+Oh3FIjp1by8bH69VhCnhie8Fj07YcBZyjvQuNmQXsE2GnG+qVFcsFYOaEEX/w5+NErs9vHmzuBPm2eUHjyetcTrjs9fpqctLW9y5Jr6/JyVAosc+9iPqTK8Si8LLvFk7YCUWk2s7X80bNPplpVcGA=="]
["receive", 85054773247245, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "BAAAADVBag5C1agNl2G7DAcy7ekAAEPI+wsVMQFXAX4MhFl217pgCraCVaOX0QjH/vhYFbrsMVih1sGaHxfmtpMzparjd5z+c2L7IvYcS/zudPqFMZFVZmC1oPSJiPaV3l82mRluHl13kHRnw8aOXDwSKsNc6JI00Q5gzqdrSb9NtiewmfF9gRObcxfxLggHjofMKqyTJo7F/igzqs3M+NgQglPJaW5n3a2jdJAJUBXY7Ah9LImKPySSDWMHDTM3F8XbP9aQWy5+v1+PhCpHoMlZmX/xkNYHFCfTDluWrRGc9zUfLaXKmsc+naV6S78NNvuvGvPZr9tXJYd5vZZvogZJdzWMtPkpzwkJd/m+V5FlJrbLWB0cq6Cto1gxhWRFylGOwpoiOxyIIq7TqmAeuFNpNY5p9YG91EH2k/a6+TSrlzcSd8MToMb34SCyaQsldN6Z0/rn9nvUL730SfHt3STwD8F1X5mELTzL48te+aNAYNkMOEMY"]
["send", 85054773596374, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AQAAAETI+wtC1agNl2G7DAZA7ekAADVBag4AAAAAAAAAAA=="]
["receive", 85054777448837, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAIAAESvUAwC6VKjZo/eBGDIZ73vu6eeXxlQTZiKLEYo/v8IRTGBiwHnkhFgZDbHI0EYGhBBiARPmiw="]
["send", 85054777788147, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "ABA8ABTk0wrjfTC8S5o5MP8Vbzsbsj0j/t5rUqnaRRWowSIpH2+k8POKDiN1+/z1pSmdySUolT7KfJiPfaZeLK9dTHv0jQ=="]
["receive", 85054777841738, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "BAAAADZBag5C1agNl2G7DAMQ7ekAAETI+ws="]
["receive", 85054780292188, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAIAAEWvUAxHobz7H6MO1UoE3RD1c1pZpkrzPSh1cbVxNA=="]
["receive", 85078301515948, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAIAAEavUAzWQ5Inc6pjKJSyxib+r3nARZPmQq7V6Ztu6TabfDJaQimalLmJMBcMExqu"]
["send", 85078301974363, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "ABA8ABXk0wqjIdtACW/UdgX6tPhej18Wvzw0djzKsKqhugqDD/3bpCA7WadQuLuxXCj/2EFs5KRSgUCpqM6XjwWG/A=="]
["receive", 85078306805562, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAIAAEevUAwl7Z8vKBl57/dsSC9JTqrOuKAZZXyB7F+YC33LVLxEGhaW"]
["receive", 85078322605591, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAIAAEivUAykKrA9YurzzwormCtb60L+VrSBhFlTlcf5/vrakq81sG2/u6sW1MPlrX+s"]
["send", 85078322801090, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "ABA8ABbk0wo+Yhu+b1z4Qt4ONKeh2qka0R+GbSSezEGrC2M33tDDBEp3"]
["receive", 85078327002160, ["fd98:bbab:bd61:8040:1882:ef54:2c4c:fde5", 63102, 0, 0], "AAIAAEmvUAxtsgWRmFXRIzKGoePy9MR2al54Td3/ESA+X3akA/GRKceS"]
["urandom", 21894081008959, 4, "XvUqLg=="]
["urandom", 21894081027434, 4, "jrcU0g=="]
["urandom", 21894081038615, 4, "5BYc0Q=="]
["urandom", 21894081045118, 4, "lb9U8g=="]
["urandom", 21894087912061, 8, "xSEfVXmXq0A="]
["receive", 21916834359035, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "BAAAAK0VHgJE2rDcReREXwUg3ckAABUwASDvoeQNVIgYlkzJygJaPIIbh5eRTbASZTPEB1+nE+H6jiUCfxokAwAoBDUFJQH0ASUCLAElA6APJAQSJAULJgYAAAQBJAcBGBg="]
["urandom", 21916834794857, 32, "A40cHZ6Twissn5oM2oAq2zEeNMxghOgkFxFMlBb8kWI="]
["urandom", 21916834896429, 4, "aBDYWQ=="]
["send", 21916835122966, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AQAAAFev4gJE2rDcReREXwYh3ckAAK0VHgIVMAEg76HkDVSIGJZMycoCWjyCG4eXkU2wEmUzxAdfpxPh+o4wAiADjRwdnpPCKyyfmgzagCrbMR40zGCE6CQXEUyUFvyRYiQDATUEJQEQJzACIObgj9CEx2MyPagRHuoX1OB32N8u1aKUpNKjb4b854YkGBg="]
["receive", 21916877322779, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "BAAAAK4VHgJE2rDcReREXwci3ckAAFev4gIVMAFBBC0bfFkBLqjTZIZzL/hG8uiRFKOqxeIlrafh72iIWfiHVNtcdFvurASmAFyV0NWXphm6rjEIQqKscYeg9srtI4gY"]
["randbelow", 21916877475607, 115792089210356248762697446949407573529996955224135760342422259061068512044369, 20635248899586279189889848880180462748155681201263863407999875450909239826541]
["send", 21916886924090, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AQAAAFiv4gJE2rDcReREXwYj3ckAAK4VHgIVMAFBBLNBzvrXQsAhoO/WLGfXFPg1q56sUy14BfyIdSX1cEIpT6o2zf1s/0RsIWTH3Cwi1XuZ6nN8XljyPiqztL0/ThYwAiA9XWXZPWKqh8vjctzVoqZgd9xQBLYgAYaMHgBmFsFHgBg="]
["receive", 21916899213712, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "BAAAAK8VHgJE2rDcReREXwck3ckAAFiv4gIVMAEgoKqGKIuUcRhY50dEvOERHkjhgVa6HXV9JVtOQdHhyfoY"]
["send", 21916899325944, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AQAAAFmv4gJE2rDcReREXwZA3ckAAK8VHgIAAAAAAAAAAA=="]
["receive", 21916902108142, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "BAAAALAVHgJE2rDcReREXwMQ3ckAAFmv4gI="]
["receive", 21916903034139, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAAM01IAefjNJdWE1TQjI6ShhRFCR9A9NnAdBioptLb1+sp50eRVD7NzmlewzpRp9y"]
["send", 21916903424225, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AH8aAAiBnQUIjHqyXncBFI3yCxsDv/zx8fovewdAVQcBvQ+kgc8gA3M60FcNh3wzOysEt4ZSyRYwHFa9hgHbdNQLQSMg"]
["receive", 21916905859468, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAAM41IAdJNKcBWzzvpiQ1/NzM95m6lC41JAPGKuKJNcQZGjHhjegt"]
["receive", 21916906873972, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAAM81IAf/YWKZcBHjhAftMPXPWSL/PxIRn5cd7Yfc/SLy+P+t/Mu+w+3hnHM0ofLV"]
["send", 21916907129534, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AH8aAAmBnQUZFuHzojAT3j5l36T8YaTvlPpV4OBF1LffCJKzuteMiMmR9gDMQKw6QCu2W0FuqtmWNZWX7KVXsf9UMCOjBrIG4RnxTgE="]
["receive", 21916912200169, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAANA1IAcHLA5Diiadb2uq1H5QJo33Qx9OiCAMu7/eDM+Rg49ougiL"]
["receive", 21916936270274, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAANE1IAeIVfshh31R/QZpTwxbRHdMYFsJRV2YqwH6WQQzfdti0Cf2pjBX38d+jOHaww=="]
["send", 21916936526978, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AH8aAAqBnQXdQR0X25yyXVV/QXfLoPt9HFgA3bo6c852yXpvLHKEy8gB4EGYekYjOJhEn9hivCZfzNEPyVZoxDCH"]
["receive", 21916942315406, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAANI1IAdZt3YVOpGaUda14CZm3uAyYfkNDN04TrhqCDqzWAcTU4G+"]
["receive", 21916957235290, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAANM1IAf5nv49sHvmTn0KlfWGxPaGxdhchN2VX8dtuc5bJUGwmoBWhJVJXFTcBwh/"]
["send", 21916957482677, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AH8aAAuBnQVQihllEL2GWODvdI72ehXRgPw5NakuFcqlj2FeQPTWFxfURYojdHUM6ZC71orS6bph0KuOpTizrWbyTwutiutgaaqxBzCqzg=="]
["receive", 21916962740054, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAANQ1IAd17MOYk50c+pDgLi3FE+tY64mZH6KkZ3it611qxj8lAEOq"]
["receive", 21916966325888, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAANU1IAeuzV9TWJ3ZBKNNn9idGQ6Ki7jy9dc1uShyyD4Ime2tw7//NvrZemQ5yfcMmw=="]
["send", 21916966566983, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AH8aAAyBnQU4XtkC8nILeQqmSYlOJMp5jJOSR1UFPP/iV4Dl8TZHLDR/lIFUZsOwBVjKRi5M+jSZYR5R/6oQGKpM"]
["receive", 21916980238181, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAANY1IAcrqOxnHxoZN3mlhq22MYQj67CueEgU2pg+0MYA2rNPZ+89"]
["receive", 21916980343430, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAANc1IAf3dcHUzh2V3DONK5q3js2I5QRp1vAxFfUniJdOSnknQKfX7xnpu/Zi3rYk1vbrPUk/FVSCn+gB1YLPzLxLnnrFjoBVa12D6Ykie6p+kH2CRWbRAY3zEpYV6AL4ar2EuKnaQ7HxkfEopdN4ZDL1LIzgYDtn/qaJRc9fzCA="]
["send", 21916980949573, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AH8aAA2BnQW9/P2wR56RN1ORz1HvV4t33L5bnaE4Wz9sMW47rwP3pjTP/3KYOOBXdhQ19t8z0Cg9lyh4ts5VJVXzisrX7sO/DPmoPMYIIfkOnriEKbSNreN7f1+zaHQpmyeFOQaffg1mx1fVZQz9AZDKhaTdeaatBJCpIRCmsu7ALtT9ISY3jMPOZJNXnRwe7OeYSOJUjmiOPH5qdPmvgjvYBSdfztKq3jhKz4krWjp4XvAMM4XsNiR4CxyiuN9oPkrFHd+CUZypzilO3dbKUhuChO6MhUKCRK1ZWs1AHHVU4Z/DT0aAqrWBsGE="]
["receive", 21916984015887, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAANg1IAcnCpmTkK41AjoLaIzH2qKReaaqHQ1QvDUTTOyfn8vqhKAE"]
["receive", 21916984735946, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAANk1IAdA6f2Ntsmmp7u6cvzzztpfeCKwT3XIzwHZbjWw4HYW1LG29oZceM8vjbz7Bk+FPvdNQDSaQbGRViPIIUGeIVj5zvmDyvg8tghiNSMAPxyzbGEXs36zxRToB8jj7u4W/4gWhA=="]
["send", 21916985014431, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AH8aAA6BnQX2awWN2gsWyNLmjahaTg756tnbJqwgR+mbdjeIAFDFEZViwaTI2hpzidKzwTd9+gLAhqRn9D0Mcw=="]
["receive", 21916988392864, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAANo1IAdcEx2bJb90tcXHTYfQhD2VCUM3zzcj3g+/jiY5dwAYGFa0"]
["receive", 21916992478340, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAANs1IAd9TeCtsWI7Usx703uvcj3ZI8fBVJjKxX9JWcfrLXJ//O3vcsCr0TYJGxN2rq5Djq9CtbUfSiT/oQI="]
["send", 21916992800528, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AH8aAA+BnQXvArFm7TTnoYT7Z/XBH83HwG3QKoTXrO8HtASls4DZ2htf1JrHYaQTqok67LYFNZDilScaA9+87XWZ8x/73w=="]
["receive", 21916997621351, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAANw1IAcg9OzS4A+qhIDGMkAqJlAt6xf6T2ka4qMxhDtPtnCJin8RX58DuqwAYxf3xhHVpLwMPACg14BO1ReLhLC2EQ=="]
["send", 21916997960271, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AH8aABCBnQVPQUhOg1sh9KI2HXtog5T1HY0u7EwoY6rlDLdD4c7d3TIclZvgFgj3ec8w4kPirkFHLqVlBR5BS/70/th8oQ=="]
["receive", 21916997990217, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAAN01IAd0vYudmS40bU29FIKQYP22FLRSxU/8O8UpFw=="]
["receive", 21917004508042, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAAN41IAfTp75ySdfePn7QM3FqQ/RrZ4OJSaCrDwRiYZdPDt9nlIctwo8a/H1L9s+PLSCv5Lf3DAEhQ8w="]
["send", 21917004857271, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AH8aABGBnQVN5fRZE0rF/odUjLcFLnMr2Snf0AnNRMXQuepyDr/5ypHOJCE6G0C69hM7RD9lzZ9HU5QQKrXG93v8JHMmf1I5jZSuZ5otSbeG+CHiW+qynHWpe7PIdns5UbtgCpGM1ywe70IgEvxqWwgTikc99Wu2rnt6QbtMzuICltRIyiBMZXxF5emtvCuP79hfSa/nJWMlj64x6TVp5Dq1h+KCdvgpHbbrRjXn7qlnE0xUXpVYQ0sYk4XT3BDpXmJjYvCQNjFfLKN9tOMKvFQGC9YqE/LzU1pCVlI91cNjmYLLRd2c8bTQOBsaNvN5ecMyn6XzUjlEN0+lenFn0pe7T9f1RrmTcM6vDzS8rlVZgWg+GLktfWbfrhpBixwX+DqwYWtqoXaQ97HL2hTw4g87piBTrTE1xWKYO4nQuuUB+xkWE615DqPhT5SxXpI11dmhzyF6y6vj8YeHjRywV1T6fXob6str0hbbpnH9NUOfNHhsWyn+uF7pssK7cP5UVN6M+MNxHAO1SZoJY3v3IUnmtmPxOKAxpPCSJS2bwAZJKU+//infnR2sLNnGC4lpGbyned79k/IC3QDxjX6sr1xzTzGdXRAEc97+81F1c1JoBZ9racBsoAFmwNMfkOqFT7g0iI5UsXWdmYMruN2pV/DGxzsPSlMI7HJdfc0h1P6dlxQk7GFg4FpwR4kVO1BZtGpgScMjqN3/1isy"]
["receive", 21917005802444, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAAN81IAcwCt/X2/qKxI7rGuV0nA2BVAiJTw8vw3JRZQ=="]
["receive", 21917081678121, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAAOA1IAdFBBEqmCflh9OSxGllMpKyXqa0PHXIts/0y1rm/RX0DEqhHlBexX4r9at79Hwhdtc7uHyKo0Y="]
["send", 21917082032179, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AH8aABKBnQXQngEyGhxxYGtWjmEffjA58BAdXigsirbhsxnH9H/SmWKKjiFC29eytKN5KcYiOIgkfUxQU+rbvvHhgLA2M5XTNKyNTUk+UyMcvAUiNkdJ81VC0JGplUtrMW2y1QhiXPNWHGWm0lDk+R1kPZ+2EBUkEiVbIuYbc1hc5cDcE0MsuJSc26lWFoX5e93LwLaUJ9LaImV/fgjlgPULtGy+rIPM46NCERn/PtYKUI57ba9bNHD5uQalDuh1efMvJAwC3SXtVMY9xfZ1V5npnawQyb3QYFNt2QqT5iDqjiKDrhQZgI8UCEMoIx3Tcu3faCkBLYsMlDOprbLEPDMbB88e283pxbbu2cc5dI82j1xPiE27v1GVuJC+mJzQea3THtl9bLlpBNfDQ6pD78ZeIr5/+SJRpaWZT9GyMir/7uD3u6HT+H4xvjggPEwan+fzgMptxkHqpS4QuyWi9YUwwfl+okWJxl1jIhto1+dkQb/rLJ52YkQXfNrrf3lZJcR1YPbaif0gWGckSKq6Fy7XyIxmn9snyJgVa10LQNy9Jak71ItqD/68vqoL31vnGHsS8qN0hPKw19tcXVtWZQjq/rThGtAJgKamL96WXdGoqaxRlgPrrs96SmYtI6sUECLJ8FtBTPD+ytbX+i37PC8UoyzSFbcHLNJKqpjU5XAA+EzxqazQ9v/re0NfStTpWTmYNGoLzpDmAFM4cetAfN/fiAlSxOzVJfYc5vMXiffg"]
["receive", 21917082098353, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAAOE1IAeUF7Nhh8QF7j84sl39cCzgPRkk7RDjKLJ9eg=="]
["receive", 21917611479279, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAAOI1IAdlvZAGyRsy76KqXEx72YYHRXaoztZMx9goVNp106bE43yL6GjvTCfCartNk4F5ClqtkjDNeowzWNfCU99fnnnbVQbDDcYVxYA2G6jBdimiJdQ8PLDm6A=="]
["send", 21917612493633, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AH8aABOBnQXqX5KAGwaFqWOaT4HYRmBFJJOPdP1XW4G+Fhaf8zV22J4SpUkNEXjAHCXDBcGuwLBsLRrsGmwLY7EmkP5pK4zmyJvJ4J0fGEovfq7z4j5VGVmF0nHL1GasQhhX2QCxUrQU84lqxKZ7X1Gmtt6OETGAKA/I0bCG9WguBKolqoesFyeRJk1T0yx55Zs4nVTjGTwd474mlBBVM4Im0Ej3XDo4iEHyZiKLW0/ZZ19rFnk8KLru89toYkrFuakpc5Hbx/D0Z9HGk//ExcCamCvsR8fWfo78UeOjZV1TELaocgcCa/l5eamG3vmE/+f+5ws3gkkCg8g9Uor3va85+d9ZeMiEh3K6s2w7LRaf0jDbqDIHlkbDLZGpMWgPVDTFN4InWPZNKh7W4mc/qHwzUraoakXjIy08v3vp3XzoA9G5rZbYoGTrE96yjUtmf2pNQW2RDHKmLQ1QnSy/m6+qkXim6fuET9P8NZrK4Uu6tbXVL3b9xutiXF2CrfI55496ZbZ+c2ZfZKPc9N8hdvE3oO0u5MWYXBjIGtAdqrg="]
["receive", 21917643028893, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAAOQ1IAdlv2wZE03/0J9c5Pjbzw+eWzACZIrGYncw9R66krdwjBp+O6RTGqzRjvjkC4MjIuIB6N7O3lBbTeU="]
["send", 21917643428216, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AH8aABSBnQWJeVhwZWI/dGMq5GSZYNjNUSyclBSmBdv52iC0lCZoYORugqTorCNgOpBZoZNr/LWorpXJGLShMznjN6cSTQ=="]
["receive", 21917643458603, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAAOU1IAd+/Jt//2BKCt0xD4vDn2t6Bnx0Y7G8gpeGRA=="]
["receive", 21917650527507, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAAOY1IAehwkagzV3zc+OCUX9IUxCoKmTnQ9GlJpY05g=="]
["receive", 21918903683973, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAAOc1IAdsmaP1oUbvNA1pHNp9AGdUre9QRdS7WUHnrv7314rHEb31GvUu8ZQW+Qjc1tkXRuHjNHR/ELLmfTB3uqkoz8amh7lcGhl1SH4RWn2voRJ29i8ti8rJig=="]
["urandom", 21918903999829, 33, "4IKkt0i5n/JSrVQOQqCyQbcMV3jISs53dU7Hs3gitUQh"]
["send", 21918905537429, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AH8aABWBnQUZoc6IX8af0Twfd6Y0QzoM/cUowtWqdtyrNBeM3iGIKNM9AUuMNCwXlI4HP6Fc+kOCReFWOGvOMM1jsUxIElFvUYZvOFi2sanKxAT8xgkQlGD2wsj7g3jaH7oe5U/44ylekitfdjWq+1/e3ixHDD2zQEh9u6023LVVQKWbcQBTzQ1EYyhwr2qnqnkf1pOzYrlq9GezVvMIPT+MfVIO9ghJBYBWOk7wH7zRLxsOPs4Le1beSmcJeEcTh4G31Mj9/bLLs9bnhUEJaeXOPwIUqedWEm7wNKzgfe7OU6G4SptHw+8LZpvluiBbCZLPwRJ8+wgtbOxNvFBs4fx6g9Y0dIRufwBa9oYPFUkwVAZo9YhFW9s7zAvpmaRZlwAO2/Llz+kDQL7FUkqHnCsklv6dXDjgrkzwgA2o0D8QLWSl4IvJXV1RdWZcRWa0bROPuF3BGTOnyANyT7q0AhW67qx6RmRrL1hXEuvg8EAlENWGq2W1Et+ixjqPYQkg7pSMkDQUxXbVj5uv"]
["receive", 21918909476630, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAAOg1IAd9uXprQnGzZb++x5XUZBk7aczRTzOGrPoGgw=="]
["receive", 21918912917540, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAAOk1IAckIIq2yCzjlVQ2t1Ja/2adQB53JL7OReLRrJKzzB6rbgtL7QkIY1zFUfJ9ubuEvMEIlqB49OIE1T1ZwbyjpbC9m2YXnROKNoCOikkI+KY9uRT8VVjZ4i68pGdwnzFz5PJsegjrLk4I7ySR2JAwaNqNTqGEvOns+yCqHAWeLz6mHNNqF7VWiNtBi3JrGHR/clc9GINV1wIMYCjCGg0tcAihahVmRwVa4kLF7giHRv1DvsGJyJkL7GP2BDv/PqlfY1VKrcRviKWhIvm+HLcS7gQVo6tJLeNrmmToYakKeTbuvOThAi7jQrW/ZcBk7F4izvGF96y4MfXExX/WlzIn4SZSlNEDOcjF9Vn4nFvX+D/iYS0ZDyK/9Bg4vqTdm4KUmU1B6CyXbVSQ7LCglKY="]
["send", 21918913214791, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AH8aABaBnQUzpbhTb6FNK9TOqPzODK+wEUrGHQpayAMzhtNsvFiQnPsi9G1wmth6APnthtRunw82GekkhDMlKkwBTg=="]
["receive", 21919022007470, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAAOo1IAd3NfJ7zftzGT0rEcqlVHqKTKUXC6r1z5/P+DRfHBnsSO0rHVDMFgQSPPP0ydh423LWKjN4NDiK70DYwHcoRlPNQWpbsfyp0UgX95wPfJGzrzGV5CZp7vmZUJPUnGPF83BlFau92o+3MAVihzLExRjr22JEZtT4Mxp2PFShZU4gBueN8YbUSpzY59Ahc6acfENPcTnm9Lp2pKxlHllC8lA9YAlHAWf73NlgY+EA5ompMVBUo5PO/7d1EiYPMkucJr1wqYYnF3ynL9hZEvrdHpWkUybfNW3oLyg9hpjuWxMvzDpqg8B3h5eQEpOS4W/v6X02RFj3qYo0DO1MFAra7NBy/DkS2NzBAVzlSeEyNKI2e81QDN32SDiCf3YeWQhacYczzZiIKF45qNAI32WwVVc/Alwqt0+vnAMZ29yhFlIR6WVYMCAEnXfWebj1P+NKM1ezp4A="]
["send", 21919022809533, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AH8aABeBnQXlct+XRqbzDs28acu+xEtMU009vLPb4ZeP8FJWBflNe/uSbmW35ONc1DsdKA5dVkMIL+Jhz7KqQ9dfHQ=="]
["receive", 21919022841864, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAAOs1IAevnSjN/fc7xHGSZqROWmUal722M+LWx3iBrg=="]
["receive", 21919031531485, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAEAAOw1IAd3lGR/6SPEfQk2M0JOyCIRgI//kozMRyPKPg=="]
["receive", 21923257339887, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "BAAAALEVHgIBgnf2oEXiDwUw7skAABUwASC1Leko1ydfqAnAGWd1LB5dde3zfF9H5W+yqVnBZgsX6SUCgBowAyC30ZqKf1VEz/rTD5IVGx1OCk+yepRpTMxLH4Lmhml/gzAEQQTYTuMMJFniUx4n2CRjDo++Vp7vtvvpMSU5oDD6NRBDipMG5gvKIcQKf6GPoE1yb70216UeeNNIfFpkMVq3b3ekNQUlAfQBJQIsASUDoA8kBBIkBQsmBgAABAEkBwEYGA=="]
["urandom", 21923257602192, 4, "bhDINg=="]
["urandom", 21923257618894, 16, "Zh8MX0XMu2sKWrZnCPb9EA=="]
["urandom", 21923257632359, 33, "nz9N1LpkCCcezlOhKTmfk2bTj5OcKczTAyC635atsuTt"]
["urandom", 21923259804106, 32, "wem6k6wmT5JeG7QgD8q/viiaWZ6zX7Z42NiItuPXV2I="]
["send", 21923260038959, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AQAAAFqv4gIBgnf2oEXiDwYx7skAALEVHgIVMAEgwem6k6wmT5JeG7QgD8q/viiaWZ6zX7Z42NiItuPXV2IkAgIwA0EE1pwmnq0NYmthwUFXPhrem1sJ0fyZUA/EkmqmFOKQSv78HwfOVyyqKiYkun4sj2HPtXefNfdO+VK/B51kzPB4RTEEaAGoWFQag+HBZkk8poG0pv1CVQIkGBGTCmV1k8fULgN8e7+st7cAhWlojBpObM4NjRmH51q/f1Tu9QNTpnOkwMoFJAJNhQ9Q1WhW57X9iGTIuID3JUnWyJqavc/ALfVHREK7Wx7CKmSH32YTb5xn2nUFFmdjzoVzT4/wqWMoLb5weyIxPWO1SnhXb9PAFIuwJ17u4rX2Gx6ahrUu5hc5fRPpkG9n1hOUFDVAh7bfxhJsdyBjvcGiVOyuwpqpQJHYgqrETVsGgiiRy0i7OlOWBpLHOkFwyM5L5W6mgpg20sMihn0j8eUWjtz5SDhnFTePc3mQMfUYJryJBCxjZ9FBDYF+Fj9DRDxo9MWFYHu5wM5x58Y3ZOO9v63eRmP/X2l8JDE+z15QduvlBMtVQlIhNXoKqDCzF95xDUoCRGfzPWAiCV+1e67w+2vd3KxZqsIx88S0Pr48ez7qEEGc+hE9J06diE7YgkVoH1wY"]
["receive", 21923408064894, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "BAAAALIVHgIBgnf2oEXiDwcy7skAAFqv4gIVMQFcAeUKu1jOPc8C5BS8QTGKU7Ab0mGOsbBEMP2ibV/EfBJruyjh0OWGKYkAHnZozivguO8szs6vDRIjBMO38EC2LusbTO2VCcRnaD0WNqaiYMk3VqiYf9tHUcybo1dDI9Q7FFLUn2pupo3ufsGnFLdbokp7+Sfr2xL2sqrYpNvB22F/EPnsN9PgZDiQqubowiPVgIphCK8X3oaqfU0TRfipPrVYuIbsLtMvjN7CTN0ywPYjp3okA4RrvT8gjkBiK20Mi87nYouNltfB++/KpNkGGkP5mGT2AY0BE9Af53kHyY5SDQ/9EWK1zHQMjBVKbNWC/SMfSIT9/waJpBboACLH/WzfBlViwK0JB+gVk5O9dkBZQ5VelMnYby97uchpwYBwCV4DNOrFrzRimvXS+FG9kBThRwNpuvts/O+Wgz97UMHRb2aI4OEQGCReCx2kxzfhwIQFSHP+UK9nEWxK6Bg="]
["send", 21923408330535, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AQAAAFuv4gIBgnf2oEXiDwZA7skAALIVHgIAAAAAAAAAAA=="]
["receive", 21923537684809, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAIAAGZzDgekU2buw9XUtZc3n+EXoRB9rZ2WECU4C/lui3JSWWjV3cPQmTKPExI97n7UgMHiIubRJ5g="]
["send", 21923538115031, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AIAaAAiBbANvZkVtb3sHn/LUaE8UDUa27J4L6Qq8Hwwz93qDNPc3O54vI4J8MsMUVDcpCFQFiQN+tEqcUcllyTkBJL7jeA=="]
["receive", 21923538146170, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "BAAAALMVHgIBgnf2oEXiDwMQ7skAAFuv4gI="]
["receive", 21923545829794, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAIAAGdzDgcS+7HpsM/LogDODpSTXJLoB8Tq4+7e66quUw=="]
["receive", 21923568427410, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAIAAGhzDgd37q0GxjYNuiu585YmkourxDfN5g5yjDqNDjBnaHDfVs+GNdkVs7HrpIq8X9x+8U6bM/8l2mxU6fPthVdvxNvHZix6JgUTUsd/8a923rCSHHjXwS1DaFItM5VJqz7U8cDS"]
["receive", 21924043106741, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAIAAGhzDgd37q0GxjYNuiu585YmkourxDfN5g5yjDqNDjBnaHDfVs+GNdkVs7HrpIq8X9x+8U6bM/8l2mxU6fPthVdvxNvHZix6JgUTUsd/8a923rCSHHjXwS1DaFItM5VJqz7U8cDS"]
["send", 21924043262856, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AIAaAAmBbANIWBRgW/zrdjvYY8d/nf50Qz+t/AHJzKE0xw=="]
["receive", 21934027221255, ["fd98:bbab:bd61:8040:403:16aa:52f3:b037", 58428, 0, 0], "AAIAAGlzDgeHD1bPhjftySmRe/CKvsPiMsB1a4IOc0AzCNte0s9MhNU3quCIy514ehfsCJBh/mJl/w=="]