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];