Skip to main content

prns_config/plan/interface/
discovery.rs

1use prns_core::interface_discovery::{StampCost, DEFAULT_STAMP_COST};
2use prns_core::interfaces::tcp::TcpWireFraming;
3use prns_core::units::DurationMillis;
4
5use super::medium::PlannedMedium;
6use crate::reference::keys::interface as interface_key;
7use crate::reference::{ReferenceConfigParams, ReferenceInterface};
8
9#[derive(Debug, Clone, PartialEq)]
10pub enum InterfaceDiscoveryPlan {
11    Disabled,
12    Announce(DiscoveryAnnouncementPlan),
13    Unpublishable(DiscoveryPublicationProblem),
14}
15
16#[derive(Debug, Clone, PartialEq)]
17pub struct DiscoveryAnnouncementPlan {
18    pub interval: DurationMillis,
19    pub stamp_cost: StampCost,
20    pub name: Option<String>,
21    pub encryption: DiscoveryEncryption,
22    pub ifac: DiscoveryIfacPublication,
23    pub location: DiscoveryLocationPlan,
24    pub advertisement: DiscoveryAdvertisementPlan,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum DiscoveryAdvertisementPlan {
29    Backbone {
30        reachable_on: String,
31        port: u16,
32    },
33    TcpServer {
34        reachable_on: String,
35        port: u16,
36    },
37    RNode {
38        frequency_hz: u64,
39        bandwidth_hz: u32,
40        spreading_factor: u8,
41        coding_rate: u8,
42    },
43    Kiss {
44        frequency_hz: u64,
45        bandwidth_hz: u32,
46        modulation: String,
47    },
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum DiscoveryPublicationProblem {
52    UnsupportedInterfaceType,
53    MissingRequiredSetting { key: &'static str },
54    IncompatibleSetting { key: &'static str },
55}
56
57#[derive(Debug, Clone, PartialEq)]
58pub enum DiscoveryEncryption {
59    Plaintext,
60    NetworkIdentity,
61}
62
63#[derive(Debug, Clone, PartialEq)]
64pub enum DiscoveryIfacPublication {
65    Omit,
66    Include,
67}
68
69#[derive(Debug, Clone, PartialEq)]
70pub struct DiscoveryLocationPlan {
71    pub latitude: Option<f64>,
72    pub longitude: Option<f64>,
73    pub height: Option<f64>,
74}
75
76pub(in crate::plan) fn plan_interface_discovery(
77    interface: &ReferenceInterface,
78    medium: &PlannedMedium,
79) -> InterfaceDiscoveryPlan {
80    if interface.discovery.discoverable != Some(true) {
81        return InterfaceDiscoveryPlan::Disabled;
82    }
83    let advertisement = match plan_discovery_advertisement(interface, medium) {
84        Ok(advertisement) => advertisement,
85        Err(problem) => return InterfaceDiscoveryPlan::Unpublishable(problem),
86    };
87    let minutes = interface
88        .discovery
89        .announce_interval_minutes
90        .unwrap_or(6 * 60)
91        .max(5) as u64;
92    InterfaceDiscoveryPlan::Announce(DiscoveryAnnouncementPlan {
93        interval: DurationMillis(minutes.saturating_mul(60 * 1_000)),
94        stamp_cost: interface.discovery.stamp_cost.unwrap_or(DEFAULT_STAMP_COST),
95        name: interface.discovery.name.clone(),
96        encryption: if interface.discovery.encrypt == Some(true) {
97            DiscoveryEncryption::NetworkIdentity
98        } else {
99            DiscoveryEncryption::Plaintext
100        },
101        ifac: if interface.discovery.publish_ifac == Some(true) {
102            DiscoveryIfacPublication::Include
103        } else {
104            DiscoveryIfacPublication::Omit
105        },
106        location: DiscoveryLocationPlan {
107            latitude: interface.discovery.latitude,
108            longitude: interface.discovery.longitude,
109            height: interface.discovery.height,
110        },
111        advertisement,
112    })
113}
114
115fn plan_discovery_advertisement(
116    interface: &ReferenceInterface,
117    medium: &PlannedMedium,
118) -> Result<DiscoveryAdvertisementPlan, DiscoveryPublicationProblem> {
119    let reachable_on = || {
120        interface.discovery.reachable_on.clone().ok_or(
121            DiscoveryPublicationProblem::MissingRequiredSetting {
122                key: interface_key::REACHABLE_ON,
123            },
124        )
125    };
126    let kiss = || {
127        Ok(DiscoveryAdvertisementPlan::Kiss {
128            frequency_hz: interface.discovery.frequency_hz.ok_or(
129                DiscoveryPublicationProblem::MissingRequiredSetting {
130                    key: interface_key::DISCOVERY_FREQUENCY,
131                },
132            )?,
133            bandwidth_hz: interface.discovery.bandwidth_hz.ok_or(
134                DiscoveryPublicationProblem::MissingRequiredSetting {
135                    key: interface_key::DISCOVERY_BANDWIDTH,
136                },
137            )?,
138            modulation: interface.discovery.modulation.clone().ok_or(
139                DiscoveryPublicationProblem::MissingRequiredSetting {
140                    key: interface_key::DISCOVERY_MODULATION,
141                },
142            )?,
143        })
144    };
145    match (medium, &interface.params) {
146        (
147            PlannedMedium::Backbone { .. },
148            ReferenceConfigParams::Backbone {
149                listen_port, port, ..
150            },
151        ) => Ok(DiscoveryAdvertisementPlan::Backbone {
152            reachable_on: reachable_on()?,
153            port: interface
154                .discovery
155                .reachable_port
156                .or(*port)
157                .or(*listen_port)
158                .ok_or(DiscoveryPublicationProblem::MissingRequiredSetting {
159                    key: interface_key::LISTEN_PORT,
160                })?,
161        }),
162        (
163            PlannedMedium::TcpServer { .. },
164            ReferenceConfigParams::TcpServer {
165                listen_port, port, ..
166            },
167        ) => Ok(DiscoveryAdvertisementPlan::TcpServer {
168            reachable_on: reachable_on()?,
169            port: interface
170                .discovery
171                .reachable_port
172                .or(*port)
173                .or(*listen_port)
174                .ok_or(DiscoveryPublicationProblem::MissingRequiredSetting {
175                    key: interface_key::LISTEN_PORT,
176                })?,
177        }),
178        (
179            PlannedMedium::Rnode {
180                frequency_hz,
181                bandwidth_hz,
182                spreading_factor,
183                coding_rate,
184                ..
185            },
186            ReferenceConfigParams::Rnode { .. },
187        ) => Ok(rnode_discovery_advertisement(
188            *frequency_hz,
189            *bandwidth_hz,
190            *spreading_factor,
191            *coding_rate,
192        )),
193        (PlannedMedium::RnodeMulti { member }, ReferenceConfigParams::RnodeMulti { .. }) => {
194            let radio = member.radio();
195            Ok(rnode_discovery_advertisement(
196                u64::from(radio.frequency().hz()),
197                radio.bandwidth_hz(),
198                radio.spreading_factor(),
199                radio.coding_rate(),
200            ))
201        }
202        (PlannedMedium::Kiss { .. }, ReferenceConfigParams::Kiss { .. }) => kiss(),
203        (
204            PlannedMedium::TcpClient {
205                framing: TcpWireFraming::Kiss,
206                ..
207            },
208            ReferenceConfigParams::TcpClient { .. },
209        ) => kiss(),
210        (PlannedMedium::TcpClient { .. }, ReferenceConfigParams::TcpClient { .. }) => {
211            Err(DiscoveryPublicationProblem::IncompatibleSetting {
212                key: interface_key::KISS_FRAMING,
213            })
214        }
215        _ => Err(DiscoveryPublicationProblem::UnsupportedInterfaceType),
216    }
217}
218
219fn rnode_discovery_advertisement(
220    frequency_hz: u64,
221    bandwidth_hz: u32,
222    spreading_factor: u8,
223    coding_rate: u8,
224) -> DiscoveryAdvertisementPlan {
225    DiscoveryAdvertisementPlan::RNode {
226        frequency_hz,
227        bandwidth_hz,
228        spreading_factor,
229        coding_rate,
230    }
231}