Files
nym/nym-node/src/node/lp/data/handler/pipeline/wire.rs
T
2026-05-28 14:09:36 +02:00

132 lines
4.4 KiB
Rust

// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
//! Wire wrapping / unwrapping
//!
//! Everything below the application-message layer (LP packet encode/decode and
//! fragment reassembly) is identical between roles, so it lives here. The
//! per-role pipelines embed a [`WirePipeline`] and delegate their
//! [`Framing`]/[`Transport`]/[`*Unwrap`] trait methods to it.
//!
//! [`Framing`]: nym_lp_data::common::traits::Framing
//! [`Transport`]: nym_lp_data::common::traits::Transport
use std::{net::SocketAddr, sync::Arc, time::Instant};
use nym_lp_data::{
AddressedTimedData, TimedData,
fragmentation::fragment::fragment_lp_message,
packet::{
EncryptedLpPacket, LpFrame, LpHeader, LpPacket, MalformedLpPacketError, frame::LpFrameKind,
version,
},
};
use rand::Rng;
use tracing::warn;
use crate::node::lp::data::shared::SharedLpDataState;
/// Wire-layer pipeline: handles LP packet encode/decode and fragment
/// reassembly. Has no knowledge of the application message type carried in
/// the frame.
pub struct WirePipeline<R> {
state: Arc<SharedLpDataState>,
rng: R,
}
impl<R: Rng> WirePipeline<R> {
pub fn new(state: Arc<SharedLpDataState>, rng: R) -> Self {
Self { state, rng }
}
/// Wrap an [`LpFrame`] into an [`EncryptedLpPacket`] for the wire.
pub fn frame_to_packet(
&mut self,
frame: AddressedTimedData<LpFrame>,
) -> AddressedTimedData<EncryptedLpPacket> {
// Here be LP encryption. For now, just wrap into an EncryptedLpPacket; we don't care at reception anyway
frame.data_transform(|f| LpPacket::new(LpHeader::new(0, 0, version::CURRENT), f).encode())
}
/// Unwrap an [`EncryptedLpPacket`] off the wire into an [`LpFrame`].
pub fn packet_to_frame(
&mut self,
packet: EncryptedLpPacket,
timestamp: Instant,
) -> Result<TimedData<LpFrame>, MalformedLpPacketError> {
// Here be LP decryption. For now we do as is, it's not encrypted
let lp_packet = LpPacket::decode(packet).inspect_err(|_| {
self.state.malformed_packet();
})?;
Ok(TimedData {
timestamp,
data: lp_packet.into_frame(),
})
}
/// Wrap an [`LpFrame`] into one or more addressed frames, fragmenting it
/// if its serialized length would exceed `frame_size`.
pub fn message_to_frame(
&mut self,
timestamp: Instant,
frame: LpFrame,
dst: SocketAddr,
frame_size: usize,
) -> Vec<AddressedTimedData<LpFrame>> {
let output_frames = if frame.len() > frame_size {
fragment_lp_message(&mut self.rng, frame, frame_size)
.into_iter()
.map(|f| f.into_lp_frame())
.collect()
} else {
vec![frame]
};
output_frames
.into_iter()
.map(|f| AddressedTimedData::new_addressed(timestamp, f, dst))
.collect()
}
/// If the frame carries a fragment, attempt reassembly; otherwise return
/// the frame as-is. Returns `None` when more fragments are needed or
/// reassembly fails. The returned frame is guaranteed not to be a
/// fragment.
pub fn frame_to_maybe_message(
&mut self,
frame: TimedData<LpFrame>,
) -> Option<TimedData<LpFrame>> {
let reassembled = if frame.data.kind() == LpFrameKind::FragmentedData {
let fragment = frame
.data
.try_into()
.inspect_err(|e| {
tracing::error!("Failed to recover a fragment : {e}");
self.state.malformed_packet();
})
.ok()?;
let message = self
.state
.message_reconstructor
.insert_new_fragment(fragment, frame.timestamp)?
.inspect_err(|e| {
tracing::error!("Failed to recover a frame : {e}");
self.state.malformed_packet();
})
.ok()?;
TimedData::new(frame.timestamp, message)
} else {
frame
};
if reassembled.data.kind() == LpFrameKind::FragmentedData {
warn!(
"Fragmented data inside fragmented data, it shouldn't happen. Dropping the message"
);
None
} else {
Some(reassembled)
}
}
}