1use crate::error::MctxError;
2use std::net::{Ipv4Addr, SocketAddrV4};
3
4#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6pub struct PublicationConfig {
7 pub group: Ipv4Addr,
9 pub dst_port: u16,
11 pub interface: Option<Ipv4Addr>,
13 pub source_port: Option<u16>,
15 pub source_addr: Option<Ipv4Addr>,
17 pub ttl: u32,
19 pub loopback: bool,
21}
22
23impl PublicationConfig {
24 pub fn new(group: Ipv4Addr, port: u16) -> Self {
26 Self {
27 group,
28 dst_port: port,
29 interface: None,
30 source_port: None,
31 source_addr: None,
32 ttl: 1,
33 loopback: true,
34 }
35 }
36
37 pub fn validate(&self) -> Result<(), MctxError> {
39 if self.dst_port == 0 {
40 return Err(MctxError::InvalidDestinationPort);
41 }
42
43 if !self.group.is_multicast() {
44 return Err(MctxError::InvalidMulticastGroup);
45 }
46
47 if matches!(self.source_port, Some(0)) {
48 return Err(MctxError::InvalidSourcePort);
49 }
50
51 if let Some(source_addr) = self.source_addr
52 && (source_addr.is_multicast() || source_addr.is_unspecified())
53 {
54 return Err(MctxError::InvalidSourceAddress);
55 }
56
57 if let Some(interface) = self.interface
58 && interface.is_multicast()
59 {
60 return Err(MctxError::InvalidInterfaceAddress);
61 }
62
63 Ok(())
64 }
65
66 pub fn with_interface(mut self, interface: Ipv4Addr) -> Self {
68 self.interface = Some(interface);
69 self
70 }
71
72 pub fn with_source_port(mut self, source_port: u16) -> Self {
74 self.source_port = Some(source_port);
75 self
76 }
77
78 pub fn with_source_addr(mut self, source_addr: Ipv4Addr) -> Self {
80 self.source_addr = Some(source_addr);
81 self
82 }
83
84 pub fn with_bind_addr(mut self, bind_addr: SocketAddrV4) -> Self {
86 self.source_addr = Some(*bind_addr.ip());
87 self.source_port = Some(bind_addr.port());
88 self
89 }
90
91 pub fn with_ttl(mut self, ttl: u32) -> Self {
93 self.ttl = ttl;
94 self
95 }
96
97 pub fn with_loopback(mut self, loopback: bool) -> Self {
99 self.loopback = loopback;
100 self
101 }
102
103 pub fn destination(&self) -> SocketAddrV4 {
105 SocketAddrV4::new(self.group, self.dst_port)
106 }
107
108 pub fn bind_addr(&self) -> Option<SocketAddrV4> {
110 if self.source_addr.is_none() && self.source_port.is_none() {
111 return None;
112 }
113
114 Some(SocketAddrV4::new(
115 self.source_addr.unwrap_or(Ipv4Addr::UNSPECIFIED),
116 self.source_port.unwrap_or(0),
117 ))
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn valid_multicast_config_passes_validation() {
127 let cfg = PublicationConfig::new(Ipv4Addr::new(239, 1, 2, 3), 5000)
128 .with_source_port(5001)
129 .with_source_addr(Ipv4Addr::new(192, 168, 10, 5))
130 .with_ttl(8)
131 .with_loopback(false);
132
133 assert!(cfg.validate().is_ok());
134 }
135
136 #[test]
137 fn port_zero_fails_validation() {
138 let cfg = PublicationConfig::new(Ipv4Addr::new(239, 1, 2, 3), 0);
139
140 let result = cfg.validate();
141
142 assert!(matches!(result, Err(MctxError::InvalidDestinationPort)));
143 }
144
145 #[test]
146 fn non_multicast_group_fails_validation() {
147 let cfg = PublicationConfig::new(Ipv4Addr::new(192, 168, 1, 10), 5000);
148
149 let result = cfg.validate();
150
151 assert!(matches!(result, Err(MctxError::InvalidMulticastGroup)));
152 }
153
154 #[test]
155 fn multicast_interface_fails_validation() {
156 let cfg = PublicationConfig::new(Ipv4Addr::new(239, 1, 2, 3), 5000)
157 .with_interface(Ipv4Addr::new(239, 9, 9, 9));
158
159 let result = cfg.validate();
160
161 assert!(matches!(result, Err(MctxError::InvalidInterfaceAddress)));
162 }
163
164 #[test]
165 fn unspecified_source_addr_fails_validation() {
166 let cfg = PublicationConfig::new(Ipv4Addr::new(239, 1, 2, 3), 5000)
167 .with_source_addr(Ipv4Addr::UNSPECIFIED);
168
169 let result = cfg.validate();
170
171 assert!(matches!(result, Err(MctxError::InvalidSourceAddress)));
172 }
173
174 #[test]
175 fn bind_addr_builder_sets_source_fields() {
176 let bind_addr = SocketAddrV4::new(Ipv4Addr::new(10, 1, 2, 3), 5001);
177 let cfg =
178 PublicationConfig::new(Ipv4Addr::new(239, 1, 2, 3), 5000).with_bind_addr(bind_addr);
179
180 assert_eq!(cfg.source_addr, Some(Ipv4Addr::new(10, 1, 2, 3)));
181 assert_eq!(cfg.source_port, Some(5001));
182 assert_eq!(cfg.bind_addr(), Some(bind_addr));
183 }
184}