1use crate::config::{
2 SourceFilter, SubscriptionAddressFamily, SubscriptionConfig, validate_multicast_selection,
3};
4use crate::error::McrxError;
5use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct RawSubscriptionConfig {
14 pub group: IpAddr,
16 pub source: SourceFilter,
18 pub interface: Option<IpAddr>,
20 pub interface_index: Option<u32>,
22}
23
24impl RawSubscriptionConfig {
25 pub fn validate(&self) -> Result<(), McrxError> {
27 validate_multicast_selection(
28 self.group,
29 &self.source,
30 self.interface,
31 self.interface_index,
32 )
33 }
34
35 pub fn family(&self) -> SubscriptionAddressFamily {
37 match self.group {
38 IpAddr::V4(_) => SubscriptionAddressFamily::Ipv4,
39 IpAddr::V6(_) => SubscriptionAddressFamily::Ipv6,
40 }
41 }
42
43 pub fn is_ipv4(&self) -> bool {
45 matches!(self.family(), SubscriptionAddressFamily::Ipv4)
46 }
47
48 pub fn is_ipv6(&self) -> bool {
50 matches!(self.family(), SubscriptionAddressFamily::Ipv6)
51 }
52
53 pub fn source_addr(&self) -> Option<IpAddr> {
55 match self.source {
56 SourceFilter::Any => None,
57 SourceFilter::Source(source) => Some(source),
58 }
59 }
60
61 pub fn asm(group: Ipv4Addr) -> Self {
63 Self::asm_ip(group.into())
64 }
65
66 pub fn asm_v6(group: Ipv6Addr) -> Self {
68 Self::asm_ip(group.into())
69 }
70
71 pub fn asm_ip(group: IpAddr) -> Self {
73 Self {
74 group,
75 source: SourceFilter::Any,
76 interface: None,
77 interface_index: None,
78 }
79 }
80
81 pub fn ssm(group: Ipv4Addr, source: Ipv4Addr) -> Self {
83 Self::ssm_ip(group.into(), source.into())
84 }
85
86 pub fn ssm_v6(group: Ipv6Addr, source: Ipv6Addr) -> Self {
88 Self::ssm_ip(group.into(), source.into())
89 }
90
91 pub fn ssm_ip(group: IpAddr, source: IpAddr) -> Self {
93 Self {
94 group,
95 source: SourceFilter::Source(source),
96 interface: None,
97 interface_index: None,
98 }
99 }
100
101 pub(crate) fn membership_compat_config(&self) -> SubscriptionConfig {
102 SubscriptionConfig {
103 group: self.group,
104 source: self.source.clone(),
105 dst_port: 1,
106 interface: self.interface,
107 interface_index: self.interface_index,
108 }
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn valid_ipv4_raw_config_passes_validation() {
118 let cfg = RawSubscriptionConfig::asm(Ipv4Addr::new(239, 1, 2, 3));
119 assert!(cfg.validate().is_ok());
120 }
121
122 #[test]
123 fn valid_ipv4_ssm_raw_config_passes_validation() {
124 let cfg =
125 RawSubscriptionConfig::ssm(Ipv4Addr::new(232, 1, 2, 3), Ipv4Addr::new(10, 0, 0, 1));
126 assert!(cfg.validate().is_ok());
127 }
128
129 #[test]
130 fn ipv4_raw_ssm_requires_232_range() {
131 let cfg =
132 RawSubscriptionConfig::ssm(Ipv4Addr::new(239, 1, 1, 1), Ipv4Addr::new(10, 0, 0, 1));
133
134 let result = cfg.validate();
135 assert!(matches!(result, Err(McrxError::InvalidIpv4SsmGroup)));
136 }
137
138 #[test]
139 fn valid_ipv6_ssm_raw_config_passes_validation() {
140 let cfg = RawSubscriptionConfig::ssm_v6(
141 "ff3e::8000:1234".parse().unwrap(),
142 "2001:db8::10".parse().unwrap(),
143 );
144 assert!(cfg.validate().is_ok());
145 }
146
147 #[test]
148 fn ipv6_raw_ssm_requires_ff3x_group_range() {
149 let cfg = RawSubscriptionConfig::ssm_v6(
150 "ff12::1234".parse().unwrap(),
151 "2001:db8::10".parse().unwrap(),
152 );
153
154 let result = cfg.validate();
155 assert!(matches!(result, Err(McrxError::InvalidIpv6SsmGroup)));
156 }
157
158 #[test]
159 fn ipv4_raw_config_rejects_interface_index() {
160 let mut cfg = RawSubscriptionConfig::asm(Ipv4Addr::new(239, 1, 2, 3));
161 cfg.interface_index = Some(7);
162
163 let result = cfg.validate();
164 assert!(matches!(result, Err(McrxError::InterfaceIndexRequiresIpv6)));
165 }
166
167 #[test]
168 fn raw_asm_rejects_ssm_reserved_groups() {
169 let ipv4 = RawSubscriptionConfig::asm(Ipv4Addr::new(232, 1, 2, 3));
170 let ipv6 = RawSubscriptionConfig::asm_v6("ff3e::8000:1234".parse().unwrap());
171
172 assert!(matches!(
173 ipv4.validate(),
174 Err(McrxError::SsmGroupRequiresSource)
175 ));
176 assert!(matches!(
177 ipv6.validate(),
178 Err(McrxError::SsmGroupRequiresSource)
179 ));
180 }
181}