Skip to main content

mctx_core/
config.rs

1use crate::error::MctxError;
2use std::net::{Ipv4Addr, SocketAddrV4};
3
4/// Configuration for one multicast publication socket.
5#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6pub struct PublicationConfig {
7    /// The destination multicast group.
8    pub group: Ipv4Addr,
9    /// The destination UDP port.
10    pub dst_port: u16,
11    /// The local interface to use for multicast egress, if explicitly set.
12    pub interface: Option<Ipv4Addr>,
13    /// The source UDP port to bind before sending, if explicitly set.
14    pub source_port: Option<u16>,
15    /// The multicast TTL for transmitted packets.
16    pub ttl: u32,
17    /// Whether outbound multicast packets should be looped back to the local host.
18    pub loopback: bool,
19}
20
21impl PublicationConfig {
22    /// Creates a basic multicast publication configuration.
23    pub fn new(group: Ipv4Addr, port: u16) -> Self {
24        Self {
25            group,
26            dst_port: port,
27            interface: None,
28            source_port: None,
29            ttl: 1,
30            loopback: true,
31        }
32    }
33
34    /// Validates the configuration and returns an error if it is not usable.
35    pub fn validate(&self) -> Result<(), MctxError> {
36        if self.dst_port == 0 {
37            return Err(MctxError::InvalidDestinationPort);
38        }
39
40        if !self.group.is_multicast() {
41            return Err(MctxError::InvalidMulticastGroup);
42        }
43
44        if matches!(self.source_port, Some(0)) {
45            return Err(MctxError::InvalidSourcePort);
46        }
47
48        if let Some(interface) = self.interface
49            && interface.is_multicast()
50        {
51            return Err(MctxError::InvalidInterfaceAddress);
52        }
53
54        Ok(())
55    }
56
57    /// Sets the multicast egress interface.
58    pub fn with_interface(mut self, interface: Ipv4Addr) -> Self {
59        self.interface = Some(interface);
60        self
61    }
62
63    /// Sets the source UDP port.
64    pub fn with_source_port(mut self, source_port: u16) -> Self {
65        self.source_port = Some(source_port);
66        self
67    }
68
69    /// Sets the multicast TTL.
70    pub fn with_ttl(mut self, ttl: u32) -> Self {
71        self.ttl = ttl;
72        self
73    }
74
75    /// Enables or disables multicast loopback.
76    pub fn with_loopback(mut self, loopback: bool) -> Self {
77        self.loopback = loopback;
78        self
79    }
80
81    /// Returns the configured destination socket address.
82    pub fn destination(&self) -> SocketAddrV4 {
83        SocketAddrV4::new(self.group, self.dst_port)
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn valid_multicast_config_passes_validation() {
93        let cfg = PublicationConfig::new(Ipv4Addr::new(239, 1, 2, 3), 5000)
94            .with_source_port(5001)
95            .with_ttl(8)
96            .with_loopback(false);
97
98        assert!(cfg.validate().is_ok());
99    }
100
101    #[test]
102    fn port_zero_fails_validation() {
103        let cfg = PublicationConfig::new(Ipv4Addr::new(239, 1, 2, 3), 0);
104
105        let result = cfg.validate();
106
107        assert!(matches!(result, Err(MctxError::InvalidDestinationPort)));
108    }
109
110    #[test]
111    fn non_multicast_group_fails_validation() {
112        let cfg = PublicationConfig::new(Ipv4Addr::new(192, 168, 1, 10), 5000);
113
114        let result = cfg.validate();
115
116        assert!(matches!(result, Err(MctxError::InvalidMulticastGroup)));
117    }
118
119    #[test]
120    fn multicast_interface_fails_validation() {
121        let cfg = PublicationConfig::new(Ipv4Addr::new(239, 1, 2, 3), 5000)
122            .with_interface(Ipv4Addr::new(239, 9, 9, 9));
123
124        let result = cfg.validate();
125
126        assert!(matches!(result, Err(MctxError::InvalidInterfaceAddress)));
127    }
128}