reticulum-rs 0.1.3

Reticulum-rs is a Rust implementation of the Reticulum Network Stack - a cryptographic, decentralised, and resilient mesh networking protocol designed for communication over any physical layer. This project is open source and community-owned, focused on bringing Reticulum capabilities to the Rust ecosystem with clear APIs, reproducible behavior, and portable deployment options.
Documentation
use super::wire::should_encrypt_packet;
use super::*;

impl TransportHandler {
    pub(super) async fn send_packet(&mut self, packet: Packet) {
        let _ = self.send_packet_with_trace(packet).await;
    }

    pub(super) async fn send_packet_with_outcome(&mut self, packet: Packet) -> SendPacketOutcome {
        self.send_packet_with_trace(packet).await.outcome
    }

    pub(super) async fn send_packet_with_trace(&mut self, mut packet: Packet) -> SendPacketTrace {
        if packet.header.packet_type == PacketType::Proof {
            eprintln!(
                "[tp] send_proof dst={} ctx={:02x}",
                packet.destination, packet.context as u8
            );
            if packet.context == PacketContext::LinkRequestProof {
                if let Ok(raw) = packet.to_bytes() {
                    eprintln!(
                        "[tp] lrproof_raw len={} hex={}",
                        raw.len(),
                        bytes_to_hex(&raw)
                    );
                }
            }
        }
        if should_encrypt_packet(&packet) {
            let destination = self
                .single_out_destinations
                .get(&packet.destination)
                .cloned();
            let Some(destination) = destination else {
                log::warn!(
                    "tp({}): missing destination identity for {}",
                    self.config.name,
                    packet.destination
                );
                return SendPacketTrace {
                    outcome: SendPacketOutcome::DroppedMissingDestinationIdentity,
                    direct_iface: None,
                    broadcast: false,
                };
            };
            let identity = destination.lock().await.identity;
            let salt = identity.address_hash.as_slice();
            let ratchet = self
                .ratchet_store
                .as_mut()
                .and_then(|store| store.get(&packet.destination));
            let public_key = ratchet.map(PublicKey::from).unwrap_or(identity.public_key);
            match encrypt_for_public_key(&public_key, salt, packet.data.as_slice(), OsRng) {
                Ok(ciphertext) => {
                    let mut buffer = PacketDataBuffer::new();
                    if buffer.write(&ciphertext).is_err() {
                        log::warn!(
                            "tp({}): ciphertext too large for packet to {}",
                            self.config.name,
                            packet.destination
                        );
                        return SendPacketTrace {
                            outcome: SendPacketOutcome::DroppedCiphertextTooLarge,
                            direct_iface: None,
                            broadcast: false,
                        };
                    }
                    packet.data = buffer;
                }
                Err(err) => {
                    log::warn!(
                        "tp({}): encrypt failed for {}: {:?}",
                        self.config.name,
                        packet.destination,
                        err
                    );
                    return SendPacketTrace {
                        outcome: SendPacketOutcome::DroppedEncryptFailed,
                        direct_iface: None,
                        broadcast: false,
                    };
                }
            }
        }

        let (packet, maybe_iface) = self.path_table.handle_packet(&packet);
        if let Some(iface) = maybe_iface {
            self.send(TxMessage {
                tx_type: TxMessageType::Direct(iface),
                packet,
            })
            .await;
            SendPacketTrace {
                outcome: SendPacketOutcome::SentDirect,
                direct_iface: Some(iface),
                broadcast: false,
            }
        } else if self.config.broadcast || packet.header.packet_type == PacketType::Announce {
            self.send(TxMessage {
                tx_type: TxMessageType::Broadcast(None),
                packet,
            })
            .await;
            SendPacketTrace {
                outcome: SendPacketOutcome::SentBroadcast,
                direct_iface: None,
                broadcast: true,
            }
        } else {
            log::trace!(
                "tp({}): no route for outbound packet dst={}",
                self.config.name,
                packet.destination
            );
            SendPacketTrace {
                outcome: SendPacketOutcome::DroppedNoRoute,
                direct_iface: None,
                broadcast: false,
            }
        }
    }

    pub(super) async fn send(&self, message: TxMessage) {
        self.packet_cache.lock().await.update(&message.packet);
        self.iface_manager.lock().await.send(message).await;
    }

    pub(super) fn has_destination(&self, address: &AddressHash) -> bool {
        self.single_in_destinations.contains_key(address)
    }

    pub(super) fn knows_destination(&self, address: &AddressHash) -> bool {
        self.single_out_destinations.contains_key(address)
    }

    pub(super) async fn filter_duplicate_packets(&self, packet: &Packet) -> bool {
        let mut allow_duplicate = false;

        match packet.header.packet_type {
            PacketType::Announce => {
                return true;
            }
            PacketType::LinkRequest => {
                allow_duplicate = true;
            }
            PacketType::Data => {
                allow_duplicate = packet.context == PacketContext::KeepAlive;
            }
            PacketType::Proof => {
                if packet.context == PacketContext::LinkRequestProof {
                    if let Some(link) = self.in_links.get(&packet.destination) {
                        if link.lock().await.status().not_yet_active() {
                            allow_duplicate = true;
                        }
                    }
                }
            }
        }

        let is_new = self.packet_cache.lock().await.update(packet);

        is_new || allow_duplicate
    }

    pub(super) async fn request_path(
        &mut self,
        address: &AddressHash,
        on_iface: Option<AddressHash>,
        tag: Option<TagBytes>,
    ) {
        let packet = self.path_requests.generate(address, tag);

        self.send(TxMessage {
            tx_type: TxMessageType::Broadcast(on_iface),
            packet,
        })
        .await;
    }
}