160 lines
5.5 KiB
Python
Raw Normal View History

2024-01-23 10:29:14 +09:00
from __future__ import annotations
2024-01-25 18:04:55 +09:00
import asyncio
2024-06-28 18:06:55 +09:00
from enum import Enum
2024-07-10 09:23:33 +09:00
from typing import TypeAlias
2024-01-23 10:29:14 +09:00
from pysphinx.sphinx import (
Payload,
ProcessedFinalHopPacket,
ProcessedForwardHopPacket,
SphinxPacket,
)
2024-07-10 09:23:33 +09:00
from mixnet.config import GlobalConfig, NodeConfig
from mixnet.connection import DuplexConnection, MixSimplexConnection
from mixnet.gossip import GossipChannel
2024-06-26 15:55:00 +09:00
from mixnet.packet import Fragment, MessageFlag, MessageReconstructor, PacketBuilder
2024-01-23 10:29:14 +09:00
2024-06-28 17:34:13 +09:00
BroadcastChannel: TypeAlias = asyncio.Queue[bytes]
2024-01-23 10:29:14 +09:00
2024-06-26 15:55:00 +09:00
class Node:
2024-07-10 09:55:51 +09:00
"""
This represents any node in the network, which:
- generates/gossips mix messages (Sphinx packets)
- performs cryptographic mix (unwrapping Sphinx packets)
- generates noise
"""
2024-06-26 15:55:00 +09:00
config: NodeConfig
2024-06-27 17:32:22 +09:00
global_config: GlobalConfig
2024-07-10 09:23:33 +09:00
mixgossip_channel: GossipChannel
2024-06-26 15:55:00 +09:00
reconstructor: MessageReconstructor
broadcast_channel: BroadcastChannel
2024-07-08 17:14:51 +09:00
packet_size: int
2024-01-23 10:29:14 +09:00
2024-06-27 17:32:22 +09:00
def __init__(self, config: NodeConfig, global_config: GlobalConfig):
2024-06-26 15:55:00 +09:00
self.config = config
2024-06-27 17:32:22 +09:00
self.global_config = global_config
2024-07-10 09:23:33 +09:00
self.mixgossip_channel = GossipChannel(config.gossip, self.__process_msg)
2024-06-26 15:55:00 +09:00
self.reconstructor = MessageReconstructor()
self.broadcast_channel = asyncio.Queue()
2024-07-08 17:14:51 +09:00
sample_packet, _ = PacketBuilder.build_real_packets(
bytes(1), global_config.membership, self.global_config.max_mix_path_length
2024-07-08 17:14:51 +09:00
)[0]
self.packet_size = len(sample_packet.bytes())
2024-07-10 09:23:33 +09:00
async def __process_msg(self, msg: bytes) -> bytes | None:
2024-07-10 09:55:51 +09:00
"""
A handler to process messages received via gossip channel
"""
2024-07-10 09:23:33 +09:00
flag, msg = Node.__parse_msg(msg)
match flag:
case MsgType.NOISE:
# Drop noise packet
return None
case MsgType.REAL:
# Handle the packet and gossip the result if needed.
sphinx_packet = SphinxPacket.from_bytes(msg)
new_sphinx_packet = await self.__process_sphinx_packet(sphinx_packet)
if new_sphinx_packet is None:
return None
return Node.__build_msg(MsgType.REAL, new_sphinx_packet.bytes())
2024-06-26 15:55:00 +09:00
async def __process_sphinx_packet(
self, packet: SphinxPacket
2024-06-28 18:06:55 +09:00
) -> SphinxPacket | None:
2024-07-10 09:55:51 +09:00
"""
Unwrap the Sphinx packet and process the next Sphinx packet or the payload.
"""
2024-06-26 15:55:00 +09:00
try:
processed = packet.process(self.config.private_key)
match processed:
case ProcessedForwardHopPacket():
return processed.next_packet
case ProcessedFinalHopPacket():
await self.__process_sphinx_payload(processed.payload)
2024-06-28 17:34:13 +09:00
except ValueError:
# Return SphinxPacket as it is, if it cannot be unwrapped by the private key of this node.
2024-06-26 15:55:00 +09:00
return packet
async def __process_sphinx_payload(self, payload: Payload):
2024-07-10 09:55:51 +09:00
"""
Process the Sphinx payload and broadcast it if it is a real message.
"""
2024-06-26 15:55:00 +09:00
msg_with_flag = self.reconstructor.add(
Fragment.from_bytes(payload.recover_plain_playload())
)
if msg_with_flag is not None:
flag, msg = PacketBuilder.parse_msg_and_flag(msg_with_flag)
if flag == MessageFlag.MESSAGE_FLAG_REAL:
await self.broadcast_channel.put(msg)
def connect(self, peer: Node):
2024-07-10 09:55:51 +09:00
"""
Establish a duplex connection with a peer node.
"""
2024-07-10 09:23:33 +09:00
noise_msg = Node.__build_msg(MsgType.NOISE, bytes(self.packet_size))
2024-06-28 12:26:44 +09:00
inbound_conn, outbound_conn = asyncio.Queue(), asyncio.Queue()
2024-07-10 09:55:51 +09:00
# Register a duplex connection for its own use
2024-06-28 12:26:44 +09:00
self.mixgossip_channel.add_conn(
DuplexConnection(
inbound_conn,
2024-07-10 09:23:33 +09:00
MixSimplexConnection(
2024-07-08 17:14:51 +09:00
outbound_conn,
self.global_config.transmission_rate_per_sec,
noise_msg,
2024-06-28 12:26:44 +09:00
),
)
)
2024-07-10 09:55:51 +09:00
# Register the same duplex connection for the peer
2024-06-28 12:26:44 +09:00
peer.mixgossip_channel.add_conn(
DuplexConnection(
outbound_conn,
2024-07-10 09:23:33 +09:00
MixSimplexConnection(
2024-07-08 17:14:51 +09:00
inbound_conn,
self.global_config.transmission_rate_per_sec,
noise_msg,
2024-06-28 12:26:44 +09:00
),
)
2024-06-26 15:55:00 +09:00
)
async def send_message(self, msg: bytes):
2024-07-10 09:55:51 +09:00
"""
Build a Sphinx packet and gossip it to all connected peers.
"""
# Here, we handle the case in which a msg is split into multiple Sphinx packets.
# But, in practice, we expect a message to be small enough to fit in a single Sphinx packet.
2024-06-27 17:32:22 +09:00
for packet, _ in PacketBuilder.build_real_packets(
msg,
self.global_config.membership,
self.config.mix_path_length,
2024-06-27 17:32:22 +09:00
):
2024-07-10 09:23:33 +09:00
await self.mixgossip_channel.gossip(
Node.__build_msg(MsgType.REAL, packet.bytes())
)
2024-06-26 15:55:00 +09:00
2024-07-10 09:23:33 +09:00
@staticmethod
def __build_msg(flag: MsgType, data: bytes) -> bytes:
2024-07-10 09:55:51 +09:00
"""
Prepend a flag to the message, right before sending it via network channel.
"""
2024-07-10 09:23:33 +09:00
return flag.value + data
2024-06-26 15:55:00 +09:00
2024-07-10 09:23:33 +09:00
@staticmethod
def __parse_msg(data: bytes) -> tuple[MsgType, bytes]:
2024-07-10 09:55:51 +09:00
"""
Parse the message and extract the flag.
"""
2024-07-10 09:23:33 +09:00
if len(data) < 1:
raise ValueError("Invalid message format")
return (MsgType(data[:1]), data[1:])
2024-01-23 10:29:14 +09:00
2024-06-28 18:06:55 +09:00
class MsgType(Enum):
REAL = b"\x00"
NOISE = b"\x01"