1use crate::api_objects::{
3 Error, MetaData, Result, SclName, SclObject, SeparationContext, TransitionError,
4};
5use serde::{Deserialize, Serialize};
6use std::net::Ipv4Addr;
7
8#[cfg(feature = "openapi")]
9use aide::OperationIo;
10#[cfg(feature = "openapi")]
11use schemars::JsonSchema;
12
13#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
15#[cfg_attr(feature = "openapi", derive(OperationIo, JsonSchema))]
16#[cfg_attr(feature = "openapi", aide(output))]
17#[serde(rename_all = "camelCase")]
18pub struct Router {
19 #[serde(flatten)]
20 pub metadata: MetaData,
21
22 pub separation_context: SclName,
24
25 pub internal_ip_netmask: Ipv4Addr,
29
30 pub internal_ip: Ipv4Addr,
35
36 pub external_ip: Ipv4Addr,
40
41 #[serde(default)]
46 pub status: RouterStatus,
47
48 #[serde(default)]
53 pub forwarded_tcp_ports: Vec<ForwardedPort>,
54
55 #[serde(default)]
60 pub forwarded_udp_ports: Vec<ForwardedPort>,
61}
62
63#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
64#[cfg_attr(feature = "openapi", derive(OperationIo, JsonSchema))]
65#[cfg_attr(feature = "openapi", aide(output))]
66#[serde(rename_all = "camelCase")]
67pub struct ForwardedPort {
68 pub src_port: u16,
69 pub dst_ip: Ipv4Addr,
70 pub dst_port: u16,
71}
72
73#[derive(Clone, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
74#[cfg_attr(feature = "openapi", derive(OperationIo, JsonSchema))]
75#[cfg_attr(feature = "openapi", aide(output))]
76#[serde(rename_all = "camelCase")]
77pub enum RouterStatus {
78 #[default]
80 Pending,
81
82 Assigned(AssignedDeviceNames),
95}
96
97#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
98#[cfg_attr(feature = "openapi", derive(OperationIo, JsonSchema))]
99#[cfg_attr(feature = "openapi", aide(output))]
100#[serde(rename_all = "camelCase")]
101pub struct AssignedDeviceNames {
102 pub veth_name_in_sc_netns: String,
104
105 pub veth_name_in_gateway_netns: String,
107}
108
109impl SclObject for Router {
110 const PREFIX: &'static str = "/routers";
112
113 fn name(&self) -> &SclName {
114 &self.metadata.name
115 }
116
117 fn separation_context(&self) -> Option<&SclName> {
118 Some(&self.separation_context)
119 }
120
121 fn metadata(&self) -> &MetaData {
122 &self.metadata
123 }
124
125 fn metadata_mut(&mut self) -> &mut MetaData {
126 &mut self.metadata
127 }
128
129 fn referenced_db_keys(&self) -> Vec<String> {
130 vec![SeparationContext::db_key(
131 self.separation_context.as_str(),
132 None,
133 )]
134 }
135
136 fn validate_fields_before_create(&self) -> Result<()> {
137 self.metadata.validate_fields_before_create()?;
138
139 if self.status != RouterStatus::default() {
140 return Err(Error::IllegalInitialValue(
141 "Illegal initial router status!".to_string(),
142 ));
143 }
144
145 Ok(())
148 }
149
150 fn validate_fields_before_update(
151 current_db_state: &Self,
152 proposed_new_state: &Self,
153 ) -> Result<()> {
154 MetaData::validate_fields_before_regular_update(
155 ¤t_db_state.metadata,
156 &proposed_new_state.metadata,
157 )?;
158
159 validate_status_transition(¤t_db_state.status, &proposed_new_state.status)?;
160
161 if current_db_state.external_ip != proposed_new_state.external_ip
163 || current_db_state.internal_ip != proposed_new_state.internal_ip
164 || current_db_state.internal_ip_netmask != proposed_new_state.internal_ip_netmask
165 || current_db_state.forwarded_tcp_ports != proposed_new_state.forwarded_tcp_ports
166 || current_db_state.forwarded_udp_ports != proposed_new_state.forwarded_udp_ports
167 || current_db_state.separation_context != proposed_new_state.separation_context
168 {
169 Err(TransitionError::Other("Only status field may be changed!".to_string()).into())
170 } else {
171 Ok(())
172 }
173 }
174}
175
176fn validate_status_transition(old_state: &RouterStatus, new_state: &RouterStatus) -> Result<()> {
178 use RouterStatus::*;
180 let err = Err(TransitionError::Other("Invalid status transition!".to_string()).into());
181 match (old_state, new_state) {
182 (Assigned(a), Assigned(b)) if a != b => return err,
183 (Assigned(_), Pending) => return err,
184 _ => (),
185 };
186
187 if let Assigned(names) = new_state {
189 if names.veth_name_in_sc_netns.is_empty() || names.veth_name_in_gateway_netns.is_empty() {
190 return Err(TransitionError::Other(
191 "Assigned device names must not be empty".to_string(),
192 )
193 .into());
194 }
195 }
196
197 Ok(())
198}
199
200#[cfg(test)]
201mod test {
202 use crate::api_objects::router::{validate_status_transition, AssignedDeviceNames};
203 use crate::api_objects::test::{
204 detect_invalid_metadata_create_mutations, detect_invalid_metadata_update_mutations,
205 detect_invalid_update_mutations, parse_json_dir,
206 };
207 use crate::api_objects::{ForwardedPort, MetaData, Router, RouterStatus, SclName, SclObject};
208 use std::net::Ipv4Addr;
209 use std::path::Path;
210
211 #[test]
212 fn parse_sample_json() {
213 parse_json_dir::<Router>(Path::new("../test/sample_json/routers"));
214 }
215
216 fn example_router() -> Router {
217 Router {
218 external_ip: "127.0.0.1".parse().unwrap(),
219 internal_ip: "127.0.0.1".parse().unwrap(),
220 internal_ip_netmask: "255.255.255.0".parse().unwrap(),
221 metadata: MetaData::new(SclName::try_from("example").unwrap()),
222 separation_context: SclName::try_from("example").unwrap(),
223 status: Default::default(),
224 forwarded_tcp_ports: Vec::new(),
225 forwarded_udp_ports: Vec::new(),
226 }
227 }
228
229 #[test]
230 fn validate_fields_before_create() {
231 let mut router = example_router();
232 assert!(router.validate_fields_before_create().is_ok());
233 router.status = RouterStatus::Assigned(AssignedDeviceNames {
234 veth_name_in_sc_netns: "sc-veth".into(),
235 veth_name_in_gateway_netns: "gateway-veth".into(),
236 });
237 assert!(router.validate_fields_before_create().is_err());
238 }
239
240 #[test]
241 fn test_validate_status_transition() {
242 use RouterStatus::*;
243
244 let assigned_status = |a: &str, b: &str| {
245 Assigned(AssignedDeviceNames {
246 veth_name_in_sc_netns: a.to_string(),
247 veth_name_in_gateway_netns: b.to_string(),
248 })
249 };
250
251 let bad_transitions = vec![
252 (Pending, assigned_status("", "")),
253 (assigned_status("a", "a"), Pending),
254 (assigned_status("a", "a"), assigned_status("a", "b")),
255 (assigned_status("a", "a"), assigned_status("b", "a")),
256 ];
257
258 for (old_state, new_state) in bad_transitions {
259 assert!(validate_status_transition(&old_state, &new_state).is_err());
260 }
261
262 let good_transitions = vec![
263 (Pending, Pending),
264 (Pending, assigned_status("a", "a")),
265 (assigned_status("a", "a"), assigned_status("a", "a")),
266 ];
267
268 for (old_state, new_state) in good_transitions {
269 assert!(validate_status_transition(&old_state, &new_state).is_ok());
270 }
271 }
272
273 #[test]
274 fn validate_fields_before_update() {
275 let mut current = example_router();
276 let proposed = example_router();
277 assert!(Router::validate_fields_before_update(¤t, &proposed).is_ok());
278
279 current.status = RouterStatus::Assigned(AssignedDeviceNames {
280 veth_name_in_sc_netns: "sc-veth".into(),
281 veth_name_in_gateway_netns: "gateway-veth".into(),
282 });
283
284 #[allow(clippy::type_complexity)]
285 let invalid_mutations: Vec<Box<dyn Fn(&mut Router)>> = vec![
286 Box::new(|t| t.separation_context = SclName::try_from("different").unwrap()),
287 Box::new(|t| t.internal_ip = "127.0.0.2".parse().unwrap()),
288 Box::new(|t| t.external_ip = "127.0.0.2".parse().unwrap()),
289 Box::new(|t| t.internal_ip_netmask = "127.0.0.2".parse().unwrap()),
290 Box::new(|t| t.status = RouterStatus::Pending),
291 Box::new(|t| {
292 t.forwarded_tcp_ports = vec![ForwardedPort {
293 src_port: 8008,
294 dst_ip: Ipv4Addr::new(192, 168, 30, 10),
295 dst_port: 22,
296 }]
297 }),
298 Box::new(|t| {
299 t.forwarded_udp_ports = vec![ForwardedPort {
300 src_port: 7000,
301 dst_ip: Ipv4Addr::new(192, 168, 30, 10),
302 dst_port: 22,
303 }]
304 }),
305 ];
306
307 detect_invalid_update_mutations(¤t, &proposed, invalid_mutations);
308 }
309
310 #[test]
311 fn detect_invalid_metadata_mutations() {
312 let t = example_router();
313 detect_invalid_metadata_create_mutations(&t);
314 detect_invalid_metadata_update_mutations(&t);
315 }
316}