rns-core 0.7.0

Wire protocol, transport routing, and link/resource engine for the Reticulum Network Stack
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
// =============================================================================
// Reticulum protocol constants
// Ported from Python RNS source
// =============================================================================

use rns_crypto::stamp_value::StampCost;

// --- From Reticulum.py ---

/// Maximum transmission unit in bytes
pub const MTU: usize = 500;

/// Truncated hash length in bits
pub const TRUNCATED_HASHLENGTH: usize = 128;

/// Minimum header size: 2 (flags + hops) + 1 (context) + 16 (dest hash)
pub const HEADER_MINSIZE: usize = 2 + 1 + (TRUNCATED_HASHLENGTH / 8);

/// Maximum header size: 2 (flags + hops) + 1 (context) + 32 (transport_id + dest hash)
pub const HEADER_MAXSIZE: usize = 2 + 1 + (TRUNCATED_HASHLENGTH / 8) * 2;

/// Minimum IFAC size
pub const IFAC_MIN_SIZE: usize = 1;

/// Maximum data unit: MTU - HEADER_MAXSIZE - IFAC_MIN_SIZE
pub const MDU: usize = MTU - HEADER_MAXSIZE - IFAC_MIN_SIZE;

// --- From Identity.py ---

/// Full key size in bits (256 X25519 + 256 Ed25519)
pub const KEYSIZE: usize = 512;

/// Ratchet key size in bits
pub const RATCHETSIZE: usize = 256;

/// Received ratchet expiry in seconds (30 days)
pub const RATCHET_EXPIRY: u64 = 60 * 60 * 24 * 30;

/// Token overhead in bytes (16 IV + 32 HMAC)
pub const TOKEN_OVERHEAD: usize = 48;

/// AES-128 block size in bytes
pub const AES128_BLOCKSIZE: usize = 16;

/// Full hash length in bits (SHA-256)
pub const HASHLENGTH: usize = 256;

/// Signature length in bits (Ed25519)
pub const SIGLENGTH: usize = KEYSIZE;

/// Name hash length in bits. Defined with the other identity sizes so the
/// derived announce and destination layouts read in one block.
pub const NAME_HASH_LENGTH: usize = 80;

/// Derived key length in bytes. The retired 32-byte legacy derivation is not
/// part of the current wire format and has no native constant.
pub const DERIVED_KEY_LENGTH: usize = 64;

/// Detached stamp length in bytes (14-byte stamps keep the search space viable)
pub const STAMP_LENGTH: usize = 14;

/// Extension payload header length in bytes (marker + flags)
pub const EXT_HEADER_LENGTH: usize = 2;

/// Announced link request cost length in bytes: the 1/256 fraction, then the
/// whole part in the trailing anchor (two bytes since upstream 796ef2d4).
pub const LR_COST_LENGTH: usize = 2;

/// Announce extension payload length in bytes (header + stamp + link cost)
pub const EXT_PAYLOAD_LENGTH: usize = EXT_HEADER_LENGTH + STAMP_LENGTH + LR_COST_LENGTH;

/// Path request extension payload length in bytes (header + stamp)
pub const PR_EXT_PAYLOAD_LENGTH: usize = EXT_HEADER_LENGTH + STAMP_LENGTH;

/// Extension flags byte marking an unstamped path request relayed by a
/// stamp-aware node (transitional sentinel, upstream 7c1b6486).
pub const PR_FLAG_UNSTAMPED_RELAY: u8 = 0xFF;

/// Marker identifying a stamp extension payload. Never a valid msgpack
/// object boundary, so it cannot be confused with application data.
pub const EXT_PAYLOAD_MARKER: u8 = 0xC1;

/// Unstamped 1.6.0 path request length: destination hash, transport id, tag.
pub const PATH_REQUEST_LENGTH: usize = 48;

/// Stamped path request length: unstamped data plus the extension payload.
pub const PATH_REQUEST_STAMPED_LENGTH: usize = PATH_REQUEST_LENGTH + PR_EXT_PAYLOAD_LENGTH;

/// Legacy path request length without a transport id: destination hash and
/// tag (`Transport.PR_LEGACY_LENGTH`, rgit `cda4d72b`).
pub const PATH_REQUEST_LEGACY_LENGTH: usize = 32;

/// Whether `len` is a path request length upstream accepts since rgit
/// `cda4d72b`: legacy, unstamped or stamped.
pub const fn is_valid_path_request_length(len: usize) -> bool {
    len == PATH_REQUEST_LEGACY_LENGTH
        || len == PATH_REQUEST_LENGTH
        || len == PATH_REQUEST_STAMPED_LENGTH
}

/// Whether incoming announces are checked for stamps.
pub const STAMPS_ENABLED: bool = true;

/// Whether stamp requirements are enforced. Disabled during the upstream
/// transition period; unstamped announces are still accepted.
pub const STAMPS_ENFORCED: bool = false;

/// Hard cap on stamp cost.
pub const STAMP_COST_CAP: u8 = 100;

/// Default required announce stamp cost (`Transport.DESTINATION_COST`,
/// 23.5 since rgit a8cc6cb2; `cost_to_fp(23.5)`).
pub const DESTINATION_STAMP_COST: StampCost = StampCost::from_fp(6016);

/// Default target stamp value for emitted announces (upstream
/// `Transport.DESTINATION_VALUE`, raised from 23 to 25 in d7749b0b).
pub const DESTINATION_STAMP_VALUE: StampCost = StampCost::from_whole(25);

/// Default required path request stamp cost (`Transport.PR_COST`, 19.8
/// in a8cc6cb2 and 15.8 since f57fa265; `cost_to_fp(15.8)`).
pub const PATH_REQUEST_STAMP_COST: StampCost = StampCost::from_fp(4045);

/// Default target stamp value for emitted path requests (`Transport.PR_VALUE`,
/// 19.8 in a8cc6cb2 and 15.8 since f57fa265).
pub const PATH_REQUEST_STAMP_VALUE: StampCost = StampCost::from_fp(4045);

/// Default link request stamp cost (`Transport.LR_COST`, 19.2 in a8cc6cb2
/// and 12.5 since f57fa265; `cost_to_fp(12.5)`). A destination that sets no
/// cost of its own announces and requires it, fraction included since
/// 796ef2d4, and `ALWAYS_STAMP_LRS` stamps requests to destinations that
/// announced no cost at it.
pub const LINK_REQUEST_STAMP_COST: StampCost = StampCost::from_fp(3200);

/// Link request stamp cost of the published blackhole list destination,
/// `rnstransport.info.blackhole` (`blackhole_destination.set_link_cost(5.25)`
/// since upstream 426673e9; `cost_to_fp(5.25)`).
pub const BLACKHOLE_LINK_COST: StampCost = StampCost::from_fp(1344);

/// Whether destination announces emitted by this instance are stamped.
pub const STAMP_DESTINATIONS: bool = false;

/// Whether path requests emitted by this instance are stamped.
pub const STAMP_PRS: bool = false;

/// Whether link requests emitted by this instance are stamped when the
/// destination announces a cost. On again since upstream a8cc6cb2, which
/// also dropped the `[stamps] stamp_lrs` option.
pub const STAMP_LRS: bool = true;

/// Whether link requests are stamped at `LINK_REQUEST_STAMP_COST` even when
/// the destination announced no cost (`Transport.ALWAYS_STAMP_LRS`,
/// `[stamps] always_stamp_lrs`, a8cc6cb2).
pub const ALWAYS_STAMP_LRS: bool = false;

/// Whether link request stamp requirements are enforced.
pub const LR_STAMPS_ENFORCED: bool = false;

/// Global hard cap on the announced link request cost.
pub const LINK_COST_CAP: u8 = 40;

/// Timeout for deferred link request stamp generation (seconds).
pub const LINK_STAMP_TIMEOUT: u64 = 15;

/// Timeout for path request stamp generation on the stamper worker
/// (seconds, upstream `Transport.PR_STAMP_TIMEOUT`). Emission of the request
/// is cancelled when no stamp is found in time. Upstream c0fe08c0 only
/// corrected the spelling in this attribute's comment; the value and its
/// use are unchanged from d7749b0b.
pub const PATH_REQUEST_STAMP_TIMEOUT: u64 = 15;

/// Lower bound applied to the configurable stamp generation timeouts
/// (`lr_stamp_timeout`, `pr_stamp_timeout`), in seconds.
pub const STAMP_TIMEOUT_MIN: f64 = 0.020;

const _: () = assert!(STAMP_COST_CAP as u32 == rns_crypto::stamp_value::STAMP_COST_CAP);
const _: () = assert!(LINK_REQUEST_STAMP_COST.whole() <= LINK_COST_CAP as u32);

// --- From Packet.py ---

/// Packet types
pub const PACKET_TYPE_DATA: u8 = 0x00;
pub const PACKET_TYPE_ANNOUNCE: u8 = 0x01;
pub const PACKET_TYPE_LINKREQUEST: u8 = 0x02;
pub const PACKET_TYPE_PROOF: u8 = 0x03;

/// Header types
pub const HEADER_1: u8 = 0x00;
pub const HEADER_2: u8 = 0x01;

/// Packet context types
pub const CONTEXT_NONE: u8 = 0x00;
pub const CONTEXT_RESOURCE: u8 = 0x01;
pub const CONTEXT_RESOURCE_ADV: u8 = 0x02;
pub const CONTEXT_RESOURCE_REQ: u8 = 0x03;
pub const CONTEXT_RESOURCE_HMU: u8 = 0x04;
pub const CONTEXT_RESOURCE_PRF: u8 = 0x05;
pub const CONTEXT_RESOURCE_ICL: u8 = 0x06;
pub const CONTEXT_RESOURCE_RCL: u8 = 0x07;
pub const CONTEXT_CACHE_REQUEST: u8 = 0x08;
pub const CONTEXT_REQUEST: u8 = 0x09;
pub const CONTEXT_RESPONSE: u8 = 0x0A;
pub const CONTEXT_PATH_RESPONSE: u8 = 0x0B;
pub const CONTEXT_COMMAND: u8 = 0x0C;
pub const CONTEXT_COMMAND_STATUS: u8 = 0x0D;
pub const CONTEXT_CHANNEL: u8 = 0x0E;
pub const CONTEXT_KEEPALIVE: u8 = 0xFA;
pub const CONTEXT_LINKIDENTIFY: u8 = 0xFB;
pub const CONTEXT_LINKCLOSE: u8 = 0xFC;
pub const CONTEXT_LINKPROOF: u8 = 0xFD;
pub const CONTEXT_LRRTT: u8 = 0xFE;
pub const CONTEXT_LRPROOF: u8 = 0xFF;

/// Context flag values
pub const FLAG_SET: u8 = 0x01;
pub const FLAG_UNSET: u8 = 0x00;

/// Encrypted MDU: floor((MDU - TOKEN_OVERHEAD - KEYSIZE/16) / AES128_BLOCKSIZE) * AES128_BLOCKSIZE - 1
pub const ENCRYPTED_MDU: usize = {
    let numerator = MDU - TOKEN_OVERHEAD - KEYSIZE / 16;
    (numerator / AES128_BLOCKSIZE) * AES128_BLOCKSIZE - 1
};

/// Plain MDU (same as MDU)
pub const PLAIN_MDU: usize = MDU;

/// Explicit proof length: HASHLENGTH/8 + SIGLENGTH/8 = 32 + 64 = 96
pub const EXPL_LENGTH: usize = HASHLENGTH / 8 + SIGLENGTH / 8;

/// Implicit proof length: SIGLENGTH/8 = 64
pub const IMPL_LENGTH: usize = SIGLENGTH / 8;

/// Receipt status constants
pub const RECEIPT_FAILED: u8 = 0x00;
pub const RECEIPT_SENT: u8 = 0x01;
pub const RECEIPT_DELIVERED: u8 = 0x02;
pub const RECEIPT_CULLED: u8 = 0xFF;

// --- From Destination.py ---

/// Destination types
pub const DESTINATION_SINGLE: u8 = 0x00;
pub const DESTINATION_GROUP: u8 = 0x01;
pub const DESTINATION_PLAIN: u8 = 0x02;
pub const DESTINATION_LINK: u8 = 0x03;

/// Destination directions
pub const DESTINATION_IN: u8 = 0x11;
pub const DESTINATION_OUT: u8 = 0x12;

// --- From Transport.py ---

/// Transport types
pub const TRANSPORT_BROADCAST: u8 = 0x00;
pub const TRANSPORT_TRANSPORT: u8 = 0x01;
pub const TRANSPORT_RELAY: u8 = 0x02;
pub const TRANSPORT_TUNNEL: u8 = 0x03;

/// Maximum hops
pub const PATHFINDER_M: u8 = 128;

/// Default transport hop limiter (upstream `Transport.HOP_LIMITER`).
/// Inbound packets whose hop count has reached the node's hop limiter are
/// dropped before the packet filter and counted per interface as
/// `hop_limiter_hits`. The limiter is a runtime value configured with
/// `[reticulum] hop_limiter`, clamped to `1..=PATHFINDER_M`; packets with a
/// hop count at or above `PATHFINDER_M` already fail to decode.
pub const HOP_LIMITER: u8 = PATHFINDER_M;

// --- PATHFINDER algorithm ---

/// Retransmit retries (total sends = PATHFINDER_R + 1)
pub const PATHFINDER_R: u8 = 1;

/// Grace period between retries (seconds)
pub const PATHFINDER_G: f64 = 5.0;

/// Random window for announce rebroadcast (seconds)
pub const PATHFINDER_RW: f64 = 0.5;

/// Path expiry = 7 days (seconds)
pub const PATHFINDER_E: f64 = 604800.0;

// --- Path expiry by interface mode ---

/// Access Point path expiry = 1 day
pub const AP_PATH_TIME: f64 = 86400.0;

/// Roaming path expiry = 6 hours
pub const ROAMING_PATH_TIME: f64 = 21600.0;

// --- Announce bandwidth cap ---

/// Default announce bandwidth cap (2% of interface bandwidth)
pub const ANNOUNCE_CAP: f64 = 0.02;

/// Maximum queued announces per interface
pub const MAX_QUEUED_ANNOUNCES: usize = 4096;

/// Queued announce lifetime (3 hours)
pub const QUEUED_ANNOUNCE_LIFE: f64 = 3.0 * 60.0 * 60.0;

/// Retention TTL for announce retransmission state (seconds)
pub const ANNOUNCE_TABLE_TTL: f64 = 30.0;

/// Maximum retained bytes for announce retransmission state
pub const ANNOUNCE_TABLE_MAX_BYTES: usize = 4 * 1024 * 1024;

// --- Table limits ---

/// How many local rebroadcasts of an announce is allowed
pub const LOCAL_REBROADCASTS_MAX: u8 = 2;

/// Maximum number of random blobs per destination to keep in memory
pub const MAX_RANDOM_BLOBS: usize = 64;

/// Maximum number of announce timestamps to keep per destination
pub const MAX_RATE_TIMESTAMPS: usize = 16;

/// Maximum packet hashlist size before rotation
pub const HASHLIST_MAXSIZE: usize = 250_000;

/// Maximum announce signature cache entries (dedup verified signatures)
pub const ANNOUNCE_SIG_CACHE_MAXSIZE: usize = 2_000;

/// TTL for announce signature cache entries (seconds)
pub const ANNOUNCE_SIG_CACHE_TTL: f64 = 600.0;

// --- Timeouts ---

/// Reverse table entry timeout (8 minutes)
pub const REVERSE_TIMEOUT: f64 = 480.0;

/// Destination table entry timeout (7 days)
pub const DESTINATION_TIMEOUT: f64 = 604800.0;

/// Path table entry retainment after the last verifiable liveness event.
pub const PATH_TIMEOUT: f64 = DESTINATION_TIMEOUT;

/// Linger time for never-used paths on transport instances (1.75 hours).
pub const UNUSED_PATH_LINGER: f64 = 6300.0;

/// Linger time for never-used paths on leaf instances (12 minutes).
pub const UNUSED_PATH_LINGER_LEAF: f64 = 720.0;

/// Maximum unused path pool target.
pub const PATH_POOL_UNUSED_TARGET: usize = 2048;

/// Interfaces with a known bitrate below this many bits per second are slow:
/// never-used paths learnt on them linger `PATH_SLOW_LINGER_FACTOR` times
/// longer and are culled last when the unused pool exceeds its target.
pub const PATH_SLOW_BITRATE: u64 = 25_000;

/// Linger time multiplier for never-used paths on slow interfaces.
pub const PATH_SLOW_LINGER_FACTOR: f64 = 10.0;

/// Default announce rate table retainment (1 hour).
pub const RATE_TABLE_RETAIN: f64 = 3600.0;

/// Tunnel table entry timeout (8 hours)
pub const TUNNEL_TIMEOUT: f64 = 28800.0;

/// Tunnel path retention timeout (8 hours)
pub const TUNNEL_PATH_TIMEOUT: f64 = 28800.0;

/// Link stale time = 2 * KEEPALIVE(360) = 720 seconds
pub const LINK_STALE_TIME: f64 = 720.0;

/// Link timeout = STALE_TIME * 1.25 = 900 seconds
pub const LINK_TIMEOUT: f64 = 900.0;

/// Link establishment timeout per hop (seconds)
pub const LINK_ESTABLISHMENT_TIMEOUT_PER_HOP: f64 = 6.0;

// --- Path request ---

/// Default timeout for path requests (seconds)
pub const PATH_REQUEST_TIMEOUT: f64 = 15.0;

/// Timeout for suppressing or batching repeated path requests (seconds).
pub const PATH_REQUEST_GATE_TIMEOUT: f64 = 45.0;

/// Minimum usable interface bitrate for timeout calculations (bits/second).
pub const MINIMUM_BITRATE: u64 = 5;

/// Grace time before a path announcement is made (seconds)
pub const PATH_REQUEST_GRACE: f64 = 0.4;

/// Extra grace time for roaming-mode interfaces (seconds)
pub const PATH_REQUEST_RG: f64 = 1.5;

/// Minimum interval for automated path requests (seconds)
pub const PATH_REQUEST_MI: f64 = 20.0;

/// Seconds between path rediscovery requests sent from the queue
/// (`Transport.discovery_pr_tx_throttle`).
pub const DISCOVERY_PR_TX_THROTTLE: f64 = 0.5;

/// Maximum number of queued path rediscovery requests
/// (`Transport.max_queued_discovery_prs`).
pub const MAX_QUEUED_DISCOVERY_PRS: usize = 32;

/// Maximum amount of unique path request tags to remember
pub const MAX_PR_TAGS: usize = 16_000;

// --- Job intervals ---

/// Announce check interval (seconds)
pub const ANNOUNCES_CHECK_INTERVAL: f64 = 1.0;

/// Table culling interval (seconds)
pub const TABLES_CULL_INTERVAL: f64 = 5.0;

/// Link check interval (seconds)
pub const LINKS_CHECK_INTERVAL: f64 = 1.0;

// --- Ingress Control (from Interface.py) ---

/// Interface "new" period in seconds (2 hours).
pub const IC_NEW_TIME: f64 = 7200.0;
/// Announce frequency threshold for new interfaces (announces/sec).
pub const IC_BURST_FREQ_NEW: f64 = 3.0;
/// Announce frequency threshold for mature interfaces (announces/sec).
pub const IC_BURST_FREQ: f64 = 10.0;
/// Path request frequency threshold for new interfaces (requests/sec).
pub const IC_PR_BURST_FREQ_NEW: f64 = 3.0;
/// Path request frequency threshold for mature interfaces (requests/sec).
pub const IC_PR_BURST_FREQ: f64 = 8.0;
/// Minimum burst active duration in seconds.
pub const IC_BURST_HOLD: f64 = 15.0;
/// Penalty delay before releasing held announces (seconds).
pub const IC_BURST_PENALTY: f64 = 15.0;
/// Interval between individual held announce releases (seconds).
pub const IC_HELD_RELEASE_INTERVAL: f64 = 5.0;
/// Minimum path request samples before egress limiting.
pub const EC_BURST_MIN_SAMPLES: usize = 2;
/// Default egress path request limit (requests/sec).
pub const EC_PR_FREQ: f64 = 5.0;
/// Maximum held announces per interface.
pub const IC_MAX_HELD_ANNOUNCES: usize = 256;

// --- Interface modes (from Interface.py) ---

pub const MODE_FULL: u8 = 0x01;
pub const MODE_POINT_TO_POINT: u8 = 0x02;
pub const MODE_ACCESS_POINT: u8 = 0x03;
pub const MODE_ROAMING: u8 = 0x04;
pub const MODE_BOUNDARY: u8 = 0x05;
pub const MODE_GATEWAY: u8 = 0x06;
pub const MODE_INTERNAL: u8 = 0x07;

/// Interface modes that forward path requests for unknown destinations.
/// Python: Interface.DISCOVER_PATHS_FOR = [MODE_ACCESS_POINT, MODE_GATEWAY, MODE_ROAMING, MODE_INTERNAL]
pub const DISCOVER_PATHS_FOR: [u8; 4] =
    [MODE_ACCESS_POINT, MODE_GATEWAY, MODE_ROAMING, MODE_INTERNAL];

/// Interface modes searched for an unknown path request received on a boundary interface.
pub const BOUNDARY_SEARCH_MODES: [u8; 2] = [MODE_BOUNDARY, MODE_GATEWAY];

/// Discovery path request expiry (seconds) — requests older than this are culled.
pub const DISCOVERY_PATH_REQUEST_TIMEOUT: f64 = 15.0;

// --- Path states ---

pub const STATE_UNKNOWN: u8 = 0x00;
pub const STATE_UNRESPONSIVE: u8 = 0x01;
pub const STATE_RESPONSIVE: u8 = 0x02;

// --- From Link.py ---

/// Link ephemeral public key size: 32 (X25519) + 32 (Ed25519)
pub const LINK_ECPUBSIZE: usize = 64;

/// Link key size in bytes
pub const LINK_KEYSIZE: usize = 32;

/// Link MTU signalling bytes size
pub const LINK_MTU_SIZE: usize = 3;

/// Maximum keepalive interval in seconds
pub const LINK_KEEPALIVE_MAX: f64 = 360.0;

/// Minimum keepalive interval in seconds
pub const LINK_KEEPALIVE_MIN: f64 = 5.0;

/// Maximum RTT used for keepalive scaling
pub const LINK_KEEPALIVE_MAX_RTT: f64 = 1.75;

/// RTT timeout factor for stale→close transition
pub const LINK_KEEPALIVE_TIMEOUT_FACTOR: f64 = 4.0;

/// Grace period for stale→close transition
pub const LINK_STALE_GRACE: f64 = 5.0;

/// Factor to compute stale_time from keepalive
pub const LINK_STALE_FACTOR: f64 = 2.0;

/// Traffic timeout factor
pub const LINK_TRAFFIC_TIMEOUT_FACTOR: f64 = 6.0;

/// Link MDU: floor((MTU - IFAC_MIN_SIZE - HEADER_MINSIZE - TOKEN_OVERHEAD) / AES128_BLOCKSIZE) * AES128_BLOCKSIZE - 1
pub const LINK_MDU: usize = {
    let numerator = MTU - IFAC_MIN_SIZE - HEADER_MINSIZE - TOKEN_OVERHEAD;
    (numerator / AES128_BLOCKSIZE) * AES128_BLOCKSIZE - 1
};

/// Link MTU bytemask (21-bit MTU field)
pub const LINK_MTU_BYTEMASK: u32 = 0x1FFFFF;

/// Smallest link MTU that leaves room for one AES block of link payload:
/// IFAC_MIN_SIZE + HEADER_MINSIZE + TOKEN_OVERHEAD + AES128_BLOCKSIZE = 84.
/// Smaller signalled MTUs are raised to this value.
pub const LINK_MTU_MIN: u32 =
    (IFAC_MIN_SIZE + HEADER_MINSIZE + TOKEN_OVERHEAD + AES128_BLOCKSIZE) as u32;

/// Largest MTU expressible in the 21-bit link signalling field.
pub const LINK_MTU_MAX: u32 = LINK_MTU_BYTEMASK;

/// Link mode bytemask (3-bit mode field in upper byte)
pub const LINK_MODE_BYTEMASK: u8 = 0xE0;

// --- From Channel.py ---

/// Initial window size at channel setup
pub const CHANNEL_WINDOW: u16 = 2;

/// Absolute minimum window size
pub const CHANNEL_WINDOW_MIN: u16 = 2;

/// Minimum window limit for slow links
pub const CHANNEL_WINDOW_MIN_LIMIT_SLOW: u16 = 2;

/// Minimum window limit for medium-speed links
pub const CHANNEL_WINDOW_MIN_LIMIT_MEDIUM: u16 = 5;

/// Minimum window limit for fast links
pub const CHANNEL_WINDOW_MIN_LIMIT_FAST: u16 = 16;

/// Maximum window size for slow links
pub const CHANNEL_WINDOW_MAX_SLOW: u16 = 5;

/// Maximum window size for medium-speed links
pub const CHANNEL_WINDOW_MAX_MEDIUM: u16 = 12;

/// Maximum window size for fast links
pub const CHANNEL_WINDOW_MAX_FAST: u16 = 48;

/// Minimum flexibility between window_max and window_min
pub const CHANNEL_WINDOW_FLEXIBILITY: u16 = 4;

/// Maximum sequence number
pub const CHANNEL_SEQ_MAX: u16 = 0xFFFF;

/// Sequence number modulus
pub const CHANNEL_SEQ_MODULUS: u32 = 0x10000;

/// Maximum number of send tries per envelope
pub const CHANNEL_MAX_TRIES: u8 = 5;

/// RTT threshold for fast links
pub const CHANNEL_RTT_FAST: f64 = 0.18;

/// RTT threshold for medium links
pub const CHANNEL_RTT_MEDIUM: f64 = 0.75;

/// RTT threshold for slow links
pub const CHANNEL_RTT_SLOW: f64 = 1.45;

/// Number of consecutive fast rounds to upgrade window
pub const CHANNEL_FAST_RATE_THRESHOLD: u16 = 10;

/// Channel envelope overhead: msgtype(2) + seq(2) + len(2)
pub const CHANNEL_ENVELOPE_OVERHEAD: usize = 6;

// --- From Buffer.py ---

/// System message type for stream data
pub const STREAM_DATA_MSGTYPE: u16 = 0xFF00;

/// Maximum stream ID (14 bits)
pub const STREAM_ID_MAX: u16 = 0x3FFF;

/// Stream data overhead: 2 (stream header) + 6 (channel envelope)
pub const STREAM_DATA_OVERHEAD: usize = 2 + CHANNEL_ENVELOPE_OVERHEAD;

// --- From Resource.py ---

/// Initial window size at beginning of transfer
pub const RESOURCE_WINDOW: usize = 4;

/// Absolute minimum window size during transfer
pub const RESOURCE_WINDOW_MIN: usize = 2;

/// Maximum window size for slow links
pub const RESOURCE_WINDOW_MAX_SLOW: usize = 10;

/// Maximum window size for very slow links
pub const RESOURCE_WINDOW_MAX_VERY_SLOW: usize = 4;

/// Maximum window size for fast links
pub const RESOURCE_WINDOW_MAX_FAST: usize = 75;

/// Global maximum window (for calculations)
pub const RESOURCE_WINDOW_MAX: usize = RESOURCE_WINDOW_MAX_FAST;

/// Minimum flexibility between window_max and window_min
pub const RESOURCE_WINDOW_FLEXIBILITY: usize = 4;

/// Sustained fast-rate rounds before enabling fast window
/// = WINDOW_MAX_SLOW - WINDOW - 2 = 10 - 4 - 2 = 4
pub const RESOURCE_FAST_RATE_THRESHOLD: usize = RESOURCE_WINDOW_MAX_SLOW - RESOURCE_WINDOW - 2;

/// Sustained very-slow-rate rounds before capping to very slow
pub const RESOURCE_VERY_SLOW_RATE_THRESHOLD: usize = 2;

/// Fast rate threshold: 50 Kbps in bytes/sec = 50000 / 8 = 6250.0
pub const RESOURCE_RATE_FAST: f64 = (50 * 1000) as f64 / 8.0;

/// Very slow rate threshold: 2 Kbps in bytes/sec = 2000 / 8 = 250.0
pub const RESOURCE_RATE_VERY_SLOW: f64 = (2 * 1000) as f64 / 8.0;

/// Number of bytes in a map hash
pub const RESOURCE_MAPHASH_LEN: usize = 4;

/// Resource SDU = Packet.MDU (NOT ENCRYPTED_MDU)
pub const RESOURCE_SDU: usize = MDU;

/// Random hash size prepended to resource data
pub const RESOURCE_RANDOM_HASH_SIZE: usize = 4;

/// Maximum efficient resource size (1 MB - 1)
pub const RESOURCE_MAX_EFFICIENT_SIZE: usize = 1024 * 1024 - 1;

/// Maximum metadata size (16 MB - 1)
pub const RESOURCE_METADATA_MAX_SIZE: usize = 16 * 1024 * 1024 - 1;

/// Maximum auto-compress size (64 MB)
pub const RESOURCE_AUTO_COMPRESS_MAX_SIZE: usize = 64 * 1024 * 1024;

/// Part timeout factor (before RTT measured)
pub const RESOURCE_PART_TIMEOUT_FACTOR: f64 = 4.0;

/// Part timeout factor (after first RTT measured)
pub const RESOURCE_PART_TIMEOUT_FACTOR_AFTER_RTT: f64 = 2.0;

/// Proof timeout factor (reduced when awaiting proof)
pub const RESOURCE_PROOF_TIMEOUT_FACTOR: f64 = 3.0;

/// Additional wait allowance, in SDU-sized transfer units, while waiting for HMU data.
pub const RESOURCE_HMU_WAIT_FACTOR: f64 = 3.5;

/// Maximum retries for part transfers
pub const RESOURCE_MAX_RETRIES: usize = 16;

/// Maximum retries for advertisement
pub const RESOURCE_MAX_ADV_RETRIES: usize = 4;

/// Sender grace time (seconds)
pub const RESOURCE_SENDER_GRACE_TIME: f64 = 10.0;

/// Processing grace for advertisement response (seconds)
pub const RESOURCE_PROCESSING_GRACE: f64 = 1.0;

/// Retry grace time (seconds)
pub const RESOURCE_RETRY_GRACE_TIME: f64 = 0.25;

/// Per-retry delay (seconds)
pub const RESOURCE_PER_RETRY_DELAY: f64 = 0.5;

/// Maximum watchdog sleep interval (seconds)
pub const RESOURCE_WATCHDOG_MAX_SLEEP: f64 = 1.0;

/// Response max grace time (seconds)
pub const RESOURCE_RESPONSE_MAX_GRACE_TIME: f64 = 10.0;

/// Advertisement overhead in bytes (fixed msgpack overhead)
pub const RESOURCE_ADVERTISEMENT_OVERHEAD: usize = 134;

/// Maximum hashmap entries per advertisement segment
/// = floor((LINK_MDU - ADVERTISEMENT_OVERHEAD) / MAPHASH_LEN)
pub const RESOURCE_HASHMAP_MAX_LEN: usize =
    (LINK_MDU - RESOURCE_ADVERTISEMENT_OVERHEAD) / RESOURCE_MAPHASH_LEN;

/// Collision guard size = 2 * WINDOW_MAX + HASHMAP_MAX_LEN
pub const RESOURCE_COLLISION_GUARD_SIZE: usize = 2 * RESOURCE_WINDOW_MAX + RESOURCE_HASHMAP_MAX_LEN;

/// Hashmap not exhausted flag
pub const RESOURCE_HASHMAP_IS_NOT_EXHAUSTED: u8 = 0x00;

/// Hashmap exhausted flag
pub const RESOURCE_HASHMAP_IS_EXHAUSTED: u8 = 0xFF;

/// Hashmap empty flag: the advertisement carried no hashmap because it would
/// not fit the link MDU, so the receiver asks for the first segment
pub const RESOURCE_HASHMAP_IS_EMPTY: u8 = 0xFE;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_derived_constants() {
        // MTU = 500
        assert_eq!(MTU, 500);

        // HEADER_MINSIZE = 2 + 1 + 16 = 19
        assert_eq!(HEADER_MINSIZE, 19);

        // HEADER_MAXSIZE = 2 + 1 + 32 = 35
        assert_eq!(HEADER_MAXSIZE, 35);

        // MDU = 500 - 35 - 1 = 464
        assert_eq!(MDU, 464);

        // ENCRYPTED_MDU = floor((464 - 48 - 32) / 16) * 16 - 1 = floor(384/16)*16 - 1 = 24*16 - 1 = 383
        assert_eq!(ENCRYPTED_MDU, 383);

        // PLAIN_MDU = MDU = 464
        assert_eq!(PLAIN_MDU, 464);

        // EXPL_LENGTH = 32 + 64 = 96
        assert_eq!(EXPL_LENGTH, 96);

        // IMPL_LENGTH = 64
        assert_eq!(IMPL_LENGTH, 64);

        // NAME_HASH_LENGTH = 80 bits = 10 bytes
        assert_eq!(NAME_HASH_LENGTH / 8, 10);

        // KEYSIZE = 512 bits = 64 bytes
        assert_eq!(KEYSIZE / 8, 64);

        // SIGLENGTH = 512 bits = 64 bytes
        assert_eq!(SIGLENGTH / 8, 64);

        // TRUNCATED_HASHLENGTH = 128 bits = 16 bytes
        assert_eq!(TRUNCATED_HASHLENGTH / 8, 16);
    }

    #[test]
    fn test_link_constants() {
        assert_eq!(LINK_ECPUBSIZE, 64);
        assert_eq!(LINK_MTU_SIZE, 3);
        // LINK_MDU = floor((500 - 1 - 19 - 48) / 16) * 16 - 1 = floor(432/16)*16 - 1 = 27*16 - 1 = 431
        assert_eq!(LINK_MDU, 431);
        assert_eq!(CHANNEL_ENVELOPE_OVERHEAD, 6);
        assert_eq!(STREAM_DATA_OVERHEAD, 8);
        assert_eq!(STREAM_ID_MAX, 0x3FFF);
    }

    #[test]
    fn test_transport_constants() {
        // PATHFINDER_E = 7 days in seconds
        assert_eq!(PATHFINDER_E, 60.0 * 60.0 * 24.0 * 7.0);

        // AP_PATH_TIME = 1 day
        assert_eq!(AP_PATH_TIME, 60.0 * 60.0 * 24.0);

        // ROAMING_PATH_TIME = 6 hours
        assert_eq!(ROAMING_PATH_TIME, 60.0 * 60.0 * 6.0);

        assert_eq!(MODE_INTERNAL, 0x07);
        assert!(DISCOVER_PATHS_FOR.contains(&MODE_INTERNAL));

        // LINK_STALE_TIME = 2 * 360
        assert_eq!(LINK_STALE_TIME, 720.0);

        // LINK_TIMEOUT = STALE_TIME * 1.25
        assert_eq!(LINK_TIMEOUT, LINK_STALE_TIME * 1.25);

        // REVERSE_TIMEOUT = 8 minutes
        assert_eq!(REVERSE_TIMEOUT, 8.0 * 60.0);

        assert_eq!(PATH_REQUEST_GATE_TIMEOUT, 45.0);
        assert_eq!(DISCOVERY_PR_TX_THROTTLE, 0.5);
        assert_eq!(MAX_QUEUED_DISCOVERY_PRS, 32);
        assert_eq!(MAX_PR_TAGS, 16_000);

        // DESTINATION_TIMEOUT = 7 days
        assert_eq!(DESTINATION_TIMEOUT, 60.0 * 60.0 * 24.0 * 7.0);
    }

    #[test]
    fn test_resource_constants() {
        // SDU = MDU = 464 (NOT ENCRYPTED_MDU)
        assert_eq!(RESOURCE_SDU, 464);
        assert_eq!(RESOURCE_SDU, MDU);

        // FAST_RATE_THRESHOLD = WINDOW_MAX_SLOW - WINDOW - 2 = 10 - 4 - 2 = 4
        assert_eq!(RESOURCE_FAST_RATE_THRESHOLD, 4);

        // RATE_FAST = 50000 / 8 = 6250.0
        assert_eq!(RESOURCE_RATE_FAST, 6250.0);

        // RATE_VERY_SLOW = 2000 / 8 = 250.0
        assert_eq!(RESOURCE_RATE_VERY_SLOW, 250.0);

        // HASHMAP_MAX_LEN = floor((431 - 134) / 4) = floor(297/4) = 74
        assert_eq!(RESOURCE_HASHMAP_MAX_LEN, 74);

        // COLLISION_GUARD_SIZE = 2 * 75 + 74 = 224
        assert_eq!(RESOURCE_COLLISION_GUARD_SIZE, 224);

        // Window constants
        assert_eq!(RESOURCE_WINDOW, 4);
        assert_eq!(RESOURCE_WINDOW_MIN, 2);
        assert_eq!(RESOURCE_WINDOW_MAX_SLOW, 10);
        assert_eq!(RESOURCE_WINDOW_MAX_VERY_SLOW, 4);
        assert_eq!(RESOURCE_WINDOW_MAX_FAST, 75);
        assert_eq!(RESOURCE_WINDOW_MAX, 75);
        assert_eq!(RESOURCE_WINDOW_FLEXIBILITY, 4);
    }

    #[test]
    fn stamp_defaults_match_upstream_parameters() {
        assert_eq!(STAMP_LENGTH, 14);
        assert_eq!(EXT_HEADER_LENGTH, 2);
        assert_eq!(EXT_PAYLOAD_LENGTH, 18);
        assert_eq!(PR_EXT_PAYLOAD_LENGTH, 16);
        assert_eq!(PATH_REQUEST_STAMPED_LENGTH, 64);
        assert_eq!(EXT_PAYLOAD_MARKER, 0xC1);
        assert_eq!(DESTINATION_STAMP_COST, StampCost::from_f64(23.5));
        assert_eq!(DESTINATION_STAMP_VALUE, StampCost::from_f64(25.0));
        assert_eq!(PATH_REQUEST_STAMP_COST, StampCost::from_f64(15.8));
        assert_eq!(PATH_REQUEST_STAMP_VALUE, StampCost::from_f64(15.8));
        assert_eq!(LINK_REQUEST_STAMP_COST, StampCost::from_f64(12.5));
        assert_eq!(BLACKHOLE_LINK_COST, StampCost::from_f64(5.25));
        assert_eq!(LINK_COST_CAP, 40);
        assert_eq!(LINK_STAMP_TIMEOUT, 15);
        assert_eq!(PATH_REQUEST_STAMP_TIMEOUT, 15);
        assert_eq!(HOP_LIMITER, PATHFINDER_M);
        // Generation, emission and enforcement stay opt-in in 1.6.0, except
        // that link requests are stamped when the destination announced a
        // cost (a8cc6cb2 turned STAMP_LRS back on).
        let opt_in = [
            STAMPS_ENFORCED,
            LR_STAMPS_ENFORCED,
            STAMP_DESTINATIONS,
            STAMP_PRS,
            ALWAYS_STAMP_LRS,
        ];
        assert_eq!((opt_in, STAMP_LRS), ([false; 5], true));
    }
}