Skip to main content

prns_config/editing/
interface.rs

1use std::collections::BTreeSet;
2use std::fmt;
3
4use prns_core::interfaces::rnode::multi::{RadioConfig, RadioConfigError, RadioConfigInput, VPort};
5
6use crate::reference::keys::{common as common_key, interface as interface_key};
7use crate::{parse_and_plan_named, ConfigErrors, InterfaceKind};
8
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
10pub struct InterfaceName(String);
11
12impl InterfaceName {
13    pub fn new(value: impl Into<String>) -> Result<Self, InterfaceNameError> {
14        let value = value.into();
15        if value.trim().is_empty() {
16            return Err(InterfaceNameError::Empty);
17        }
18        if value
19            .chars()
20            .any(|character| matches!(character, '[' | ']' | '\r' | '\n'))
21        {
22            return Err(InterfaceNameError::ConfigObjDelimiter);
23        }
24        Ok(Self(value))
25    }
26
27    pub fn as_str(&self) -> &str {
28        &self.0
29    }
30}
31
32impl fmt::Display for InterfaceName {
33    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
34        formatter.write_str(&self.0)
35    }
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum InterfaceNameError {
40    Empty,
41    ConfigObjDelimiter,
42}
43
44impl fmt::Display for InterfaceNameError {
45    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
46        formatter.write_str(match self {
47            Self::Empty => "interface name cannot be empty",
48            Self::ConfigObjDelimiter => "interface name cannot contain brackets or line separators",
49        })
50    }
51}
52
53impl std::error::Error for InterfaceNameError {}
54
55#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
56pub struct InterfaceConfigKey(String);
57
58impl InterfaceConfigKey {
59    pub fn new(value: impl Into<String>) -> Result<Self, InterfaceConfigKeyError> {
60        let value = value.into();
61        if value.trim().is_empty() {
62            return Err(InterfaceConfigKeyError::Empty);
63        }
64        if value
65            .chars()
66            .any(|character| matches!(character, '=' | '[' | ']' | '\r' | '\n'))
67        {
68            return Err(InterfaceConfigKeyError::ConfigObjDelimiter);
69        }
70        Ok(Self(value))
71    }
72
73    pub fn as_str(&self) -> &str {
74        &self.0
75    }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum InterfaceConfigKeyError {
80    Empty,
81    ConfigObjDelimiter,
82}
83
84impl fmt::Display for InterfaceConfigKeyError {
85    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
86        formatter.write_str(match self {
87            Self::Empty => "interface configuration key cannot be empty",
88            Self::ConfigObjDelimiter => {
89                "interface configuration key cannot contain ConfigObj delimiters"
90            }
91        })
92    }
93}
94
95impl std::error::Error for InterfaceConfigKeyError {}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
98pub struct InterfaceSettingKey(&'static str);
99
100impl InterfaceSettingKey {
101    pub fn parse(value: &str) -> Option<Self> {
102        ALL_SETTING_KEYS
103            .iter()
104            .copied()
105            .find(|candidate| *candidate == value)
106            .map(Self)
107    }
108
109    pub const fn as_str(self) -> &'static str {
110        self.0
111    }
112
113    pub fn is_secret(self) -> bool {
114        matches!(
115            self.0,
116            interface_key::PASS_PHRASE | interface_key::PASSPHRASE
117        )
118    }
119
120    pub fn canonical(self) -> Self {
121        match self.0 {
122            interface_key::ENABLED => Self(interface_key::INTERFACE_ENABLED),
123            interface_key::MODE => Self(interface_key::INTERFACE_MODE),
124            interface_key::NETWORKNAME => Self(interface_key::NETWORK_NAME),
125            interface_key::PASSPHRASE => Self(interface_key::PASS_PHRASE),
126            _ => self,
127        }
128    }
129
130    pub fn aliases(self) -> &'static [&'static str] {
131        match self.canonical().0 {
132            interface_key::INTERFACE_ENABLED => interface_key::ENABLED_ALIASES,
133            interface_key::INTERFACE_MODE => interface_key::MODE_ALIASES,
134            interface_key::NETWORK_NAME => interface_key::NETWORK_NAME_ALIASES,
135            interface_key::PASS_PHRASE => interface_key::PASSPHRASE_ALIASES,
136            _ => &[],
137        }
138    }
139}
140
141#[derive(Debug, Clone, PartialEq)]
142pub enum InterfaceSettingValue {
143    Bool(bool),
144    Unsigned(u64),
145    Signed(i64),
146    Decimal(f64),
147    Text(String),
148    List(Vec<String>),
149}
150
151#[derive(Debug, Clone, PartialEq)]
152pub struct InterfaceSetting {
153    key: InterfaceSettingKey,
154    value: InterfaceSettingValue,
155}
156
157impl InterfaceSetting {
158    pub const fn new(key: InterfaceSettingKey, value: InterfaceSettingValue) -> Self {
159        Self { key, value }
160    }
161
162    pub const fn key(&self) -> InterfaceSettingKey {
163        self.key
164    }
165
166    pub fn value(&self) -> &InterfaceSettingValue {
167        &self.value
168    }
169}
170
171#[derive(Debug, Clone, PartialEq)]
172pub struct InterfaceDefinition {
173    name: InterfaceName,
174    kind: InterfaceKind,
175    enabled: bool,
176    settings: Vec<InterfaceSetting>,
177    rnode_multi_radios: Vec<RNodeMultiRadioDefinition>,
178}
179
180impl InterfaceDefinition {
181    pub fn new(
182        name: InterfaceName,
183        kind: InterfaceKind,
184        enabled: bool,
185        settings: Vec<InterfaceSetting>,
186    ) -> Result<Self, InterfaceDefinitionError> {
187        Self::new_with_rnode_multi_radios(name, kind, enabled, settings, Vec::new())
188    }
189
190    pub fn new_with_rnode_multi_radios(
191        name: InterfaceName,
192        kind: InterfaceKind,
193        enabled: bool,
194        settings: Vec<InterfaceSetting>,
195        rnode_multi_radios: Vec<RNodeMultiRadioDefinition>,
196    ) -> Result<Self, InterfaceDefinitionError> {
197        Self::new_named_with_rnode_multi_radios(
198            "<interface definition>",
199            name,
200            kind,
201            enabled,
202            settings,
203            rnode_multi_radios,
204        )
205    }
206
207    pub fn new_named_with_rnode_multi_radios(
208        source_name: impl Into<String>,
209        name: InterfaceName,
210        kind: InterfaceKind,
211        enabled: bool,
212        settings: Vec<InterfaceSetting>,
213        rnode_multi_radios: Vec<RNodeMultiRadioDefinition>,
214    ) -> Result<Self, InterfaceDefinitionError> {
215        let mut keys = BTreeSet::new();
216        for setting in &settings {
217            if !keys.insert(setting.key) {
218                return Err(InterfaceDefinitionError::DuplicateSetting(setting.key));
219            }
220        }
221        if kind != InterfaceKind::RnodeMulti && !rnode_multi_radios.is_empty() {
222            return Err(InterfaceDefinitionError::RadiosOnNonRnodeMulti(kind));
223        }
224        let mut radio_names = BTreeSet::new();
225        for radio in &rnode_multi_radios {
226            if !radio_names.insert(radio.name.clone()) {
227                return Err(InterfaceDefinitionError::DuplicateRadio(radio.name.clone()));
228            }
229        }
230        let candidate = Self {
231            name,
232            kind,
233            enabled,
234            settings,
235            rnode_multi_radios,
236        };
237        let validation = candidate.render_with_enabled(true, "\n");
238        let document = format!("[interfaces]\n{validation}");
239        parse_and_plan_named(source_name, &document).map_err(InterfaceDefinitionError::Invalid)?;
240        Ok(candidate)
241    }
242
243    pub fn name(&self) -> &InterfaceName {
244        &self.name
245    }
246
247    pub const fn kind(&self) -> InterfaceKind {
248        self.kind
249    }
250
251    pub const fn enabled(&self) -> bool {
252        self.enabled
253    }
254
255    pub fn settings(&self) -> &[InterfaceSetting] {
256        &self.settings
257    }
258
259    pub fn rnode_multi_radios(&self) -> &[RNodeMultiRadioDefinition] {
260        &self.rnode_multi_radios
261    }
262
263    pub(crate) fn render(&self, newline: &str) -> String {
264        self.render_with_enabled(self.enabled, newline)
265    }
266
267    fn render_with_enabled(&self, enabled: bool, newline: &str) -> String {
268        let mut rendered = format!(
269            "  [[{}]]{newline}    type = {}{newline}    interface_enabled = {}{newline}",
270            self.name,
271            self.kind.canonical_name(),
272            render_bool(enabled),
273        );
274        for setting in &self.settings {
275            rendered.push_str("    ");
276            rendered.push_str(setting.key.as_str());
277            rendered.push_str(" = ");
278            rendered.push_str(&render_value(&setting.value));
279            rendered.push_str(newline);
280        }
281        for radio in &self.rnode_multi_radios {
282            rendered.push_str(&radio.render(newline));
283        }
284        rendered
285    }
286}
287
288#[derive(Debug, Clone, PartialEq, Eq)]
289pub struct RNodeMultiRadioDefinition {
290    name: InterfaceName,
291    vport: VPort,
292    radio: RadioConfig,
293}
294
295impl RNodeMultiRadioDefinition {
296    pub fn new(
297        name: InterfaceName,
298        vport: u8,
299        frequency: u64,
300        bandwidth: u32,
301        txpower: i16,
302        spreading_factor: u8,
303        coding_rate: u8,
304    ) -> Result<Self, RNodeMultiRadioDefinitionError> {
305        let vport = VPort::new(vport).ok_or(RNodeMultiRadioDefinitionError::Vport(vport))?;
306        let radio = RadioConfig::new(RadioConfigInput {
307            frequency_hz: frequency,
308            bandwidth_hz: bandwidth,
309            tx_power_dbm: txpower,
310            spreading_factor,
311            coding_rate,
312            airtime_limit_short_centi_percent: None,
313            airtime_limit_long_centi_percent: None,
314        })
315        .map_err(RNodeMultiRadioDefinitionError::Radio)?;
316        Ok(Self { name, vport, radio })
317    }
318
319    pub fn name(&self) -> &InterfaceName {
320        &self.name
321    }
322
323    pub const fn vport(&self) -> u8 {
324        self.vport.get()
325    }
326
327    pub const fn frequency(&self) -> u64 {
328        self.radio.frequency().hz() as u64
329    }
330
331    pub const fn bandwidth(&self) -> u32 {
332        self.radio.bandwidth_hz()
333    }
334
335    pub const fn txpower(&self) -> i16 {
336        self.radio.tx_power_dbm() as i16
337    }
338
339    pub const fn spreading_factor(&self) -> u8 {
340        self.radio.spreading_factor()
341    }
342
343    pub const fn coding_rate(&self) -> u8 {
344        self.radio.coding_rate()
345    }
346
347    pub(crate) fn render(&self, newline: &str) -> String {
348        format!(
349            "    [[[{name}]]]{newline}      interface_enabled = Yes{newline}      vport = {vport}{newline}      frequency = {frequency}{newline}      bandwidth = {bandwidth}{newline}      txpower = {txpower}{newline}      spreadingfactor = {spreading_factor}{newline}      codingrate = {coding_rate}{newline}",
350            name = self.name,
351            vport = self.vport.get(),
352            frequency = self.radio.frequency().hz(),
353            bandwidth = self.radio.bandwidth_hz(),
354            txpower = self.radio.tx_power_dbm(),
355            spreading_factor = self.radio.spreading_factor(),
356            coding_rate = self.radio.coding_rate(),
357        )
358    }
359}
360
361#[derive(Debug, Clone, Copy, PartialEq, Eq)]
362pub enum RNodeMultiRadioDefinitionError {
363    Vport(u8),
364    Radio(RadioConfigError),
365}
366
367impl fmt::Display for RNodeMultiRadioDefinitionError {
368    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
369        match self {
370            Self::Vport(value) => write!(
371                formatter,
372                "RNodeMulti vport {value} is outside 0 through 10"
373            ),
374            Self::Radio(error) => write!(formatter, "invalid RNodeMulti radio: {error:?}"),
375        }
376    }
377}
378
379impl std::error::Error for RNodeMultiRadioDefinitionError {}
380
381#[derive(Debug)]
382pub enum InterfaceDefinitionError {
383    DuplicateSetting(InterfaceSettingKey),
384    DuplicateRadio(InterfaceName),
385    RadiosOnNonRnodeMulti(InterfaceKind),
386    Invalid(ConfigErrors),
387}
388
389impl fmt::Display for InterfaceDefinitionError {
390    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
391        match self {
392            Self::DuplicateSetting(key) => {
393                write!(
394                    formatter,
395                    "interface setting {:?} was provided twice",
396                    key.as_str()
397                )
398            }
399            Self::DuplicateRadio(name) => {
400                write!(formatter, "RNodeMulti radio {name} was provided twice")
401            }
402            Self::RadiosOnNonRnodeMulti(kind) => write!(
403                formatter,
404                "RNodeMulti radios do not apply to {}",
405                kind.canonical_name()
406            ),
407            Self::Invalid(errors) => errors.fmt(formatter),
408        }
409    }
410}
411
412impl std::error::Error for InterfaceDefinitionError {}
413
414pub(crate) fn render_bool(value: bool) -> &'static str {
415    if value {
416        "Yes"
417    } else {
418        "No"
419    }
420}
421
422pub(crate) fn render_value(value: &InterfaceSettingValue) -> String {
423    match value {
424        InterfaceSettingValue::Bool(value) => render_bool(*value).to_string(),
425        InterfaceSettingValue::Unsigned(value) => value.to_string(),
426        InterfaceSettingValue::Signed(value) => value.to_string(),
427        InterfaceSettingValue::Decimal(value) => value.to_string(),
428        InterfaceSettingValue::Text(value) => render_text(value),
429        InterfaceSettingValue::List(values) => values
430            .iter()
431            .map(|value| render_text(value))
432            .collect::<Vec<_>>()
433            .join(", "),
434    }
435}
436
437fn render_text(value: &str) -> String {
438    let unquoted = !value.is_empty()
439        && value.trim() == value
440        && !value
441            .chars()
442            .any(|character| matches!(character, '#' | ',' | '\r' | '\n'));
443    if unquoted {
444        return value.to_string();
445    }
446    if !value.contains('"') {
447        return format!("\"{value}\"");
448    }
449    if !value.contains('\'') {
450        return format!("'{value}'");
451    }
452    format!("\"\"\"{value}\"\"\"")
453}
454
455pub(super) const ALL_SETTING_KEYS: &[&str] = &[
456    interface_key::INTERFACE_ENABLED,
457    interface_key::ENABLED,
458    interface_key::INTERFACE_MODE,
459    interface_key::MODE,
460    interface_key::OUTGOING,
461    interface_key::BITRATE,
462    interface_key::GRAVITY,
463    interface_key::ANNOUNCE_CAP,
464    interface_key::ANNOUNCE_RATE_TARGET,
465    interface_key::ANNOUNCE_RATE_GRACE,
466    interface_key::ANNOUNCE_RATE_PENALTY,
467    interface_key::NETWORK_NAME,
468    interface_key::NETWORKNAME,
469    interface_key::PASS_PHRASE,
470    interface_key::PASSPHRASE,
471    interface_key::IFAC_SIZE,
472    interface_key::DISCOVERABLE,
473    interface_key::ANNOUNCE_INTERVAL,
474    interface_key::DISCOVERY_STAMP_VALUE,
475    interface_key::DISCOVERY_NAME,
476    interface_key::DISCOVERY_ENCRYPT,
477    interface_key::REACHABLE_ON,
478    interface_key::PUBLISH_IFAC,
479    interface_key::LATITUDE,
480    interface_key::LONGITUDE,
481    interface_key::HEIGHT,
482    interface_key::DISCOVERY_FREQUENCY,
483    interface_key::DISCOVERY_BANDWIDTH,
484    interface_key::DISCOVERY_MODULATION,
485    interface_key::BOOTSTRAP_ONLY,
486    interface_key::RECURSIVE_PRS,
487    interface_key::ANNOUNCES_FROM_INTERNAL,
488    interface_key::ANNOUNCES_TO_INTERNAL,
489    interface_key::IGNORE_CONFIG_WARNINGS,
490    interface_key::GROUP_ID,
491    interface_key::DISCOVERY_SCOPE,
492    interface_key::DISCOVERY_PORT,
493    interface_key::DATA_PORT,
494    interface_key::DEVICES,
495    interface_key::IGNORED_DEVICES,
496    interface_key::MULTICAST_ADDRESS_TYPE,
497    interface_key::TARGET_HOST,
498    interface_key::TARGET_PORT,
499    interface_key::TARGET,
500    interface_key::KISS_FRAMING,
501    interface_key::I2P_TUNNELED,
502    interface_key::CONNECT_TIMEOUT,
503    interface_key::MAX_RECONNECT_TRIES,
504    interface_key::FIXED_MTU,
505    interface_key::LISTEN_IP,
506    interface_key::LISTEN_PORT,
507    interface_key::DEVICE,
508    interface_key::PORT,
509    interface_key::PREFER_IPV6,
510    interface_key::FORWARD_IP,
511    interface_key::FORWARD_PORT,
512    interface_key::SPEED,
513    interface_key::DATABITS,
514    interface_key::PARITY,
515    interface_key::STOPBITS,
516    interface_key::FLOW_CONTROL,
517    interface_key::PREAMBLE,
518    interface_key::TXTAIL,
519    interface_key::PERSISTENCE,
520    interface_key::SLOTTIME,
521    interface_key::ID_CALLSIGN,
522    interface_key::ID_INTERVAL,
523    interface_key::CALLSIGN,
524    interface_key::SSID,
525    interface_key::FREQUENCY,
526    interface_key::BANDWIDTH,
527    interface_key::SPREADINGFACTOR,
528    interface_key::CODINGRATE,
529    interface_key::TXPOWER,
530    interface_key::AIRTIME_LIMIT_SHORT,
531    interface_key::AIRTIME_LIMIT_LONG,
532    interface_key::COMMAND,
533    interface_key::RESPAWN_DELAY,
534    interface_key::REMOTE,
535    interface_key::LISTEN_ON,
536    interface_key::VPORT,
537    interface_key::PEERS,
538    interface_key::CONNECTABLE,
539    common_key::INGRESS_CONTROL,
540    common_key::EGRESS_CONTROL,
541    common_key::IC_MAX_HELD_ANNOUNCES,
542    common_key::IC_BURST_HOLD,
543    common_key::IC_BURST_FREQ_NEW,
544    common_key::IC_BURST_FREQ,
545    common_key::IC_PR_BURST_FREQ_NEW,
546    common_key::IC_PR_BURST_FREQ,
547    common_key::EC_PR_FREQ,
548    common_key::IC_NEW_TIME,
549    common_key::IC_BURST_PENALTY,
550    common_key::IC_HELD_RELEASE_INTERVAL,
551];