1use std::time::Duration;
2
3use prns_core::interfaces::rnode::policy as rnode_policy;
4use prns_core::interfaces::tcp::TcpWireFraming;
5pub use prns_core::interfaces::wifi_auto::{
6 DiscoveryScope as AutoInterfaceDiscoveryScope,
7 MulticastAddressType as AutoInterfaceMulticastAddressType,
8};
9use prns_core::interfaces::wifi_auto::{DEFAULT_DATA_PORT, DEFAULT_DISCOVERY_PORT, GROUP_NAME};
10use prns_core::interfaces::{BitrateBps, InterfaceDefaults};
11
12use super::PlanErrorKind;
13use crate::plan::rnode::RNodeTransportPlan;
14use crate::plan::RNodeMultiMemberPlan;
15use crate::reference::i2p::{validate_peer, validate_peers};
16use crate::reference::keys::interface as interface_key;
17use crate::reference::{ReferenceConfigParams, ReferenceInterface};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum AddressFamilyPreference {
21 System,
22 Ipv4,
23 Ipv6,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum TcpTunnelMode {
28 Direct,
29 I2p,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum ReconnectLimit {
34 Unlimited,
35 Attempts(u32),
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub struct ConnectTimeoutSeconds(u64);
40
41impl ConnectTimeoutSeconds {
42 pub const fn new(seconds: u64) -> Self {
43 Self(seconds)
44 }
45
46 pub const fn get(self) -> u64 {
47 self.0
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct TcpDialPlan {
53 pub host: String,
54 pub port: u16,
55 pub connect_timeout: ConnectTimeoutSeconds,
56 pub reconnect_limit: ReconnectLimit,
57 pub address_family: AddressFamilyPreference,
58 pub tunnel: TcpTunnelMode,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub enum TcpListenHost {
63 Any,
64 Address(String),
65 Device(String),
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct TcpListenPlan {
70 pub host: TcpListenHost,
71 pub port: u16,
72 pub address_family: AddressFamilyPreference,
73 pub tunnel: TcpTunnelMode,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq)]
77pub enum UdpEndpointHost {
78 Address(String),
79 DeviceBroadcast(String),
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct UdpEndpointPlan {
84 pub host: UdpEndpointHost,
85 pub port: u16,
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
89pub enum UdpFlowPlan {
90 ReceiveOnly {
91 listen: UdpEndpointPlan,
92 },
93 SendOnly {
94 forward: UdpEndpointPlan,
95 },
96 Bidirectional {
97 listen: UdpEndpointPlan,
98 forward: UdpEndpointPlan,
99 },
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum SerialDataBits {
104 Five,
105 Six,
106 Seven,
107 Eight,
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum SerialParity {
112 None,
113 Even,
114 Odd,
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub enum SerialStopBits {
119 One,
120 Two,
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub struct SerialLinePlan {
125 pub(in crate::plan) baud: u32,
126 pub(in crate::plan) data_bits: SerialDataBits,
127 pub(in crate::plan) parity: SerialParity,
128 pub(in crate::plan) stop_bits: SerialStopBits,
129}
130
131impl SerialLinePlan {
132 pub const fn baud(self) -> u32 {
133 self.baud
134 }
135
136 pub const fn data_bits(self) -> SerialDataBits {
137 self.data_bits
138 }
139
140 pub const fn parity(self) -> SerialParity {
141 self.parity
142 }
143
144 pub const fn stop_bits(self) -> SerialStopBits {
145 self.stop_bits
146 }
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub enum ReadyCommandFlowControl {
151 Disabled,
152 Enabled,
153}
154
155#[derive(Debug, Clone, PartialEq, Eq)]
156pub struct StationIdentificationPlan {
157 pub(in crate::plan) callsign: String,
158 pub(in crate::plan) interval_seconds: u64,
159}
160
161impl StationIdentificationPlan {
162 pub fn callsign(&self) -> &str {
163 &self.callsign
164 }
165
166 pub const fn interval_seconds(&self) -> u64 {
167 self.interval_seconds
168 }
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub struct AirtimeLimitCentiPercent(pub(in crate::plan) u16);
173
174impl AirtimeLimitCentiPercent {
175 pub const fn get(self) -> u16 {
176 self.0
177 }
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq)]
181pub struct PipeRespawnDelay(pub(in crate::plan) std::time::Duration);
182
183impl PipeRespawnDelay {
184 pub const fn get(self) -> std::time::Duration {
185 self.0
186 }
187}
188
189#[derive(Debug, Clone, PartialEq, Eq)]
190pub struct PipeCommandPlan {
191 pub(in crate::plan) source: String,
192 pub(in crate::plan) argv: Vec<String>,
193}
194
195#[derive(Debug, Clone, PartialEq, Eq)]
196pub struct WebSocketTargetPlan(String);
197
198impl WebSocketTargetPlan {
199 fn from_configured(target: String) -> Result<Self, PlanErrorKind> {
200 let target = target.trim();
201 if !crate::reference::supported_websocket_target(target) {
202 return Err(PlanErrorKind::InvalidSetting {
203 key: interface_key::TARGET,
204 });
205 }
206 Ok(Self(target.to_string()))
207 }
208
209 pub fn as_str(&self) -> &str {
210 &self.0
211 }
212}
213
214impl PipeCommandPlan {
215 pub fn source(&self) -> &str {
216 &self.source
217 }
218
219 pub fn argv(&self) -> &[String] {
220 &self.argv
221 }
222}
223
224#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
225pub struct I2pPeerPlan(String);
226
227impl I2pPeerPlan {
228 fn new(value: String) -> Result<Self, PlanErrorKind> {
229 validate_peer(&value).map_err(|_| PlanErrorKind::InvalidSetting {
230 key: interface_key::PEERS,
231 })?;
232 Ok(Self(value))
233 }
234
235 pub fn as_str(&self) -> &str {
236 &self.0
237 }
238}
239
240#[derive(Debug, Clone, PartialEq, Eq)]
241pub struct I2pPeersPlan(Vec<I2pPeerPlan>);
242
243impl I2pPeersPlan {
244 fn new(peers: Vec<String>) -> Result<Self, PlanErrorKind> {
245 validate_peers(peers.iter().map(String::as_str)).map_err(|_| {
246 PlanErrorKind::InvalidSetting {
247 key: interface_key::PEERS,
248 }
249 })?;
250 peers
251 .into_iter()
252 .map(I2pPeerPlan::new)
253 .collect::<Result<Vec<_>, _>>()
254 .map(Self)
255 }
256
257 pub fn iter(&self) -> impl Iterator<Item = &I2pPeerPlan> {
258 self.0.iter()
259 }
260
261 pub fn is_empty(&self) -> bool {
262 self.0.is_empty()
263 }
264}
265
266#[derive(Debug, Clone, Copy, PartialEq, Eq)]
267pub enum I2pReachabilityPlan {
268 OutboundOnly,
269 Connectable,
270}
271
272impl I2pReachabilityPlan {
273 pub const fn is_connectable(self) -> bool {
274 matches!(self, Self::Connectable)
275 }
276}
277
278#[derive(Debug, Clone, PartialEq, Eq)]
279pub struct AutoInterfaceGroupId(String);
280
281impl AutoInterfaceGroupId {
282 pub fn as_str(&self) -> &str {
283 &self.0
284 }
285
286 pub fn as_bytes(&self) -> &[u8] {
287 self.0.as_bytes()
288 }
289}
290
291#[derive(Debug, Clone, Copy, PartialEq, Eq)]
292pub struct AutoInterfaceDiscoveryPort(u16);
293
294impl AutoInterfaceDiscoveryPort {
295 fn new(port: u16) -> Option<Self> {
296 (port != 0 && port < u16::MAX).then_some(Self(port))
297 }
298
299 pub const fn get(self) -> u16 {
300 self.0
301 }
302
303 pub const fn reverse_discovery_port(self) -> u16 {
304 self.0 + 1
305 }
306}
307
308#[derive(Debug, Clone, Copy, PartialEq, Eq)]
309pub struct AutoInterfaceDataPort(u16);
310
311impl AutoInterfaceDataPort {
312 fn new(port: u16) -> Option<Self> {
313 (port != 0).then_some(Self(port))
314 }
315
316 pub const fn get(self) -> u16 {
317 self.0
318 }
319}
320
321#[derive(Debug, Clone, PartialEq, Eq)]
322pub struct AutoInterfaceDevicePolicy {
323 allowed: Vec<String>,
324 ignored: Vec<String>,
325}
326
327impl AutoInterfaceDevicePolicy {
328 pub fn allowed(&self) -> &[String] {
329 &self.allowed
330 }
331
332 pub fn ignored(&self) -> &[String] {
333 &self.ignored
334 }
335}
336
337#[derive(Debug, Clone, PartialEq, Eq)]
338pub struct AutoInterfacePlan {
339 group_id: AutoInterfaceGroupId,
340 discovery_scope: AutoInterfaceDiscoveryScope,
341 discovery_port: AutoInterfaceDiscoveryPort,
342 data_port: AutoInterfaceDataPort,
343 devices: AutoInterfaceDevicePolicy,
344 multicast_address_type: AutoInterfaceMulticastAddressType,
345}
346
347impl AutoInterfacePlan {
348 pub fn group_id(&self) -> &AutoInterfaceGroupId {
349 &self.group_id
350 }
351
352 pub const fn discovery_scope(&self) -> AutoInterfaceDiscoveryScope {
353 self.discovery_scope
354 }
355
356 pub const fn discovery_port(&self) -> AutoInterfaceDiscoveryPort {
357 self.discovery_port
358 }
359
360 pub const fn data_port(&self) -> AutoInterfaceDataPort {
361 self.data_port
362 }
363
364 pub const fn devices(&self) -> &AutoInterfaceDevicePolicy {
365 &self.devices
366 }
367
368 pub const fn multicast_address_type(&self) -> AutoInterfaceMulticastAddressType {
369 self.multicast_address_type
370 }
371}
372
373#[derive(Debug, Clone, PartialEq, Eq)]
375pub enum PlannedMedium {
376 AutoWifi(AutoInterfacePlan),
378 TcpClient {
380 connection: TcpDialPlan,
381 framing: TcpWireFraming,
382 },
383 TcpServer {
385 listener: TcpListenPlan,
386 framing: TcpWireFraming,
387 },
388 Udp {
390 flow: UdpFlowPlan,
391 },
392 Serial {
394 device: String,
395 line: SerialLinePlan,
396 },
397 Kiss {
400 device: String,
401 line: SerialLinePlan,
402 preamble_ms: u32,
403 txtail_ms: u32,
404 persistence: u8,
405 slottime_ms: u32,
406 flow_control: ReadyCommandFlowControl,
407 station_id: Option<StationIdentificationPlan>,
408 },
409 Ax25Kiss {
412 device: String,
413 line: SerialLinePlan,
414 preamble_ms: u32,
415 txtail_ms: u32,
416 persistence: u8,
417 slottime_ms: u32,
418 flow_control: ReadyCommandFlowControl,
419 callsign: String,
420 ssid: u8,
421 },
422 Pipe {
425 command: PipeCommandPlan,
426 respawn_delay: PipeRespawnDelay,
427 },
428 Rnode {
432 transport: RNodeTransportPlan,
433 frequency_hz: u64,
434 bandwidth_hz: u32,
435 tx_power_dbm: i16,
436 spreading_factor: u8,
437 coding_rate: u8,
438 flow_control: ReadyCommandFlowControl,
439 station_id: Option<StationIdentificationPlan>,
440 airtime_limit_short: Option<AirtimeLimitCentiPercent>,
441 airtime_limit_long: Option<AirtimeLimitCentiPercent>,
442 },
443 RnodeMulti {
444 member: RNodeMultiMemberPlan,
445 },
446 Backbone {
448 listener: TcpListenPlan,
449 },
450 BackboneClient {
453 connection: TcpDialPlan,
454 },
455 I2p {
456 peers: I2pPeersPlan,
457 reachability: I2pReachabilityPlan,
458 },
459 Weave {
460 device: String,
461 },
462 PrnsUsbAuto,
463 PrnsBluetoothAuto,
464 PrnsWebSocketClient {
465 target: WebSocketTargetPlan,
466 },
467 PrnsWebSocketServer {
468 listener: TcpListenPlan,
469 },
470}
471
472pub(super) fn rnode_defaults(
473 spreading_factor: u8,
474 coding_rate: u8,
475 bandwidth_hz: u32,
476) -> Result<InterfaceDefaults, PlanErrorKind> {
477 let raw = rnode_policy::nominal_bitrate_bps(spreading_factor, coding_rate, bandwidth_hz);
478 let bitrate = BitrateBps::new(u64::from(raw)).ok_or(PlanErrorKind::InvalidSetting {
479 key: interface_key::BANDWIDTH,
480 })?;
481 Ok(rnode_policy::defaults_for_bitrate(bitrate))
482}
483
484pub(super) fn plan_medium(interface: &ReferenceInterface) -> Result<PlannedMedium, PlanErrorKind> {
485 match &interface.params {
486 ReferenceConfigParams::Auto {
487 group_id,
488 discovery_scope,
489 discovery_port,
490 data_port,
491 devices,
492 ignored_devices,
493 multicast_address_type,
494 } => Ok(PlannedMedium::AutoWifi(auto_interface_plan(
495 group_id,
496 discovery_scope,
497 *discovery_port,
498 *data_port,
499 devices,
500 ignored_devices,
501 multicast_address_type,
502 )?)),
503 ReferenceConfigParams::TcpClient {
504 target_host,
505 target_port,
506 kiss_framing,
507 i2p_tunneled,
508 connect_timeout,
509 max_reconnect_tries,
510 fixed_mtu: _,
511 } => {
512 let host = target_host
513 .clone()
514 .ok_or(PlanErrorKind::MissingRequiredField {
515 key: interface_key::TARGET_HOST,
516 })?;
517 let port = target_port.ok_or(PlanErrorKind::MissingRequiredField {
518 key: interface_key::TARGET_PORT,
519 })?;
520 Ok(PlannedMedium::TcpClient {
521 connection: tcp_dial_plan(
522 host,
523 port,
524 *connect_timeout,
525 *max_reconnect_tries,
526 AddressFamilyPreference::System,
527 *i2p_tunneled,
528 ),
529 framing: if *kiss_framing == Some(true) {
530 TcpWireFraming::Kiss
531 } else {
532 TcpWireFraming::Hdlc
533 },
534 })
535 }
536 ReferenceConfigParams::TcpServer {
537 listen_ip,
538 listen_port,
539 device,
540 port,
541 prefer_ipv6,
542 i2p_tunneled,
543 kiss_framing,
544 fixed_mtu: _,
545 } => {
546 let listen_port = port
547 .or(*listen_port)
548 .ok_or(PlanErrorKind::MissingRequiredField {
549 key: interface_key::LISTEN_PORT,
550 })?;
551 Ok(PlannedMedium::TcpServer {
552 listener: TcpListenPlan {
553 host: tcp_listen_host(listen_ip, device),
554 port: listen_port,
555 address_family: preferred_ip_family(*prefer_ipv6),
556 tunnel: tunnel_mode(*i2p_tunneled),
557 },
558 framing: if *kiss_framing == Some(true) {
559 TcpWireFraming::Kiss
560 } else {
561 TcpWireFraming::Hdlc
562 },
563 })
564 }
565 ReferenceConfigParams::Udp {
566 listen_ip,
567 listen_port,
568 forward_ip,
569 forward_port,
570 device,
571 port,
572 } => {
573 let listen = udp_endpoint(
574 listen_ip.as_deref(),
575 port.or(*listen_port),
576 device.as_deref(),
577 interface_key::LISTEN_PORT,
578 )?;
579 let forward = udp_endpoint(
580 forward_ip.as_deref(),
581 port.or(*forward_port),
582 device.as_deref(),
583 interface_key::FORWARD_PORT,
584 )?;
585 let flow = match (listen, forward) {
586 (Some(listen), Some(forward)) => UdpFlowPlan::Bidirectional { listen, forward },
587 (Some(listen), None) => UdpFlowPlan::ReceiveOnly { listen },
588 (None, Some(forward)) => UdpFlowPlan::SendOnly { forward },
589 (None, None) => {
590 return Err(PlanErrorKind::MissingRequiredField {
591 key: interface_key::LISTEN_IP,
592 })
593 }
594 };
595 Ok(PlannedMedium::Udp { flow })
596 }
597 ReferenceConfigParams::Serial {
598 port,
599 speed,
600 databits,
601 parity,
602 stopbits,
603 } => {
604 let device = port.clone().ok_or(PlanErrorKind::MissingRequiredField {
605 key: interface_key::PORT,
606 })?;
607 Ok(PlannedMedium::Serial {
608 device,
609 line: serial_line(*speed, *databits, parity.as_deref(), *stopbits)?,
610 })
611 }
612 ReferenceConfigParams::Kiss {
613 port,
614 speed,
615 databits,
616 parity,
617 stopbits,
618 flow_control,
619 preamble,
620 txtail,
621 persistence,
622 slottime,
623 id_callsign,
624 id_interval,
625 } => {
626 let device = port.clone().ok_or(PlanErrorKind::MissingRequiredField {
627 key: interface_key::PORT,
628 })?;
629 Ok(PlannedMedium::Kiss {
630 device,
631 line: serial_line(*speed, *databits, parity.as_deref(), *stopbits)?,
632 preamble_ms: preamble.unwrap_or(RNS_KISS_DEFAULT_PREAMBLE_MS),
633 txtail_ms: txtail.unwrap_or(RNS_KISS_DEFAULT_TXTAIL_MS),
634 persistence: persistence
635 .map(|p| p.min(u8::MAX as u32) as u8)
636 .unwrap_or(RNS_KISS_DEFAULT_PERSISTENCE),
637 slottime_ms: slottime.unwrap_or(RNS_KISS_DEFAULT_SLOTTIME_MS),
638 flow_control: ready_command_flow_control(*flow_control),
639 station_id: station_identification(id_callsign.as_deref(), *id_interval, None)?,
640 })
641 }
642 ReferenceConfigParams::Ax25Kiss {
643 port,
644 speed,
645 databits,
646 parity,
647 stopbits,
648 flow_control,
649 preamble,
650 txtail,
651 persistence,
652 slottime,
653 callsign,
654 ssid,
655 } => {
656 let device = port.clone().ok_or(PlanErrorKind::MissingRequiredField {
657 key: interface_key::PORT,
658 })?;
659 let callsign = callsign
660 .clone()
661 .ok_or(PlanErrorKind::MissingRequiredField {
662 key: interface_key::CALLSIGN,
663 })?;
664 let ssid = ssid.ok_or(PlanErrorKind::MissingRequiredField {
665 key: interface_key::SSID,
666 })?;
667 Ok(PlannedMedium::Ax25Kiss {
668 device,
669 line: serial_line(*speed, *databits, parity.as_deref(), *stopbits)?,
670 preamble_ms: preamble.unwrap_or(RNS_KISS_DEFAULT_PREAMBLE_MS),
671 txtail_ms: txtail.unwrap_or(RNS_KISS_DEFAULT_TXTAIL_MS),
672 persistence: persistence
673 .map(|p| p.min(u8::MAX as u32) as u8)
674 .unwrap_or(RNS_KISS_DEFAULT_PERSISTENCE),
675 slottime_ms: slottime.unwrap_or(RNS_KISS_DEFAULT_SLOTTIME_MS),
676 flow_control: ready_command_flow_control(*flow_control),
677 callsign,
678 ssid,
679 })
680 }
681 ReferenceConfigParams::Rnode {
682 port,
683 radio,
684 flow_control,
685 id_callsign,
686 id_interval,
687 airtime_limit_short,
688 airtime_limit_long,
689 } => {
690 let configured_port = port.clone().ok_or(PlanErrorKind::MissingRequiredField {
691 key: interface_key::PORT,
692 })?;
693 let transport = RNodeTransportPlan::from_configured_port(configured_port)?;
694 let frequency_hz = radio.frequency.ok_or(PlanErrorKind::MissingRequiredField {
695 key: interface_key::FREQUENCY,
696 })?;
697 let bandwidth_hz = radio.bandwidth.ok_or(PlanErrorKind::MissingRequiredField {
698 key: interface_key::BANDWIDTH,
699 })?;
700 let spreading_factor =
701 radio
702 .spreadingfactor
703 .ok_or(PlanErrorKind::MissingRequiredField {
704 key: interface_key::SPREADINGFACTOR,
705 })?;
706 let coding_rate = radio
707 .codingrate
708 .ok_or(PlanErrorKind::MissingRequiredField {
709 key: interface_key::CODINGRATE,
710 })?;
711 let tx_power_dbm = radio.txpower.ok_or(PlanErrorKind::MissingRequiredField {
712 key: interface_key::TXPOWER,
713 })?;
714 Ok(PlannedMedium::Rnode {
715 transport,
716 frequency_hz,
717 bandwidth_hz,
718 tx_power_dbm,
719 spreading_factor,
720 coding_rate,
721 flow_control: ready_command_flow_control(*flow_control),
722 station_id: station_identification(id_callsign.as_deref(), *id_interval, Some(32))?,
723 airtime_limit_short: airtime_limit(
724 *airtime_limit_short,
725 interface_key::AIRTIME_LIMIT_SHORT,
726 )?,
727 airtime_limit_long: airtime_limit(
728 *airtime_limit_long,
729 interface_key::AIRTIME_LIMIT_LONG,
730 )?,
731 })
732 }
733 ReferenceConfigParams::Pipe {
734 command,
735 respawn_delay,
736 } => {
737 let command = command
738 .as_deref()
739 .ok_or(PlanErrorKind::MissingRequiredField {
740 key: interface_key::COMMAND,
741 })?;
742 Ok(PlannedMedium::Pipe {
743 command: pipe_command(command)?,
744 respawn_delay: pipe_respawn_delay(*respawn_delay)?,
745 })
746 }
747 ReferenceConfigParams::Backbone {
748 listen_ip,
749 listen_port,
750 target_host,
751 target_port,
752 port,
753 device,
754 prefer_ipv6,
755 i2p_tunneled,
756 connect_timeout,
757 max_reconnect_tries,
758 } => {
759 if target_host.is_some() || interface.type_name == "BackboneClientInterface" {
760 let host = target_host
761 .clone()
762 .ok_or(PlanErrorKind::MissingRequiredField {
763 key: interface_key::TARGET_HOST,
764 })?;
765 let port = port
766 .or(*target_port)
767 .ok_or(PlanErrorKind::MissingRequiredField {
768 key: interface_key::TARGET_PORT,
769 })?;
770 Ok(PlannedMedium::BackboneClient {
771 connection: tcp_dial_plan(
772 host,
773 port,
774 *connect_timeout,
775 *max_reconnect_tries,
776 preferred_ip_family(*prefer_ipv6),
777 *i2p_tunneled,
778 ),
779 })
780 } else {
781 let bind_port =
782 (*port)
783 .or(*listen_port)
784 .ok_or(PlanErrorKind::MissingRequiredField {
785 key: interface_key::LISTEN_PORT,
786 })?;
787 Ok(PlannedMedium::Backbone {
788 listener: TcpListenPlan {
789 host: tcp_listen_host(listen_ip, device),
790 port: bind_port,
791 address_family: preferred_ip_family(*prefer_ipv6),
792 tunnel: TcpTunnelMode::Direct,
793 },
794 })
795 }
796 }
797 ReferenceConfigParams::I2p { peers, connectable } => Ok(PlannedMedium::I2p {
798 peers: I2pPeersPlan::new(peers.clone().unwrap_or_default())?,
799 reachability: if *connectable == Some(true) {
800 I2pReachabilityPlan::Connectable
801 } else {
802 I2pReachabilityPlan::OutboundOnly
803 },
804 }),
805 ReferenceConfigParams::Weave { port } => Ok(PlannedMedium::Weave {
806 device: port.clone().ok_or(PlanErrorKind::MissingRequiredField {
807 key: interface_key::PORT,
808 })?,
809 }),
810 ReferenceConfigParams::PrnsUsbAuto => Ok(PlannedMedium::PrnsUsbAuto),
811 ReferenceConfigParams::PrnsBluetoothAuto => Ok(PlannedMedium::PrnsBluetoothAuto),
812 ReferenceConfigParams::PrnsWebSocketClient { target } => {
813 let target = target.clone().ok_or(PlanErrorKind::MissingRequiredField {
814 key: interface_key::TARGET,
815 })?;
816 Ok(PlannedMedium::PrnsWebSocketClient {
817 target: WebSocketTargetPlan::from_configured(target)?,
818 })
819 }
820 ReferenceConfigParams::PrnsWebSocketServer {
821 listen_ip,
822 listen_port,
823 device,
824 port,
825 prefer_ipv6,
826 } => {
827 let port = port
828 .or(*listen_port)
829 .ok_or(PlanErrorKind::MissingRequiredField {
830 key: interface_key::LISTEN_PORT,
831 })?;
832 Ok(PlannedMedium::PrnsWebSocketServer {
833 listener: TcpListenPlan {
834 host: tcp_listen_host(listen_ip, device),
835 port,
836 address_family: preferred_ip_family(*prefer_ipv6),
837 tunnel: TcpTunnelMode::Direct,
838 },
839 })
840 }
841 _ => Err(PlanErrorKind::UnsupportedKind),
842 }
843}
844
845#[allow(clippy::too_many_arguments)]
846fn auto_interface_plan(
847 group_id: &Option<String>,
848 discovery_scope: &Option<String>,
849 discovery_port: Option<u16>,
850 data_port: Option<u16>,
851 devices: &Option<Vec<String>>,
852 ignored_devices: &Option<Vec<String>>,
853 multicast_address_type: &Option<String>,
854) -> Result<AutoInterfacePlan, PlanErrorKind> {
855 let discovery_scope =
856 discovery_scope
857 .as_deref()
858 .map_or(Ok(AutoInterfaceDiscoveryScope::Link), |value| {
859 AutoInterfaceDiscoveryScope::from_name(value.trim()).ok_or(
860 PlanErrorKind::InvalidSetting {
861 key: interface_key::DISCOVERY_SCOPE,
862 },
863 )
864 })?;
865 let multicast_address_type = multicast_address_type.as_deref().map_or(
866 Ok(AutoInterfaceMulticastAddressType::Temporary),
867 |value| {
868 AutoInterfaceMulticastAddressType::from_name(value.trim()).ok_or(
869 PlanErrorKind::InvalidSetting {
870 key: interface_key::MULTICAST_ADDRESS_TYPE,
871 },
872 )
873 },
874 )?;
875 let discovery_port = AutoInterfaceDiscoveryPort::new(
876 discovery_port.unwrap_or(DEFAULT_DISCOVERY_PORT),
877 )
878 .ok_or(PlanErrorKind::InvalidSetting {
879 key: interface_key::DISCOVERY_PORT,
880 })?;
881 let data_port = AutoInterfaceDataPort::new(data_port.unwrap_or(DEFAULT_DATA_PORT)).ok_or(
882 PlanErrorKind::InvalidSetting {
883 key: interface_key::DATA_PORT,
884 },
885 )?;
886 Ok(AutoInterfacePlan {
887 group_id: AutoInterfaceGroupId(group_id.clone().unwrap_or_else(|| GROUP_NAME.to_string())),
888 discovery_scope,
889 discovery_port,
890 data_port,
891 devices: AutoInterfaceDevicePolicy {
892 allowed: devices.clone().unwrap_or_default(),
893 ignored: ignored_devices.clone().unwrap_or_default(),
894 },
895 multicast_address_type,
896 })
897}
898
899fn tcp_dial_plan(
900 host: String,
901 port: u16,
902 connect_timeout_seconds: Option<u64>,
903 max_reconnect_tries: Option<u32>,
904 address_family: AddressFamilyPreference,
905 i2p_tunneled: Option<bool>,
906) -> TcpDialPlan {
907 TcpDialPlan {
908 host,
909 port,
910 connect_timeout: ConnectTimeoutSeconds::new(
911 connect_timeout_seconds.unwrap_or(RNS_TCP_CONNECT_TIMEOUT_SECONDS),
912 ),
913 reconnect_limit: max_reconnect_tries
914 .map(ReconnectLimit::Attempts)
915 .unwrap_or(ReconnectLimit::Unlimited),
916 address_family,
917 tunnel: tunnel_mode(i2p_tunneled),
918 }
919}
920
921fn tcp_listen_host(listen_ip: &Option<String>, device: &Option<String>) -> TcpListenHost {
922 match (device, listen_ip) {
923 (Some(device), _) => TcpListenHost::Device(device.clone()),
924 (None, Some(address)) => TcpListenHost::Address(address.clone()),
925 (None, None) => TcpListenHost::Any,
926 }
927}
928
929const fn preferred_ip_family(prefer_ipv6: Option<bool>) -> AddressFamilyPreference {
930 match prefer_ipv6 {
931 Some(true) => AddressFamilyPreference::Ipv6,
932 Some(false) | None => AddressFamilyPreference::Ipv4,
933 }
934}
935
936const fn tunnel_mode(i2p_tunneled: Option<bool>) -> TcpTunnelMode {
937 match i2p_tunneled {
938 Some(true) => TcpTunnelMode::I2p,
939 Some(false) | None => TcpTunnelMode::Direct,
940 }
941}
942
943fn udp_endpoint(
944 address: Option<&str>,
945 port: Option<u16>,
946 device: Option<&str>,
947 port_key: &'static str,
948) -> Result<Option<UdpEndpointPlan>, PlanErrorKind> {
949 if address.is_none() && port.is_none() {
950 return Ok(None);
951 }
952 let host = match (address, device) {
953 (Some(address), _) => Some(UdpEndpointHost::Address(address.to_string())),
954 (None, Some(device)) => Some(UdpEndpointHost::DeviceBroadcast(device.to_string())),
955 (None, None) => None,
956 };
957 match (host, port) {
958 (Some(host), Some(port)) => Ok(Some(UdpEndpointPlan { host, port })),
959 (Some(_), None) => Err(PlanErrorKind::MissingRequiredField { key: port_key }),
960 (None, _) => Ok(None),
961 }
962}
963
964pub(in crate::plan) const RNS_DEFAULT_SERIAL_BAUD: u32 = 9_600;
965const RNS_TCP_CONNECT_TIMEOUT_SECONDS: u64 = 5;
966
967const RNS_KISS_DEFAULT_PREAMBLE_MS: u32 = 350;
971const RNS_KISS_DEFAULT_TXTAIL_MS: u32 = 20;
972const RNS_KISS_DEFAULT_PERSISTENCE: u8 = 64;
973const RNS_KISS_DEFAULT_SLOTTIME_MS: u32 = 20;
974
975const RNS_PIPE_DEFAULT_RESPAWN_SECONDS: u64 = 5;
976
977fn serial_line(
978 speed: Option<u32>,
979 data_bits: Option<u8>,
980 parity: Option<&str>,
981 stop_bits: Option<u8>,
982) -> Result<SerialLinePlan, PlanErrorKind> {
983 let baud = speed.unwrap_or(RNS_DEFAULT_SERIAL_BAUD);
984 if u64::from(baud) < BitrateBps::MINIMUM {
985 return Err(PlanErrorKind::InvalidSetting {
986 key: interface_key::SPEED,
987 });
988 }
989 let data_bits = match data_bits.unwrap_or(8) {
990 5 => SerialDataBits::Five,
991 6 => SerialDataBits::Six,
992 7 => SerialDataBits::Seven,
993 8 => SerialDataBits::Eight,
994 _ => {
995 return Err(PlanErrorKind::InvalidSetting {
996 key: interface_key::DATABITS,
997 })
998 }
999 };
1000 let parity = match parity.unwrap_or("n").trim().to_ascii_lowercase().as_str() {
1001 "n" | "none" => SerialParity::None,
1002 "e" | "even" => SerialParity::Even,
1003 "o" | "odd" => SerialParity::Odd,
1004 _ => {
1005 return Err(PlanErrorKind::InvalidSetting {
1006 key: interface_key::PARITY,
1007 })
1008 }
1009 };
1010 let stop_bits = match stop_bits.unwrap_or(1) {
1011 1 => SerialStopBits::One,
1012 2 => SerialStopBits::Two,
1013 _ => {
1014 return Err(PlanErrorKind::InvalidSetting {
1015 key: interface_key::STOPBITS,
1016 })
1017 }
1018 };
1019 Ok(SerialLinePlan {
1020 baud,
1021 data_bits,
1022 parity,
1023 stop_bits,
1024 })
1025}
1026
1027pub(in crate::plan) fn ready_command_flow_control(
1028 configured: Option<bool>,
1029) -> ReadyCommandFlowControl {
1030 match configured {
1031 Some(true) => ReadyCommandFlowControl::Enabled,
1032 Some(false) | None => ReadyCommandFlowControl::Disabled,
1033 }
1034}
1035
1036pub(in crate::plan) fn station_identification(
1037 callsign: Option<&str>,
1038 interval_seconds: Option<u64>,
1039 maximum_callsign_bytes: Option<usize>,
1040) -> Result<Option<StationIdentificationPlan>, PlanErrorKind> {
1041 let (callsign, interval_seconds) = match (callsign, interval_seconds) {
1042 (None, None) => return Ok(None),
1043 (Some(_), None) => {
1044 return Err(PlanErrorKind::MissingRequiredField {
1045 key: interface_key::ID_INTERVAL,
1046 })
1047 }
1048 (None, Some(_)) => {
1049 return Err(PlanErrorKind::MissingRequiredField {
1050 key: interface_key::ID_CALLSIGN,
1051 })
1052 }
1053 (Some(callsign), Some(interval_seconds)) => (callsign, interval_seconds),
1054 };
1055 if callsign.is_empty() || maximum_callsign_bytes.is_some_and(|maximum| callsign.len() > maximum)
1056 {
1057 return Err(PlanErrorKind::InvalidSetting {
1058 key: interface_key::ID_CALLSIGN,
1059 });
1060 }
1061 Ok(Some(StationIdentificationPlan {
1062 callsign: callsign.to_string(),
1063 interval_seconds,
1064 }))
1065}
1066
1067pub(in crate::plan) fn airtime_limit(
1068 percent: Option<f64>,
1069 key: &'static str,
1070) -> Result<Option<AirtimeLimitCentiPercent>, PlanErrorKind> {
1071 let Some(percent) = percent else {
1072 return Ok(None);
1073 };
1074 if !percent.is_finite() || !(0.0..=100.0).contains(&percent) {
1075 return Err(PlanErrorKind::InvalidSetting { key });
1076 }
1077 Ok(Some(AirtimeLimitCentiPercent((percent * 100.0) as u16)))
1078}
1079
1080fn pipe_respawn_delay(seconds: Option<f64>) -> Result<PipeRespawnDelay, PlanErrorKind> {
1081 let duration = match seconds {
1082 Some(seconds) => {
1083 Duration::try_from_secs_f64(seconds).map_err(|_| PlanErrorKind::InvalidSetting {
1084 key: interface_key::RESPAWN_DELAY,
1085 })?
1086 }
1087 None => Duration::from_secs(RNS_PIPE_DEFAULT_RESPAWN_SECONDS),
1088 };
1089 Ok(PipeRespawnDelay(duration))
1090}
1091
1092fn pipe_command(source: &str) -> Result<PipeCommandPlan, PlanErrorKind> {
1093 let argv = shlex::split(source).filter(|argv| !argv.is_empty()).ok_or(
1094 PlanErrorKind::InvalidSetting {
1095 key: interface_key::COMMAND,
1096 },
1097 )?;
1098 Ok(PipeCommandPlan {
1099 source: source.to_string(),
1100 argv,
1101 })
1102}