Skip to main content

libaprs_engine/
transport.rs

1use std::io::{self, Read};
2
3/// Default maximum byte batch accepted by transport helper readers.
4pub const DEFAULT_TRANSPORT_READ_LIMIT: usize = 1024 * 1024;
5
6/// Stable transport diagnostic categories for logs and external systems.
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub enum TransportErrorCode {
9    /// Input exceeded the configured byte limit.
10    OversizedInput,
11    /// Input framing was invalid for the transport.
12    InvalidFrame,
13    /// Transport reached EOF before a complete packet/frame was available.
14    UnexpectedEof,
15    /// Transport operation timed out.
16    Timeout,
17    /// Underlying I/O failed.
18    Io,
19}
20
21impl TransportErrorCode {
22    /// Returns a stable machine-readable diagnostic code.
23    #[must_use]
24    pub const fn code(self) -> &'static str {
25        match self {
26            Self::OversizedInput => "transport.oversized_input",
27            Self::InvalidFrame => "transport.invalid_frame",
28            Self::UnexpectedEof => "transport.unexpected_eof",
29            Self::Timeout => "transport.timeout",
30            Self::Io => "transport.io",
31        }
32    }
33}
34
35/// Common packet-source contract for transport adapters.
36pub trait PacketSource {
37    /// Source-specific error type.
38    type Error;
39
40    /// Reads a bounded batch of packet byte vectors.
41    fn recv_packets(&mut self) -> Result<Vec<Vec<u8>>, Self::Error>;
42}
43
44/// Common packet-sink contract for transport adapters.
45pub trait PacketSink {
46    /// Sink-specific error type.
47    type Error;
48
49    /// Sends one packet byte slice without mutating or normalizing it.
50    fn send_packet(&mut self, packet: &[u8]) -> Result<(), Self::Error>;
51}
52
53/// Reads all available bytes from a reader while enforcing a hard limit.
54pub fn read_all_with_limit(mut reader: impl Read, max_bytes: usize) -> io::Result<Vec<u8>> {
55    let mut input = Vec::new();
56    let mut limited = (&mut reader).take(max_bytes.saturating_add(1) as u64);
57    limited.read_to_end(&mut input)?;
58    if input.len() > max_bytes {
59        return Err(oversized_input_error());
60    }
61    Ok(input)
62}
63
64/// Creates the standard oversized-input transport error.
65#[must_use]
66pub fn oversized_input_error() -> io::Error {
67    io::Error::new(
68        io::ErrorKind::InvalidData,
69        TransportErrorCode::OversizedInput.code(),
70    )
71}
72
73/// Line-oriented packet source for file/stdin style transports.
74#[derive(Clone, Debug, Eq, PartialEq)]
75pub struct LineTransport<'a> {
76    input: &'a [u8],
77}
78
79impl<'a> LineTransport<'a> {
80    /// Creates a transport over newline-separated packet bytes.
81    #[must_use]
82    pub fn new(input: &'a [u8]) -> Self {
83        Self { input }
84    }
85
86    /// Iterates packet lines without trailing CR/LF bytes.
87    #[must_use]
88    pub fn packets(&self) -> Vec<&'a [u8]> {
89        self.input
90            .split(|byte| *byte == b'\n')
91            .map(trim_trailing_carriage_return)
92            .filter(|line| !line.is_empty())
93            .collect()
94    }
95}
96
97impl PacketSource for LineTransport<'_> {
98    type Error = io::Error;
99
100    fn recv_packets(&mut self) -> Result<Vec<Vec<u8>>, Self::Error> {
101        Ok(self.packets().into_iter().map(<[u8]>::to_vec).collect())
102    }
103}
104
105impl PacketSink for Vec<Vec<u8>> {
106    type Error = io::Error;
107
108    fn send_packet(&mut self, packet: &[u8]) -> Result<(), Self::Error> {
109        self.push(packet.to_vec());
110        Ok(())
111    }
112}
113
114fn trim_trailing_carriage_return(line: &[u8]) -> &[u8] {
115    line.strip_suffix(b"\r").unwrap_or(line)
116}