Skip to main content

mcrx_core/
packet.rs

1use crate::subscription::SubscriptionId;
2use bytes::Bytes;
3use std::net::{IpAddr, SocketAddr};
4
5/// A received packet together with the metadata needed by the receiver core.
6#[derive(Debug, Clone)]
7pub struct Packet {
8    /// The subscription through which this packet was received.
9    pub subscription_id: SubscriptionId,
10    /// The remote sender's source address and source port.
11    pub source: SocketAddr,
12    /// The destination multicast group address.
13    pub group: IpAddr,
14    /// The destination UDP port on which the packet was received.
15    pub dst_port: u16,
16    /// The raw UDP payload bytes.
17    pub payload: Bytes,
18}
19
20/// Structured receive metadata that can grow as the platform layer learns more
21/// about the packet delivery context.
22#[derive(Debug, Clone, PartialEq, Eq)]
23#[non_exhaustive]
24pub struct ReceiveMetadata {
25    /// The local socket address currently bound by the receiving socket, if known.
26    pub socket_local_addr: Option<SocketAddr>,
27    /// The local interface requested by the subscription configuration, if any.
28    ///
29    /// This reflects configured intent, not pktinfo-derived ingress state.
30    pub configured_interface: Option<IpAddr>,
31    /// The local IPv6 interface index requested by the subscription configuration, if any.
32    pub configured_interface_index: Option<u32>,
33    /// The local destination IP address from pktinfo-style metadata, if available.
34    pub destination_local_ip: Option<IpAddr>,
35    /// The ingress interface index from pktinfo-style metadata, if available.
36    pub ingress_interface_index: Option<u32>,
37}
38
39impl ReceiveMetadata {
40    #[cfg_attr(
41        any(
42            not(feature = "raw-packets"),
43            not(any(target_os = "linux", target_os = "macos", windows))
44        ),
45        allow(dead_code)
46    )]
47    pub(crate) fn empty() -> Self {
48        Self {
49            socket_local_addr: None,
50            configured_interface: None,
51            configured_interface_index: None,
52            destination_local_ip: None,
53            ingress_interface_index: None,
54        }
55    }
56}
57
58/// A received packet together with richer receive metadata.
59#[derive(Debug, Clone)]
60#[non_exhaustive]
61pub struct PacketWithMetadata {
62    /// The packet payload and core addressing information.
63    pub packet: Packet,
64    /// Additional receive context supplied by the platform layer.
65    pub metadata: ReceiveMetadata,
66}
67
68impl Packet {
69    /// Returns the length of the payload in bytes.
70    pub fn payload_len(&self) -> usize {
71        self.payload.len()
72    }
73
74    pub fn payload(&self) -> &[u8] {
75        &self.payload
76    }
77}
78
79impl PacketWithMetadata {
80    /// Returns a read-only reference to the inner packet.
81    pub fn packet(&self) -> &Packet {
82        &self.packet
83    }
84
85    /// Returns a read-only reference to the receive metadata.
86    pub fn metadata(&self) -> &ReceiveMetadata {
87        &self.metadata
88    }
89
90    /// Discards the richer metadata and returns the inner packet.
91    pub fn into_packet(self) -> Packet {
92        self.packet
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99    use std::net::{IpAddr, Ipv4Addr, SocketAddrV4};
100
101    #[test]
102    fn packet_payload_len_returns_correct_length() {
103        let packet = Packet {
104            subscription_id: SubscriptionId(1),
105            source: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 1), 12345)),
106            group: IpAddr::V4(Ipv4Addr::new(239, 1, 2, 3)),
107            dst_port: 5000,
108            payload: Bytes::from_static(&[1, 2, 3]),
109        };
110
111        assert_eq!(packet.payload_len(), 3);
112    }
113
114    #[test]
115    fn subscription_id_equality_works() {
116        let a = SubscriptionId(7);
117        let b = SubscriptionId(7);
118        let c = SubscriptionId(8);
119
120        assert_eq!(a, b);
121        assert_ne!(a, c);
122    }
123
124    #[test]
125    fn packet_with_metadata_into_packet_discards_metadata() {
126        let packet = Packet {
127            subscription_id: SubscriptionId(1),
128            source: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 1), 12345)),
129            group: IpAddr::V4(Ipv4Addr::new(239, 1, 2, 3)),
130            dst_port: 5000,
131            payload: Bytes::from_static(&[1, 2, 3]),
132        };
133
134        let detailed = PacketWithMetadata {
135            packet: packet.clone(),
136            metadata: ReceiveMetadata {
137                socket_local_addr: Some(SocketAddr::V4(SocketAddrV4::new(
138                    Ipv4Addr::UNSPECIFIED,
139                    5000,
140                ))),
141                configured_interface: None,
142                configured_interface_index: None,
143                destination_local_ip: None,
144                ingress_interface_index: None,
145            },
146        };
147
148        let stripped = detailed.into_packet();
149
150        assert_eq!(stripped.subscription_id, packet.subscription_id);
151        assert_eq!(stripped.source, packet.source);
152        assert_eq!(stripped.group, packet.group);
153        assert_eq!(stripped.dst_port, packet.dst_port);
154        assert_eq!(stripped.payload, packet.payload);
155    }
156}