pim-core 0.1.8

Core types, traits, and configuration for the Proximity Internet Mesh protocol
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
//! Configuration data model.

use serde::{Deserialize, Serialize};
use std::path::PathBuf;

#[allow(unused_imports)]
use super::defaults::*;
use super::peer::PeerConfig;
use super::peer_cleanup::PeerCleanupConfig;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
/// Top-level configuration loaded from the node TOML file.
pub struct Config {
    /// Identity and local state settings for this node.
    pub node: NodeConfig,
    /// TUN interface settings for the mesh dataplane.
    #[serde(default)]
    pub interface: InterfaceConfig,
    /// LAN peer discovery settings.
    #[serde(default)]
    pub discovery: DiscoveryConfig,
    /// Optional private-mesh membership settings. Absent or `mode = "open"` →
    /// open mesh (any peer can connect; default behaviour). When configured
    /// with `mode = "private"` and a passphrase, only peers that derive the
    /// same mesh secret can complete the handshake or decrypt discovery
    /// advertisements. See [`MeshConfig`] for the schema.
    #[serde(default)]
    pub mesh: MeshConfig,
    /// Peer-to-peer transport settings.
    #[serde(default)]
    pub transport: TransportConfig,
    /// Route propagation and expiry settings.
    #[serde(default)]
    pub routing: RoutingConfig,
    /// Internet gateway behaviour and NAT settings.
    #[serde(default)]
    pub gateway: GatewayConfig,
    /// Relay forwarding settings. When enabled this node forwards traffic for other mesh peers.
    #[serde(default)]
    pub relay: RelayConfig,
    /// Key material and encryption policy settings.
    #[serde(default)]
    pub security: SecurityConfig,
    /// Wi-Fi Direct (IEEE 802.11 P2P) peer discovery and group formation settings.
    #[serde(default)]
    pub wifi_direct: WifiDirectConfig,
    /// Bluetooth PAN peer link monitoring and address handoff settings.
    #[serde(default)]
    pub bluetooth: BluetoothConfig,
    /// Bluetooth RFCOMM direct-channel discovery and TCP bridge settings.
    #[serde(default)]
    pub bluetooth_rfcomm: BluetoothRfcommConfig,
    /// User-to-user encrypted messaging settings, including broadcast policy.
    #[serde(default)]
    pub messaging: MessagingConfig,
    /// Statically configured peers. Optional — nodes can rely entirely on discovery when empty.
    #[serde(default)]
    pub peers: Vec<PeerConfig>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
/// Node-local identity and filesystem settings.
pub struct NodeConfig {
    /// Human-readable node name used in logs and operator-facing output.
    pub name: String,
    /// Directory for persistent node state such as keys and runtime metadata.
    #[serde(default = "default_data_dir")]
    pub data_dir: PathBuf,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
/// Settings for the Linux TUN interface that carries mesh IP traffic.
pub struct InterfaceConfig {
    /// Requested interface name, for example `pim0`.
    #[serde(default = "default_interface_name")]
    pub name: String,
    /// Interface MTU in bytes.
    #[serde(default = "default_mtu")]
    pub mtu: u32,
    /// Mesh IPv4 address or the string `\"auto\"` to request assignment automatically.
    #[serde(default = "default_mesh_ip")]
    pub mesh_ip: String,
    /// Optional mesh IPv6 CIDR assigned to the local TUN interface.
    #[serde(default)]
    pub mesh_ipv6: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
/// UDP broadcast discovery timing and policy configuration.
pub struct DiscoveryConfig {
    /// Enable or disable the discovery service entirely. When `false` the daemon connects only
    /// to statically configured `[[peers]]`.
    #[serde(default = "default_discovery_enabled")]
    pub enabled: bool,
    /// UDP port used for sending and receiving discovery broadcasts.
    #[serde(default = "default_discovery_port")]
    pub port: u16,
    /// Interval between outgoing discovery broadcasts, in milliseconds.
    #[serde(default = "default_broadcast_interval_ms")]
    pub broadcast_interval_ms: u64,
    /// Time after which an unseen peer is considered stale, in milliseconds.
    #[serde(default = "default_peer_timeout_ms")]
    pub peer_timeout_ms: u64,
    /// Automatically initiate connections to discovered peers advertising relay capability.
    #[serde(default = "default_connect_relays")]
    pub connect_relays: bool,
    /// Automatically initiate connections to discovered peers advertising gateway capability.
    #[serde(default = "default_connect_gateways")]
    pub connect_gateways: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
/// Mesh-membership mode. `Open` admits any authenticated peer (mode default
/// when `[mesh]` is absent). `Private` requires every peer to derive the
/// same mesh secret from a shared passphrase.
pub enum MeshMode {
    /// No mesh-level gating. Behaves identically to a daemon configured
    /// without a `[mesh]` section.
    #[default]
    Open,
    /// Passphrase-gated mesh: only peers that derive the same secret can
    /// complete the handshake or decrypt discovery advertisements.
    Private,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
/// Private-mesh membership configuration.
///
/// All fields are optional; an absent `[mesh]` section is treated as
/// `mode = "open"`. When `mode = "private"`, `passphrase` must be a
/// non-empty UTF-8 string. The passphrase is stretched once at daemon
/// startup via Argon2id (see [`MeshKdfConfig`]) into a 32-byte master,
/// then split via HKDF-SHA256 into purpose-bound sub-keys for discovery
/// encryption and handshake binding.
pub struct MeshConfig {
    /// Mesh mode. Defaults to [`MeshMode::Open`] (open mesh).
    #[serde(default)]
    pub mode: MeshMode,
    /// Passphrase used to derive the mesh secret. Required when
    /// `mode = "private"`. Empty string in private mode is rejected at
    /// startup. Ignored entirely when `mode = "open"`.
    #[serde(default)]
    pub passphrase: Option<String>,
    /// Optional cosmetic mesh label used by the UI / CLI. Also mixed into
    /// the Argon2id salt, so two meshes that happen to share a passphrase
    /// but use different `mesh_id` values derive distinct secrets and
    /// cannot interconnect. Renaming `mesh_id` therefore invalidates
    /// every existing peer connection — UI surfaces a warning.
    #[serde(default)]
    pub mesh_id: Option<String>,
    /// Argon2id parameters used to stretch the passphrase. The default
    /// targets ~100 ms on a desktop. Tunable for embedded targets.
    #[serde(default)]
    pub kdf: MeshKdfConfig,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
/// Argon2id KDF parameters used when stretching the mesh passphrase. The
/// derivation runs once at daemon startup and the result is cached, so
/// values can be conservative without affecting per-handshake latency.
pub struct MeshKdfConfig {
    /// Memory cost in KiB. Default 65536 (64 MiB).
    #[serde(default = "default_mesh_kdf_m_cost_kib")]
    pub m_cost_kib: u32,
    /// Number of iterations. Default 3.
    #[serde(default = "default_mesh_kdf_t_cost")]
    pub t_cost: u32,
    /// Parallelism. Default 1.
    #[serde(default = "default_mesh_kdf_p_cost")]
    pub p_cost: u32,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
/// Settings that control whether this node acts as a relay, forwarding traffic for other peers.
pub struct RelayConfig {
    /// Enables relay forwarding when `true`. Gateway nodes are implicitly relays regardless of
    /// this setting.
    #[serde(default)]
    pub enabled: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
/// Wire transport configuration for direct peer connections.
pub struct TransportConfig {
    /// Transport backend name, currently `tcp`.
    #[serde(default = "default_transport_type")]
    pub r#type: String,
    /// Local port the transport listens on for inbound peer connections.
    #[serde(default = "default_listen_port")]
    pub listen_port: u16,
    /// Maximum reconnect attempts per peer before giving up.
    #[serde(default = "default_max_reconnect_attempts")]
    pub max_reconnect_attempts: u32,
    /// Timeout for outbound TCP connect attempts in milliseconds.
    #[serde(default = "default_connect_timeout_ms")]
    pub connect_timeout_ms: u64,
    /// Periodic cleanup of `ReconnectManager.discovered_targets` —
    /// see [`PeerCleanupConfig`]. Discovered targets are
    /// `(NodeId, ConnectTarget)` pairs accumulated as the daemon
    /// learns peers from various discovery sources; without
    /// cleanup the set grows monotonically. Ephemeral defaults
    /// (7 days / 1 h).
    #[serde(default = "PeerCleanupConfig::ephemeral_default")]
    pub peer_cleanup: PeerCleanupConfig,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
/// Routing behaviour for propagating and aging route advertisements.
pub struct RoutingConfig {
    /// Maximum hop count accepted before a route is considered unusable.
    #[serde(default = "default_max_hops")]
    pub max_hops: u8,
    /// Routing algorithm identifier used for compatibility and diagnostics.
    #[serde(default = "default_route_algorithm")]
    pub algorithm: String,
    /// Lifetime of learned routes before expiry, in seconds.
    #[serde(default = "default_route_expiry_s")]
    pub route_expiry_s: u64,
    /// DNS resolvers handed to the system resolver (`resolvectl dns
    /// pim0 …` on Linux/systemd-resolved) when split-default routing
    /// is engaged. Without this list the resolver keeps its
    /// DHCP-provided upstream — which becomes unreachable the moment
    /// the user disables their wifi/wired uplink, so `curl gmail.com`
    /// fails even though the IP path through the mesh is live.
    /// Defaults to a small public anycast set; override for corporate
    /// split-DNS (`["10.0.0.53"]`), pi-hole on the gateway
    /// (`["10.77.0.1"]`), or IPv6-only resolvers.
    #[serde(default = "default_dns_servers")]
    pub dns_servers: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
/// Settings that control whether this node can act as an internet gateway.
pub struct GatewayConfig {
    /// Enables gateway and NAT behaviour when `true`.
    #[serde(default)]
    pub enabled: bool,
    /// Name of the internet-facing interface used for masquerading.
    #[serde(default = "default_nat_interface")]
    pub nat_interface: String,
    /// Maximum number of concurrent tracked gateway connections.
    #[serde(default = "default_max_connections")]
    pub max_connections: u32,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
/// Encryption policy and key storage configuration.
pub struct SecurityConfig {
    /// Path to the Ed25519 private key file for this node.
    #[serde(default = "default_key_file")]
    pub key_file: PathBuf,
    /// Whether unencrypted sessions should be rejected.
    #[serde(default = "default_require_encryption")]
    pub require_encryption: bool,
    /// Authorization policy applied after peer identity is authenticated.
    #[serde(default)]
    pub authorization_policy: AuthorizationPolicy,
    /// Explicitly authorized peers when `authorization_policy = "allow_list"`.
    #[serde(default)]
    pub authorized_peers: Vec<crate::NodeId>,
    /// Persistent trust store used by `trust_on_first_use`.
    #[serde(default = "default_trust_store_file")]
    pub trust_store_file: PathBuf,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
/// Direct-peer authorization policy.
pub enum AuthorizationPolicy {
    /// Admit any authenticated peer.
    #[default]
    AllowAll,
    /// Admit only peers listed in `authorized_peers`.
    AllowList,
    /// Admit authenticated peers on first contact and persist their identity locally.
    TrustOnFirstUse,
}

/// Wi-Fi Direct (IEEE 802.11 P2P) discovery and group negotiation configuration.
///
/// When `enabled = true` the daemon will start Wi-Fi Direct peer discovery via
/// `wpa_cli` and attempt to form P2P groups with discovered devices. Once a group
/// is established the resulting IP address is used to open a standard TCP transport
/// connection, so all existing security, routing, and gateway logic applies unchanged.
///
/// **Prerequisite:** `wpa_supplicant` compiled with P2P support must be running and
/// controlling the interface specified by `interface`.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct WifiDirectConfig {
    /// Enable Wi-Fi Direct peer discovery. Defaults to `false` (opt-in).
    #[serde(default)]
    pub enabled: bool,
    /// Physical Wi-Fi interface to use for P2P operations (e.g. `wlan0`).
    #[serde(default = "default_wfd_interface")]
    pub interface: String,
    /// Group Owner intent value (0–15). Higher values make this node more likely to
    /// become the Group Owner during negotiation. Default 7 (neutral).
    #[serde(default = "default_wfd_go_intent")]
    pub go_intent: u8,
    /// P2P listen channel number. Default 6.
    #[serde(default = "default_wfd_listen_channel")]
    pub listen_channel: u8,
    /// P2P operating channel number. Default 6.
    #[serde(default = "default_wfd_op_channel")]
    pub op_channel: u8,
    /// Connection method: `"pbc"` (push-button) or `"pin:<8-digit-pin>"`. Default `"pbc"`.
    #[serde(default = "default_wfd_connect_method")]
    pub connect_method: String,
    /// Periodic cleanup of unreachable Wi-Fi Direct peers — see
    /// [`PeerCleanupConfig`]. The destructive action drops the
    /// peer's row from the in-daemon `wfd_peer_lifecycle` table.
    /// Bluetooth-flavoured defaults (2 h / 1 h) since the radio-
    /// layer reachability characteristics are similar.
    #[serde(default = "PeerCleanupConfig::bluetooth_default")]
    pub peer_cleanup: PeerCleanupConfig,
}

/// Bluetooth PAN link-establishment configuration.
///
/// This mechanism is intentionally narrow: it does not replace the transport
/// layer and it does not manage pairing. Instead, it waits for a Bluetooth PAN
/// interface to appear, then learns peer IPs from the PAN neighbor table and
/// hands them to the daemon so the existing TCP transport and handshake logic
/// can connect normally. Static Bluetooth peers are configured under
/// `[[peers]]` with `mechanism = "bluetooth"`.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BluetoothConfig {
    /// Enable Bluetooth PAN link monitoring. Defaults to `false` (opt-in).
    #[serde(default)]
    pub enabled: bool,
    /// Preferred PAN-facing interface name or `"auto"` for runtime resolution.
    #[serde(default = "default_bluetooth_interface")]
    pub interface: String,
    /// Enable radio-level Bluetooth discovery and pairing for new peers.
    #[serde(default = "default_bluetooth_radio_discovery_enabled")]
    pub radio_discovery_enabled: bool,
    /// Prefix used to identify PIM peers by Bluetooth device name.
    #[serde(default = "default_bluetooth_device_name_prefix")]
    pub device_name_prefix: String,
    /// Local Bluetooth controller alias to advertise. Empty means derived from node name.
    #[serde(default)]
    pub local_alias: String,
    /// Allow outbound PAN/NAP connection attempts to discovered peers.
    #[serde(default = "default_bluetooth_connect_pan")]
    pub connect_pan: bool,
    /// Start and supervise a local Linux NAP server process.
    #[serde(default)]
    pub serve_nap: bool,
    /// Linux bridge/interface to expose through the local NAP server.
    #[serde(default = "default_bluetooth_nap_bridge")]
    pub nap_bridge: String,
    /// IPv4 address/CIDR assigned to `nap_bridge` when the daemon manages it.
    #[serde(default = "default_bluetooth_nap_bridge_addr")]
    pub nap_bridge_addr: String,
    /// Run a daemon-supervised DHCP server on `nap_bridge` when serving NAP.
    #[serde(default = "default_bluetooth_dhcp_enabled")]
    pub dhcp_enabled: bool,
    /// Explicit DHCP range (`start,end`). When unset, derived from `nap_bridge_addr`.
    #[serde(default)]
    pub dhcp_range: Option<String>,
    /// DHCP lease time passed to dnsmasq (e.g. `"12h"`, `"infinite"`).
    #[serde(default = "default_bluetooth_dhcp_lease_time")]
    pub dhcp_lease_time: String,
    /// Comma-separated DNS server list advertised to DHCP clients. When unset,
    /// inherited from the host's `/etc/resolv.conf` at runtime.
    #[serde(default)]
    pub dhcp_dns: Option<String>,
    /// Automatically request DHCP on the resolved PAN interface when acting as a
    /// Linux PAN client (`connect_pan = true`, `serve_nap = false`).
    #[serde(default = "default_bluetooth_request_dhcp")]
    pub request_dhcp: bool,
    /// Automatically discover peer IPs from the PAN interface neighbor table.
    #[serde(default = "default_bluetooth_auto_discover_peers")]
    pub auto_discover_peers: bool,
    /// Poll interval used while waiting for the PAN interface to become ready.
    #[serde(default = "default_bluetooth_poll_interval_ms")]
    pub poll_interval_ms: u64,
    /// Poll interval used for radio-level device scans.
    #[serde(default = "default_bluetooth_scan_interval_ms")]
    pub scan_interval_ms: u64,
    /// Poll interval used for automatic peer discovery after the interface is ready.
    #[serde(default = "default_bluetooth_peer_discovery_interval_ms")]
    pub peer_discovery_interval_ms: u64,
    /// Timeout for `bluetoothctl` operations, in seconds.
    #[serde(default = "default_bluetoothctl_timeout_s")]
    pub bluetoothctl_timeout_s: u64,
    /// How long the controller remains discoverable after startup.
    #[serde(default = "default_bluetooth_discoverable_timeout_s")]
    pub discoverable_timeout_s: u64,
    /// Maximum time to wait for the PAN interface to appear before giving up.
    #[serde(default = "default_bluetooth_startup_timeout_ms")]
    pub startup_timeout_ms: u64,
    /// Periodic cleanup of unreachable PAN-paired peers — see
    /// [`PeerCleanupConfig`]. Bluetooth-flavoured defaults (2 h /
    /// 1 h). The destructive action is `bluetoothctl remove
    /// <bd_addr>`. Coordinates with `[bluetooth_rfcomm.peer_cleanup]`
    /// via the BlueZ `Connected` check so an active session in
    /// either subsystem suppresses the unpair.
    #[serde(default = "PeerCleanupConfig::bluetooth_default")]
    pub peer_cleanup: PeerCleanupConfig,
}

/// Bluetooth RFCOMM direct-channel configuration.
///
/// RFCOMM is independent from Bluetooth PAN/NAP. It opens a Bluetooth RFCOMM
/// channel to paired devices whose names match `device_name_prefix`, exchanges
/// PIM identity frames, and can bridge the resulting byte stream to the local
/// TCP transport listener so the rest of the daemon sees a normal peer session.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BluetoothRfcommConfig {
    /// Enable the Linux RFCOMM service. Defaults to `false` (opt-in).
    #[serde(default)]
    pub enabled: bool,
    /// RFCOMM channel to bind and dial.
    #[serde(default = "default_bluetooth_rfcomm_channel")]
    pub channel: u8,
    /// Prefix used to identify paired PIM peers by Bluetooth device name.
    #[serde(default = "default_bluetooth_device_name_prefix")]
    pub device_name_prefix: String,
    /// Enable outbound paired-device scanning and dialing.
    #[serde(default = "default_bluetooth_rfcomm_outbound_enabled")]
    pub outbound_enabled: bool,
    /// Poll interval for outbound paired-device scans, in milliseconds.
    #[serde(default = "default_bluetooth_rfcomm_poll_interval_ms")]
    pub poll_interval_ms: u64,
    /// Bridge established RFCOMM sessions to the local TCP transport listener.
    #[serde(default = "default_bluetooth_rfcomm_bridge_to_tcp")]
    pub bridge_to_tcp: bool,
    /// Periodic cleanup of unreachable paired peers — see
    /// [`PeerCleanupConfig`]. The destructive action is
    /// `bluetoothctl remove <bd_addr>`. Bluetooth-flavoured defaults:
    /// enabled, 2 h lifetime, 1 h sweep cadence.
    #[serde(default = "PeerCleanupConfig::bluetooth_default")]
    pub peer_cleanup: PeerCleanupConfig,
}

/// Messaging subsystem configuration. Exposes the
/// `[messaging.broadcast]` policy that governs how this node
/// advertises its identity across the mesh, plus a peer-cleanup
/// subsection that ages out stale entries from the daemon's
/// `peers_seen` identity keystore.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MessagingConfig {
    /// Identity-broadcast policy for the multi-hop mesh.
    #[serde(default)]
    pub broadcast: BroadcastConfig,
    /// Periodic cleanup of unreachable mesh-identity entries in the
    /// daemon's `peers_seen` table — see [`PeerCleanupConfig`]. The
    /// destructive action drops the row + emits `peer_forgotten`.
    /// Mesh-identity defaults (90 days / daily): identity loss
    /// forces a fresh handshake on next contact (cheap), so a long
    /// horizon keeps the keystore from growing forever on a long-
    /// running node.
    #[serde(default = "PeerCleanupConfig::mesh_identity_default")]
    pub peer_cleanup: PeerCleanupConfig,
}

impl Default for MessagingConfig {
    fn default() -> Self {
        Self {
            broadcast: BroadcastConfig::default(),
            peer_cleanup: PeerCleanupConfig::mesh_identity_default(),
        }
    }
}

/// Identity-broadcast policy.
///
/// Broadcasts are routed `PeerInfo` control frames sent to every node
/// in the local routing table — they let multi-hop peers learn each
/// other's `node_id + x25519_pubkey` without an out-of-band exchange.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BroadcastConfig {
    /// Cadence between scheduled outbound broadcast cycles, in seconds.
    /// `None` (or omitted) disables the periodic task; one-shot
    /// `peers.broadcast_identity_now` calls still work. Values below
    /// `MIN_OUTGOING_INTERVAL_S` are rejected at the RPC layer.
    #[serde(default)]
    pub outgoing_interval_s: Option<u64>,
    /// When `true`, routed `PeerInfo` arrivals (the inbound side of a
    /// broadcast) are surfaced as `peer_seen` events and a discovered
    /// row in the UI. When `false`, the X25519 key is still cached
    /// (replies need it) but no event is emitted — useful on noisy
    /// meshes where the local node does not want surfaced discovery.
    #[serde(default = "default_broadcast_watch_incoming")]
    pub watch_incoming: bool,
    /// Minimum seconds that must elapse between accepted broadcasts
    /// from any single peer. Subsequent broadcasts from the same peer
    /// arriving sooner are dropped before the keystore is touched —
    /// a single misbehaving peer cannot flood the keystore or the UI.
    #[serde(default = "default_broadcast_min_peer_interval_s")]
    pub min_peer_interval_s: u64,
    /// Periodic cleanup of `state.broadcast_peer_last_seen` rate-
    /// limit entries — see [`PeerCleanupConfig`]. Ephemeral defaults
    /// (7 days / 1 h): the rate-limit map costs ~32 bytes per peer,
    /// so the horizon is generous; the goal is bounding worst-case
    /// growth on long-running nodes that see thousands of unique
    /// broadcasters.
    #[serde(default = "PeerCleanupConfig::ephemeral_default")]
    pub peer_cleanup: PeerCleanupConfig,
}

impl Default for BroadcastConfig {
    fn default() -> Self {
        Self {
            outgoing_interval_s: None,
            watch_incoming: default_broadcast_watch_incoming(),
            min_peer_interval_s: default_broadcast_min_peer_interval_s(),
            peer_cleanup: PeerCleanupConfig::ephemeral_default(),
        }
    }
}

impl BroadcastConfig {
    /// Floor enforced by the RPC layer when a non-`None`
    /// `outgoing_interval_s` is supplied. Below this we'd start
    /// hammering the mesh.
    pub const MIN_OUTGOING_INTERVAL_S: u64 = 30;
}

fn default_broadcast_watch_incoming() -> bool {
    true
}

fn default_broadcast_min_peer_interval_s() -> u64 {
    60
}

// Default value functions

fn default_data_dir() -> PathBuf {
    PathBuf::from("~/.pim")
}

fn default_interface_name() -> String {
    "pim0".into()
}

fn default_mtu() -> u32 {
    1400
}

fn default_mesh_ip() -> String {
    "auto".into()
}

fn default_discovery_enabled() -> bool {
    true
}

fn default_discovery_port() -> u16 {
    9101
}

fn default_broadcast_interval_ms() -> u64 {
    5000
}

fn default_peer_timeout_ms() -> u64 {
    30000
}

fn default_connect_relays() -> bool {
    true
}

fn default_connect_gateways() -> bool {
    true
}

fn default_transport_type() -> String {
    "tcp".into()
}

fn default_listen_port() -> u16 {
    9100
}

fn default_max_reconnect_attempts() -> u32 {
    20
}

fn default_connect_timeout_ms() -> u64 {
    3_000
}

fn default_max_hops() -> u8 {
    10
}

fn default_route_algorithm() -> String {
    "distance-vector".into()
}

fn default_route_expiry_s() -> u64 {
    300
}

fn default_dns_servers() -> Vec<String> {
    vec!["1.1.1.1".into(), "1.0.0.1".into(), "8.8.8.8".into()]
}

fn default_nat_interface() -> String {
    "eth0".into()
}

fn default_max_connections() -> u32 {
    200
}

fn default_key_file() -> PathBuf {
    PathBuf::from("~/.pim/node.key")
}

fn default_require_encryption() -> bool {
    true
}

fn default_trust_store_file() -> PathBuf {
    PathBuf::from("~/.pim/trusted-peers.toml")
}

fn default_wfd_interface() -> String {
    "wlan0".into()
}

fn default_wfd_go_intent() -> u8 {
    7
}

fn default_wfd_listen_channel() -> u8 {
    6
}

fn default_wfd_op_channel() -> u8 {
    6
}

fn default_wfd_connect_method() -> String {
    "pbc".into()
}

fn default_bluetooth_interface() -> String {
    #[cfg(target_os = "macos")]
    {
        "bridge0".into()
    }

    #[cfg(not(target_os = "macos"))]
    {
        "auto".into()
    }
}

fn default_bluetooth_radio_discovery_enabled() -> bool {
    true
}

fn default_bluetooth_device_name_prefix() -> String {
    "PIM-".into()
}

fn default_bluetooth_connect_pan() -> bool {
    true
}

fn default_bluetooth_nap_bridge() -> String {
    "br-bt".into()
}

fn default_bluetooth_nap_bridge_addr() -> String {
    "192.168.44.1/24".into()
}

fn default_bluetooth_dhcp_enabled() -> bool {
    true
}

fn default_bluetooth_dhcp_lease_time() -> String {
    "12h".into()
}

fn default_bluetooth_request_dhcp() -> bool {
    true
}

fn default_bluetooth_auto_discover_peers() -> bool {
    true
}

fn default_bluetooth_poll_interval_ms() -> u64 {
    2_000
}

fn default_bluetooth_scan_interval_ms() -> u64 {
    5_000
}

fn default_bluetooth_peer_discovery_interval_ms() -> u64 {
    2_000
}

fn default_bluetoothctl_timeout_s() -> u64 {
    15
}

fn default_bluetooth_discoverable_timeout_s() -> u64 {
    180
}

fn default_bluetooth_startup_timeout_ms() -> u64 {
    15_000
}

fn default_bluetooth_rfcomm_channel() -> u8 {
    22
}

fn default_bluetooth_rfcomm_outbound_enabled() -> bool {
    true
}

fn default_bluetooth_rfcomm_poll_interval_ms() -> u64 {
    30_000
}

fn default_bluetooth_rfcomm_bridge_to_tcp() -> bool {
    true
}

impl Default for InterfaceConfig {
    fn default() -> Self {
        Self {
            name: default_interface_name(),
            mtu: default_mtu(),
            mesh_ip: default_mesh_ip(),
            mesh_ipv6: None,
        }
    }
}

impl Default for DiscoveryConfig {
    fn default() -> Self {
        Self {
            enabled: default_discovery_enabled(),
            port: default_discovery_port(),
            broadcast_interval_ms: default_broadcast_interval_ms(),
            peer_timeout_ms: default_peer_timeout_ms(),
            connect_relays: default_connect_relays(),
            connect_gateways: default_connect_gateways(),
        }
    }
}

impl Default for MeshKdfConfig {
    fn default() -> Self {
        Self {
            m_cost_kib: default_mesh_kdf_m_cost_kib(),
            t_cost: default_mesh_kdf_t_cost(),
            p_cost: default_mesh_kdf_p_cost(),
        }
    }
}

fn default_mesh_kdf_m_cost_kib() -> u32 {
    65536
}

fn default_mesh_kdf_t_cost() -> u32 {
    3
}

fn default_mesh_kdf_p_cost() -> u32 {
    1
}

impl Default for TransportConfig {
    fn default() -> Self {
        Self {
            r#type: default_transport_type(),
            listen_port: default_listen_port(),
            max_reconnect_attempts: default_max_reconnect_attempts(),
            connect_timeout_ms: default_connect_timeout_ms(),
            peer_cleanup: PeerCleanupConfig::ephemeral_default(),
        }
    }
}

impl Default for RoutingConfig {
    fn default() -> Self {
        Self {
            max_hops: default_max_hops(),
            algorithm: default_route_algorithm(),
            route_expiry_s: default_route_expiry_s(),
            dns_servers: default_dns_servers(),
        }
    }
}

impl Default for GatewayConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            nat_interface: default_nat_interface(),
            max_connections: default_max_connections(),
        }
    }
}

impl Default for WifiDirectConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            interface: default_wfd_interface(),
            go_intent: default_wfd_go_intent(),
            listen_channel: default_wfd_listen_channel(),
            op_channel: default_wfd_op_channel(),
            connect_method: default_wfd_connect_method(),
            peer_cleanup: PeerCleanupConfig::bluetooth_default(),
        }
    }
}

impl Default for BluetoothConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            interface: default_bluetooth_interface(),
            radio_discovery_enabled: default_bluetooth_radio_discovery_enabled(),
            device_name_prefix: default_bluetooth_device_name_prefix(),
            local_alias: String::new(),
            connect_pan: default_bluetooth_connect_pan(),
            serve_nap: false,
            nap_bridge: default_bluetooth_nap_bridge(),
            nap_bridge_addr: default_bluetooth_nap_bridge_addr(),
            dhcp_enabled: default_bluetooth_dhcp_enabled(),
            dhcp_range: None,
            dhcp_lease_time: default_bluetooth_dhcp_lease_time(),
            dhcp_dns: None,
            request_dhcp: default_bluetooth_request_dhcp(),
            auto_discover_peers: default_bluetooth_auto_discover_peers(),
            poll_interval_ms: default_bluetooth_poll_interval_ms(),
            scan_interval_ms: default_bluetooth_scan_interval_ms(),
            peer_discovery_interval_ms: default_bluetooth_peer_discovery_interval_ms(),
            bluetoothctl_timeout_s: default_bluetoothctl_timeout_s(),
            discoverable_timeout_s: default_bluetooth_discoverable_timeout_s(),
            startup_timeout_ms: default_bluetooth_startup_timeout_ms(),
            peer_cleanup: PeerCleanupConfig::bluetooth_default(),
        }
    }
}

impl Default for BluetoothRfcommConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            channel: default_bluetooth_rfcomm_channel(),
            device_name_prefix: default_bluetooth_device_name_prefix(),
            outbound_enabled: default_bluetooth_rfcomm_outbound_enabled(),
            poll_interval_ms: default_bluetooth_rfcomm_poll_interval_ms(),
            bridge_to_tcp: default_bluetooth_rfcomm_bridge_to_tcp(),
            peer_cleanup: PeerCleanupConfig::bluetooth_default(),
        }
    }
}

impl Default for SecurityConfig {
    fn default() -> Self {
        Self {
            key_file: default_key_file(),
            require_encryption: default_require_encryption(),
            authorization_policy: AuthorizationPolicy::default(),
            authorized_peers: Vec::new(),
            trust_store_file: default_trust_store_file(),
        }
    }
}