1use std::{env, fmt, time::Duration};
15
16use regex::Regex;
17use serde::{de, de::Visitor, ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer};
18use tracing::warn;
19use zenoh::{key_expr::OwnedKeyExpr, qos::Priority};
20
21pub const DEFAULT_NAMESPACE: &str = "/";
22pub const DEFAULT_NODENAME: &str = "zenoh_bridge_ros2dds";
23pub const DEFAULT_DOMAIN: u32 = 0;
24pub const DEFAULT_RELIABLE_ROUTES_BLOCKING: bool = true;
25pub const DEFAULT_TRANSIENT_LOCAL_CACHE_MULTIPLIER: usize = 10;
26pub const DEFAULT_DDS_LOCALHOST_ONLY: bool = false;
27pub const DEFAULT_QUERIES_TIMEOUT: f32 = 5.0;
28pub const DEFAULT_ACTION_GET_RESULT_TIMEOUT: f32 = 300.0;
32pub const DEFAULT_WORK_THREAD_NUM: usize = 2;
33pub const DEFAULT_MAX_BLOCK_THREAD_NUM: usize = 50;
34
35#[derive(Deserialize, Debug, Serialize)]
36#[serde(deny_unknown_fields)]
37pub struct Config {
38 #[serde(default = "default_namespace")]
39 pub namespace: String,
40 #[serde(default = "default_nodename")]
41 pub nodename: OwnedKeyExpr,
42 #[serde(default = "default_domain")]
43 pub domain: u32,
44 #[serde(default = "default_localhost_only")]
45 pub ros_localhost_only: bool,
46 #[serde(
47 default = "default_automatic_discovery_range",
48 deserialize_with = "deserialize_automatic_discovery_range"
49 )]
50 pub ros_automatic_discovery_range: Option<RosAutomaticDiscoveryRange>,
51 #[serde(default, deserialize_with = "deserialize_static_peers")]
52 pub ros_static_peers: Option<Vec<String>>,
53 #[serde(default, flatten)]
54 pub allowance: Option<Allowance>,
55 #[serde(
56 default,
57 deserialize_with = "deserialize_vec_regex_f32",
58 serialize_with = "serialize_vec_regex_f32"
59 )]
60 pub pub_max_frequencies: Vec<(Regex, f32)>,
61 #[serde(default)]
62 #[cfg(feature = "dds_shm")]
63 pub shm_enabled: bool,
64 #[serde(default = "default_transient_local_cache_multiplier")]
65 pub transient_local_cache_multiplier: usize,
66 #[serde(default = "default_queries_timeout")]
67 pub queries_timeout: Option<QueriesTimeouts>,
68 #[serde(default = "default_reliable_routes_blocking")]
69 pub reliable_routes_blocking: bool,
70 #[serde(
71 default,
72 deserialize_with = "deserialize_vec_regex_prio",
73 serialize_with = "serialize_vec_regex_prio"
74 )]
75 pub pub_priorities: Vec<(Regex, (Priority, bool))>,
76 #[serde(default = "default_work_thread_num")]
77 pub work_thread_num: usize,
78 #[serde(default = "default_max_block_thread_num")]
79 pub max_block_thread_num: usize,
80 __required__: Option<bool>,
81 #[serde(default, deserialize_with = "deserialize_path")]
82 __path__: Option<Vec<String>>,
83}
84
85impl Config {
86 pub fn get_pub_max_frequencies(&self, ros2_name: &str) -> Option<f32> {
87 for (re, freq) in &self.pub_max_frequencies {
88 if re.is_match(ros2_name) {
89 return Some(*freq);
90 }
91 }
92 None
93 }
94
95 pub fn get_pub_priority_and_express(&self, ros2_name: &str) -> Option<(Priority, bool)> {
96 for (re, p) in &self.pub_priorities {
97 if re.is_match(ros2_name) {
98 return Some(*p);
99 }
100 }
101 None
102 }
103
104 pub fn get_queries_timeout_tl_sub(&self, ros2_name: &str) -> Duration {
105 if let Some(qt) = &self.queries_timeout {
106 for (re, secs) in &qt.transient_local_subscribers {
107 if re.is_match(ros2_name) {
108 return Duration::from_secs_f32(*secs);
109 }
110 }
111 return Duration::from_secs_f32(qt.default);
112 }
113 Duration::from_secs_f32(DEFAULT_QUERIES_TIMEOUT)
114 }
115
116 pub fn get_queries_timeout_service(&self, ros2_name: &str) -> Duration {
117 if let Some(qt) = &self.queries_timeout {
118 for (re, secs) in &qt.services {
119 if re.is_match(ros2_name) {
120 return Duration::from_secs_f32(*secs);
121 }
122 }
123 return Duration::from_secs_f32(qt.default);
124 }
125 Duration::from_secs_f32(DEFAULT_QUERIES_TIMEOUT)
126 }
127
128 pub fn get_queries_timeout_action_send_goal(&self, ros2_name: &str) -> Duration {
129 match &self.queries_timeout {
130 Some(QueriesTimeouts {
131 default,
132 actions: Some(at),
133 ..
134 }) => {
135 for (re, secs) in &at.send_goal {
136 if re.is_match(ros2_name) {
137 return Duration::from_secs_f32(*secs);
138 }
139 }
140 Duration::from_secs_f32(*default)
141 }
142 Some(QueriesTimeouts {
143 default,
144 actions: None,
145 ..
146 }) => Duration::from_secs_f32(*default),
147 _ => Duration::from_secs_f32(DEFAULT_QUERIES_TIMEOUT),
148 }
149 }
150
151 pub fn get_queries_timeout_action_cancel_goal(&self, ros2_name: &str) -> Duration {
152 match &self.queries_timeout {
153 Some(QueriesTimeouts {
154 default,
155 actions: Some(at),
156 ..
157 }) => {
158 for (re, secs) in &at.cancel_goal {
159 if re.is_match(ros2_name) {
160 return Duration::from_secs_f32(*secs);
161 }
162 }
163 Duration::from_secs_f32(*default)
164 }
165 Some(QueriesTimeouts {
166 default,
167 actions: None,
168 ..
169 }) => Duration::from_secs_f32(*default),
170 _ => Duration::from_secs_f32(DEFAULT_QUERIES_TIMEOUT),
171 }
172 }
173
174 pub fn get_queries_timeout_action_get_result(&self, ros2_name: &str) -> Duration {
175 match &self.queries_timeout {
176 Some(QueriesTimeouts {
177 actions: Some(at), ..
178 }) => {
179 for (re, secs) in &at.get_result {
180 if re.is_match(ros2_name) {
181 return Duration::from_secs_f32(*secs);
182 }
183 }
184 Duration::from_secs_f32(DEFAULT_ACTION_GET_RESULT_TIMEOUT)
185 }
186 _ => Duration::from_secs_f32(DEFAULT_ACTION_GET_RESULT_TIMEOUT),
187 }
188 }
189}
190
191#[derive(Deserialize, Debug, Serialize)]
192#[serde(deny_unknown_fields)]
193pub struct QueriesTimeouts {
194 #[serde(default = "default_queries_timeout_default")]
195 default: f32,
196 #[serde(
197 default,
198 deserialize_with = "deserialize_vec_regex_f32",
199 serialize_with = "serialize_vec_regex_f32"
200 )]
201 transient_local_subscribers: Vec<(Regex, f32)>,
202 #[serde(
203 default,
204 deserialize_with = "deserialize_vec_regex_f32",
205 serialize_with = "serialize_vec_regex_f32"
206 )]
207 services: Vec<(Regex, f32)>,
208 #[serde(default = "default_actions_timeout")]
209 actions: Option<ActionsTimeouts>,
210}
211
212#[derive(Deserialize, Debug, Serialize)]
213#[serde(deny_unknown_fields)]
214pub struct ActionsTimeouts {
215 #[serde(
216 default,
217 deserialize_with = "deserialize_vec_regex_f32",
218 serialize_with = "serialize_vec_regex_f32"
219 )]
220 send_goal: Vec<(Regex, f32)>,
221 #[serde(
222 default,
223 deserialize_with = "deserialize_vec_regex_f32",
224 serialize_with = "serialize_vec_regex_f32"
225 )]
226 cancel_goal: Vec<(Regex, f32)>,
227 #[serde(
228 default = "default_actions_get_result_timeout",
229 deserialize_with = "deserialize_vec_regex_f32",
230 serialize_with = "serialize_vec_regex_f32"
231 )]
232 get_result: Vec<(Regex, f32)>,
233}
234
235#[derive(Deserialize, Debug, Serialize)]
236pub enum Allowance {
237 #[serde(rename = "allow")]
238 Allow(ROS2InterfacesRegex),
239 #[serde(rename = "deny")]
240 Deny(ROS2InterfacesRegex),
241}
242
243impl Allowance {
244 pub fn is_publisher_allowed(&self, name: &str) -> bool {
245 use Allowance::*;
246 match self {
247 Allow(r) => r
248 .publishers
249 .as_ref()
250 .map(|re| re.is_match(name))
251 .unwrap_or(false),
252 Deny(r) => r
253 .publishers
254 .as_ref()
255 .map(|re| !re.is_match(name))
256 .unwrap_or(true),
257 }
258 }
259
260 pub fn is_subscriber_allowed(&self, name: &str) -> bool {
261 use Allowance::*;
262 match self {
263 Allow(r) => r
264 .subscribers
265 .as_ref()
266 .map(|re| re.is_match(name))
267 .unwrap_or(false),
268 Deny(r) => r
269 .subscribers
270 .as_ref()
271 .map(|re| !re.is_match(name))
272 .unwrap_or(true),
273 }
274 }
275
276 pub fn is_service_srv_allowed(&self, name: &str) -> bool {
277 use Allowance::*;
278 match self {
279 Allow(r) => r
280 .service_servers
281 .as_ref()
282 .map(|re| re.is_match(name))
283 .unwrap_or(false),
284 Deny(r) => r
285 .service_servers
286 .as_ref()
287 .map(|re| !re.is_match(name))
288 .unwrap_or(true),
289 }
290 }
291
292 pub fn is_service_cli_allowed(&self, name: &str) -> bool {
293 use Allowance::*;
294 match self {
295 Allow(r) => r
296 .service_clients
297 .as_ref()
298 .map(|re| re.is_match(name))
299 .unwrap_or(false),
300 Deny(r) => r
301 .service_clients
302 .as_ref()
303 .map(|re| !re.is_match(name))
304 .unwrap_or(true),
305 }
306 }
307
308 pub fn is_action_srv_allowed(&self, name: &str) -> bool {
309 use Allowance::*;
310 match self {
311 Allow(r) => r
312 .action_servers
313 .as_ref()
314 .map(|re| re.is_match(name))
315 .unwrap_or(false),
316 Deny(r) => r
317 .action_servers
318 .as_ref()
319 .map(|re| !re.is_match(name))
320 .unwrap_or(true),
321 }
322 }
323
324 pub fn is_action_cli_allowed(&self, name: &str) -> bool {
325 use Allowance::*;
326 match self {
327 Allow(r) => r
328 .action_clients
329 .as_ref()
330 .map(|re| re.is_match(name))
331 .unwrap_or(false),
332 Deny(r) => r
333 .action_clients
334 .as_ref()
335 .map(|re| !re.is_match(name))
336 .unwrap_or(true),
337 }
338 }
339}
340
341#[derive(Deserialize, Debug, Default, Serialize)]
342pub struct ROS2InterfacesRegex {
343 #[serde(
344 default,
345 deserialize_with = "deserialize_regex",
346 serialize_with = "serialize_regex",
347 skip_serializing_if = "Option::is_none"
348 )]
349 pub publishers: Option<Regex>,
350 #[serde(
351 default,
352 deserialize_with = "deserialize_regex",
353 serialize_with = "serialize_regex",
354 skip_serializing_if = "Option::is_none"
355 )]
356 pub subscribers: Option<Regex>,
357 #[serde(
358 default,
359 deserialize_with = "deserialize_regex",
360 serialize_with = "serialize_regex",
361 skip_serializing_if = "Option::is_none"
362 )]
363 pub service_servers: Option<Regex>,
364 #[serde(
365 default,
366 deserialize_with = "deserialize_regex",
367 serialize_with = "serialize_regex",
368 skip_serializing_if = "Option::is_none"
369 )]
370 pub service_clients: Option<Regex>,
371 #[serde(
372 default,
373 deserialize_with = "deserialize_regex",
374 serialize_with = "serialize_regex",
375 skip_serializing_if = "Option::is_none"
376 )]
377 pub action_servers: Option<Regex>,
378 #[serde(
379 default,
380 deserialize_with = "deserialize_regex",
381 serialize_with = "serialize_regex",
382 skip_serializing_if = "Option::is_none"
383 )]
384 pub action_clients: Option<Regex>,
385}
386
387fn default_namespace() -> String {
388 DEFAULT_NAMESPACE.to_string()
389}
390
391fn default_nodename() -> OwnedKeyExpr {
392 unsafe { OwnedKeyExpr::from_string_unchecked(DEFAULT_NODENAME.into()) }
393}
394
395fn default_domain() -> u32 {
396 if let Ok(s) = env::var("ROS_DOMAIN_ID") {
397 s.parse::<u32>().unwrap_or(DEFAULT_DOMAIN)
398 } else {
399 DEFAULT_DOMAIN
400 }
401}
402
403fn default_queries_timeout() -> Option<QueriesTimeouts> {
404 Some(QueriesTimeouts {
405 default: default_queries_timeout_default(),
406 transient_local_subscribers: Vec::new(),
407 services: Vec::new(),
408 actions: default_actions_timeout(),
409 })
410}
411
412fn default_queries_timeout_default() -> f32 {
413 DEFAULT_QUERIES_TIMEOUT
414}
415
416fn default_actions_timeout() -> Option<ActionsTimeouts> {
417 Some(ActionsTimeouts {
418 send_goal: Vec::new(),
419 cancel_goal: Vec::new(),
420 get_result: default_actions_get_result_timeout(),
421 })
422}
423
424fn default_actions_get_result_timeout() -> Vec<(Regex, f32)> {
425 vec![(Regex::new(".*").unwrap(), DEFAULT_ACTION_GET_RESULT_TIMEOUT)]
426}
427
428fn deserialize_path<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error>
429where
430 D: Deserializer<'de>,
431{
432 deserializer.deserialize_option(OptPathVisitor)
433}
434
435struct OptPathVisitor;
436
437impl<'de> serde::de::Visitor<'de> for OptPathVisitor {
438 type Value = Option<Vec<String>>;
439
440 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
441 write!(formatter, "none or a string or an array of strings")
442 }
443
444 fn visit_none<E>(self) -> Result<Self::Value, E>
445 where
446 E: de::Error,
447 {
448 Ok(None)
449 }
450
451 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
452 where
453 D: Deserializer<'de>,
454 {
455 deserializer.deserialize_any(PathVisitor).map(Some)
456 }
457}
458
459struct PathVisitor;
460
461impl<'de> serde::de::Visitor<'de> for PathVisitor {
462 type Value = Vec<String>;
463
464 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
465 write!(formatter, "a string or an array of strings")
466 }
467
468 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
469 where
470 E: de::Error,
471 {
472 Ok(vec![v.into()])
473 }
474
475 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
476 where
477 A: de::SeqAccess<'de>,
478 {
479 let mut v = if let Some(l) = seq.size_hint() {
480 Vec::with_capacity(l)
481 } else {
482 Vec::new()
483 };
484 while let Some(s) = seq.next_element()? {
485 v.push(s);
486 }
487 Ok(v)
488 }
489}
490
491fn default_reliable_routes_blocking() -> bool {
492 DEFAULT_RELIABLE_ROUTES_BLOCKING
493}
494
495#[derive(Deserialize, Debug, Serialize, Eq, PartialEq, Clone, Copy)]
496pub enum RosAutomaticDiscoveryRange {
497 Subnet,
498 Localhost,
499 Off,
500 SystemDefault,
501}
502
503fn default_localhost_only() -> bool {
504 env::var("ROS_LOCALHOST_ONLY").as_deref() == Ok("1")
505}
506
507fn default_automatic_discovery_range() -> Option<RosAutomaticDiscoveryRange> {
508 match env::var("ROS_AUTOMATIC_DISCOVERY_RANGE").as_deref() {
509 Ok("SUBNET") => Some(RosAutomaticDiscoveryRange::Subnet),
510 Ok("LOCALHOST") => Some(RosAutomaticDiscoveryRange::Localhost),
511 Ok("OFF") => Some(RosAutomaticDiscoveryRange::Localhost),
512 Ok("SYSTEM_DEFAULT") => Some(RosAutomaticDiscoveryRange::SystemDefault),
513 Ok(value) => {
514 warn!(
515 r#"Invalid value for environment variable ROS_AUTOMATIC_DISCOVERY_RANGE ("{value}"). Using "SUBNET" instead "#
516 );
517 Some(RosAutomaticDiscoveryRange::Subnet)
518 }
519 Err(_) => None,
520 }
521}
522
523fn deserialize_automatic_discovery_range<'de, D>(
524 deserializer: D,
525) -> Result<Option<RosAutomaticDiscoveryRange>, D::Error>
526where
527 D: Deserializer<'de>,
528{
529 let discovery_range: String = Deserialize::deserialize(deserializer).unwrap();
530 match discovery_range.as_str() {
531 "SUBNET" => Ok(Some(RosAutomaticDiscoveryRange::Subnet)),
532 "LOCALHOST" => Ok(Some(RosAutomaticDiscoveryRange::Localhost)),
533 "OFF" => Ok(Some(RosAutomaticDiscoveryRange::Off)),
534 "SYSTEM_DEFAULT" => Ok(Some(RosAutomaticDiscoveryRange::SystemDefault)),
535 unknown => Err(de::Error::custom(format!(
536 r#"Invalid parameter "{unknown}" for ROS_AUTOMATICALLY_DISCOVERY_RANGE"#
537 ))),
538 }
539}
540
541fn deserialize_static_peers<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error>
542where
543 D: Deserializer<'de>,
544{
545 let peers: String = Deserialize::deserialize(deserializer).unwrap();
546 let mut peer_list: Vec<String> = Vec::new();
547 for peer in peers.split(';') {
548 if !peer.is_empty() {
549 peer_list.push(peer.to_owned());
550 }
551 }
552 if peer_list.is_empty() {
553 Ok(None)
554 } else {
555 Ok(Some(peer_list))
556 }
557}
558
559fn default_transient_local_cache_multiplier() -> usize {
560 DEFAULT_TRANSIENT_LOCAL_CACHE_MULTIPLIER
561}
562
563fn default_work_thread_num() -> usize {
564 DEFAULT_WORK_THREAD_NUM
565}
566
567fn default_max_block_thread_num() -> usize {
568 DEFAULT_MAX_BLOCK_THREAD_NUM
569}
570
571fn serialize_regex<S>(r: &Option<Regex>, serializer: S) -> Result<S::Ok, S::Error>
572where
573 S: Serializer,
574{
575 match r {
576 Some(ex) => serializer.serialize_some(ex.as_str()),
577 None => serializer.serialize_none(),
578 }
579}
580
581fn deserialize_regex<'de, D>(deserializer: D) -> Result<Option<Regex>, D::Error>
582where
583 D: Deserializer<'de>,
584{
585 deserializer.deserialize_any(RegexVisitor)
586}
587
588struct RegexVisitor;
591
592impl<'de> Visitor<'de> for RegexVisitor {
593 type Value = Option<Regex>;
594
595 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
596 formatter.write_str(r#"either a string or a list of strings"#)
597 }
598
599 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
600 where
601 E: de::Error,
602 {
603 Regex::new(&format!("^{value}$"))
604 .map(Some)
605 .map_err(|e| de::Error::custom(format!("Invalid regex '{value}': {e}")))
606 }
607
608 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
609 where
610 A: de::SeqAccess<'de>,
611 {
612 let mut vec: Vec<String> = Vec::new();
613 while let Some(s) = seq.next_element::<String>()? {
614 vec.push(format!("^{s}$"));
615 }
616 if vec.is_empty() {
617 return Ok(None);
618 };
619
620 let s: String = vec.join("|");
621 Regex::new(&s)
622 .map(Some)
623 .map_err(|e| de::Error::custom(format!("Invalid regex '{s}': {e}")))
624 }
625}
626
627fn deserialize_vec_regex_f32<'de, D>(deserializer: D) -> Result<Vec<(Regex, f32)>, D::Error>
628where
629 D: Deserializer<'de>,
630{
631 #[derive(Deserialize)]
632 #[serde(untagged)]
633 enum AcceptedValues {
634 Float(f32),
635 List(Vec<String>),
636 }
637
638 let values: AcceptedValues = Deserialize::deserialize(deserializer).unwrap();
639 match values {
640 AcceptedValues::Float(f) => {
641 Ok(vec![(Regex::new(".*").unwrap(), f)])
643 }
644 AcceptedValues::List(strs) => {
645 let mut result: Vec<(Regex, f32)> = Vec::with_capacity(strs.len());
646 for s in strs {
647 let i = s.find('=').ok_or_else(|| {
648 de::Error::custom(format!(
649 r#"Invalid list of "<regex>=<float>" elements": {s}"#
650 ))
651 })?;
652 let regex = Regex::new(&s[0..i])
653 .map_err(|e| de::Error::custom(format!("Invalid regex in '{s}': {e}")))?;
654 let frequency: f32 = s[i + 1..]
655 .parse()
656 .map_err(|e| de::Error::custom(format!("Invalid float value in '{s}': {e}")))?;
657 result.push((regex, frequency));
658 }
659 Ok(result)
660 }
661 }
662}
663
664fn serialize_vec_regex_f32<S>(v: &Vec<(Regex, f32)>, serializer: S) -> Result<S::Ok, S::Error>
665where
666 S: Serializer,
667{
668 let mut seq = serializer.serialize_seq(Some(v.len()))?;
669 for (r, f) in v {
670 let s = format!("{}={}", r.as_str(), f);
671 seq.serialize_element(&s)?;
672 }
673 seq.end()
674}
675
676#[allow(clippy::type_complexity)]
677fn deserialize_vec_regex_prio<'de, D>(
678 deserializer: D,
679) -> Result<Vec<(Regex, (Priority, bool))>, D::Error>
680where
681 D: Deserializer<'de>,
682{
683 let strs: Vec<String> = Deserialize::deserialize(deserializer).unwrap();
684 let mut result: Vec<(Regex, (Priority, bool))> = Vec::with_capacity(strs.len());
685 for s in strs {
686 let i = s.find('=').ok_or_else(|| {
687 de::Error::custom(format!(
688 r#"Invalid list of "<regex>=<int>[:express]" elements": {s}"#
689 ))
690 })?;
691 let regex = Regex::new(&s[0..i])
692 .map_err(|e| de::Error::custom(format!("Invalid regex in '{s}': {e}")))?;
693 let (prio_str, is_express) = match s[i + 1..].strip_suffix(":express") {
694 Some(prio_str) => (prio_str, true),
695 None => (&s[i + 1..], false),
696 };
697 let i: u8 = prio_str.parse().map_err(|e| {
698 de::Error::custom(format!(
699 "Invalid priority (format is not <int>[:express]) in '{s}': {e}"
700 ))
701 })?;
702 let priority = Priority::try_from(i)
703 .map_err(|e| de::Error::custom(format!("Invalid priority in '{s}': {e}")))?;
704 result.push((regex, (priority, is_express)));
705 }
706 Ok(result)
707}
708
709fn serialize_vec_regex_prio<S>(
710 v: &Vec<(Regex, (Priority, bool))>,
711 serializer: S,
712) -> Result<S::Ok, S::Error>
713where
714 S: Serializer,
715{
716 let mut seq = serializer.serialize_seq(Some(v.len()))?;
717 for (r, (p, is_express)) in v {
718 let s = if *is_express {
719 format!("{}={}:express", r.as_str(), *p as u8)
720 } else {
721 format!("{}={}", r.as_str(), *p as u8)
722 };
723 seq.serialize_element(&s)?;
724 }
725 seq.end()
726}
727
728pub fn serialize_duration_as_f32<S>(d: &Duration, serializer: S) -> Result<S::Ok, S::Error>
729where
730 S: Serializer,
731{
732 serializer.serialize_f32(d.as_secs_f32())
733}
734
735#[cfg(test)]
736mod tests {
737 use test_case::test_case;
738
739 use super::{Config, RosAutomaticDiscoveryRange};
740
741 #[test]
742 fn test_allowance() {
743 use super::*;
744
745 let allow: Allowance = serde_json::from_str(
746 r#"{
747 "allow": {
748 "publishers": ["/tf", ".*/pose"],
749 "subscribers": [],
750 "service_servers": [".*"],
751 "action_servers": [".*/rotate_absolute"],
752 "action_clients": [ "" ]
753 }
754 }"#,
755 )
756 .unwrap();
757 println!("allow: {}", serde_json::to_string(&allow).unwrap());
758
759 assert!(matches!(
760 allow,
761 Allowance::Allow(ROS2InterfacesRegex {
762 publishers: Some(_),
763 subscribers: None,
764 service_servers: Some(_),
765 service_clients: None,
766 action_servers: Some(_),
767 action_clients: Some(_),
768 })
769 ));
770
771 assert!(allow.is_publisher_allowed("/tf"));
772 assert!(allow.is_publisher_allowed("/x/y/pose"));
773 assert!(!allow.is_publisher_allowed("/abc/rotate_absolute"));
774 assert!(!allow.is_publisher_allowed("/cmd_vel"));
775 assert!(!allow.is_service_cli_allowed("/some_pseudo_random_name"));
776
777 assert!(!allow.is_subscriber_allowed("/tf"));
778 assert!(!allow.is_subscriber_allowed("/x/y/pose"));
779 assert!(!allow.is_publisher_allowed("/abc/rotate_absolute"));
780 assert!(!allow.is_subscriber_allowed("/cmd_vel"));
781 assert!(!allow.is_service_cli_allowed("/some_pseudo_random_name"));
782
783 assert!(allow.is_service_srv_allowed("/tf"));
784 assert!(allow.is_service_srv_allowed("/x/y/pose"));
785 assert!(allow.is_service_srv_allowed("/abc/rotate_absolute"));
786 assert!(allow.is_service_srv_allowed("/cmd_vel"));
787 assert!(allow.is_service_srv_allowed("/some_pseudo_random_name"));
788
789 assert!(!allow.is_service_cli_allowed("/tf"));
790 assert!(!allow.is_service_cli_allowed("/x/y/pose"));
791 assert!(!allow.is_service_cli_allowed("/abc/rotate_absolute"));
792 assert!(!allow.is_service_cli_allowed("/cmd_vel"));
793 assert!(!allow.is_service_cli_allowed("/some_pseudo_random_name"));
794
795 assert!(!allow.is_action_srv_allowed("/tf"));
796 assert!(!allow.is_action_srv_allowed("/x/y/pose"));
797 assert!(allow.is_action_srv_allowed("/abc/rotate_absolute"));
798 assert!(!allow.is_action_srv_allowed("/cmd_vel"));
799 assert!(!allow.is_action_srv_allowed("/some_pseudo_random_name"));
800
801 assert!(!allow.is_action_cli_allowed("/tf"));
802 assert!(!allow.is_action_cli_allowed("/x/y/pose"));
803 assert!(!allow.is_action_cli_allowed("/abc/rotate_absolute"));
804 assert!(!allow.is_action_cli_allowed("/cmd_vel"));
805 assert!(!allow.is_action_cli_allowed("/some_pseudo_random_name"));
806
807 let deny: Allowance = serde_json::from_str(
808 r#"{
809 "deny": {
810 "publishers": ["/tf", ".*/pose"],
811 "subscribers": [],
812 "service_servers": [".*"],
813 "action_servers": [".*/rotate_absolute"],
814 "action_clients": [ "" ]
815 }
816 }"#,
817 )
818 .unwrap();
819 println!("deny: {}", serde_json::to_string(&allow).unwrap());
820
821 assert!(matches!(
822 deny,
823 Allowance::Deny(ROS2InterfacesRegex {
824 publishers: Some(_),
825 subscribers: None,
826 service_servers: Some(_),
827 service_clients: None,
828 action_servers: Some(_),
829 action_clients: Some(_),
830 })
831 ));
832
833 assert!(!deny.is_publisher_allowed("/tf"));
834 assert!(!deny.is_publisher_allowed("/x/y/pose"));
835 assert!(deny.is_publisher_allowed("/abc/rotate_absolute"));
836 assert!(deny.is_publisher_allowed("/cmd_vel"));
837 assert!(deny.is_service_cli_allowed("/some_pseudo_random_name"));
838
839 assert!(deny.is_subscriber_allowed("/tf"));
840 assert!(deny.is_subscriber_allowed("/x/y/pose"));
841 assert!(deny.is_publisher_allowed("/abc/rotate_absolute"));
842 assert!(deny.is_subscriber_allowed("/cmd_vel"));
843 assert!(deny.is_service_cli_allowed("/some_pseudo_random_name"));
844
845 assert!(!deny.is_service_srv_allowed("/tf"));
846 assert!(!deny.is_service_srv_allowed("/x/y/pose"));
847 assert!(!deny.is_service_srv_allowed("/abc/rotate_absolute"));
848 assert!(!deny.is_service_srv_allowed("/cmd_vel"));
849 assert!(!deny.is_service_srv_allowed("/some_pseudo_random_name"));
850
851 assert!(deny.is_service_cli_allowed("/tf"));
852 assert!(deny.is_service_cli_allowed("/x/y/pose"));
853 assert!(deny.is_service_cli_allowed("/abc/rotate_absolute"));
854 assert!(deny.is_service_cli_allowed("/cmd_vel"));
855 assert!(deny.is_service_cli_allowed("/some_pseudo_random_name"));
856
857 assert!(deny.is_action_srv_allowed("/tf"));
858 assert!(deny.is_action_srv_allowed("/x/y/pose"));
859 assert!(!deny.is_action_srv_allowed("/abc/rotate_absolute"));
860 assert!(deny.is_action_srv_allowed("/cmd_vel"));
861 assert!(deny.is_action_srv_allowed("/some_pseudo_random_name"));
862
863 assert!(deny.is_action_cli_allowed("/tf"));
864 assert!(deny.is_action_cli_allowed("/x/y/pose"));
865 assert!(deny.is_action_cli_allowed("/abc/rotate_absolute"));
866 assert!(deny.is_action_cli_allowed("/cmd_vel"));
867 assert!(deny.is_action_cli_allowed("/some_pseudo_random_name"));
868
869 let invalid = serde_json::from_str::<Allowance>(
870 r#"{
871 "allow": {
872 "publishers": ["/tf", ".*/pose"],
873 "subscribers": [],
874 "service_servers": [".*"],
875 "action_servers": [".*/rotate_absolute"],
876 "action_clients": [ "" ]
877 },
878 "deny": {
879 "subscribers": ["/tf", ".*/pose"],
880 "service_clients": [".*"],
881 "action_servers": [""],
882 "action_clients": [ ".*/rotate_absolute" ]
883 },
884 }"#,
885 );
886 assert!(invalid.is_err());
887 }
888
889 #[test]
890 fn test_path_field() {
891 let config = serde_json::from_str::<Config>(r#"{"__path__": "/example/path"}"#);
893
894 assert!(config.is_ok());
895 let Config {
896 __required__,
897 __path__,
898 ..
899 } = config.unwrap();
900
901 assert_eq!(__path__, Some(vec![String::from("/example/path")]));
902 assert_eq!(__required__, None);
903 }
904
905 #[test]
906 fn test_required_field() {
907 let config = serde_json::from_str::<Config>(r#"{"__required__": true}"#);
909 assert!(config.is_ok());
910 let Config {
911 __required__,
912 __path__,
913 ..
914 } = config.unwrap();
915
916 assert_eq!(__path__, None);
917 assert_eq!(__required__, Some(true));
918 }
919
920 #[test]
921 fn test_path_field_and_required_field() {
922 let config = serde_json::from_str::<Config>(
924 r#"{"__path__": "/example/path", "__required__": true}"#,
925 );
926
927 assert!(config.is_ok());
928 let Config {
929 __required__,
930 __path__,
931 ..
932 } = config.unwrap();
933
934 assert_eq!(__path__, Some(vec![String::from("/example/path")]));
935 assert_eq!(__required__, Some(true));
936 }
937
938 #[test]
939 fn test_no_path_field_and_no_required_field() {
940 let config = serde_json::from_str::<Config>("{}");
942
943 assert!(config.is_ok());
944 let Config {
945 __required__,
946 __path__,
947 ..
948 } = config.unwrap();
949
950 assert_eq!(__path__, None);
951 assert_eq!(__required__, None);
952 }
953
954 #[test_case("{}", None; "Empty tests")]
955 #[test_case(r#"{"ros_automatic_discovery_range": "SUBNET"}"#, Some(RosAutomaticDiscoveryRange::Subnet); "SUBNET tests")]
956 #[test_case(r#"{"ros_automatic_discovery_range": "LOCALHOST"}"#, Some(RosAutomaticDiscoveryRange::Localhost); "LOCALHOST tests")]
957 #[test_case(r#"{"ros_automatic_discovery_range": "OFF"}"#, Some(RosAutomaticDiscoveryRange::Off); "OFF tests")]
958 #[test_case(r#"{"ros_automatic_discovery_range": "SYSTEM_DEFAULT"}"#, Some(RosAutomaticDiscoveryRange::SystemDefault); "SYSTEM_DEFAULT tests")]
959 fn test_ros_automatic_discovery_range(
960 config: &str,
961 result: Option<RosAutomaticDiscoveryRange>,
962 ) {
963 std::env::remove_var("ROS_AUTOMATIC_DISCOVERY_RANGE");
967 let config = serde_json::from_str::<Config>(config);
968 assert!(config.is_ok());
969 let Config {
970 ros_automatic_discovery_range,
971 ..
972 } = config.unwrap();
973 assert_eq!(ros_automatic_discovery_range, result);
974 }
975
976 #[test_case("{}", None; "Empty tests")]
977 #[test_case(r#"{"ros_static_peers": "127.0.0.1"}"#, Some(vec!["127.0.0.1".to_owned()]); "Single peer")]
978 #[test_case(r#"{"ros_static_peers": "192.168.1.1;192.168.1.2"}"#, Some(vec!["192.168.1.1".to_owned(), "192.168.1.2".to_owned()]); "Multiple peers")]
979 fn test_ros_static_peers(config: &str, result: Option<Vec<String>>) {
980 std::env::remove_var("ROS_STATIC_PEERS");
982 let config = serde_json::from_str::<Config>(config);
983 assert!(config.is_ok());
984 let Config {
985 ros_static_peers, ..
986 } = config.unwrap();
987 assert_eq!(ros_static_peers, result);
988 }
989}