⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/net/routing/
ospf.rs

1//! OSPF routing daemon (RFC 2328)
2//!
3//! Link-state routing protocol implementation (single area, Area 0):
4//! - Hello protocol for neighbor discovery
5//! - Database description exchange
6//! - Link-state database with LSA flooding
7//! - SPF (Dijkstra) shortest path calculation
8//! - DR/BDR election
9
10use alloc::{collections::BTreeMap, vec::Vec};
11
12use crate::net::Ipv4Address;
13
14/// OSPF protocol number (IP protocol 89)
15pub const OSPF_PROTOCOL: u8 = 89;
16
17/// OSPF version 2
18pub const OSPF_VERSION: u8 = 2;
19
20/// Default Hello interval in ticks
21pub const HELLO_INTERVAL: u32 = 10;
22
23/// Default dead interval in ticks (4x hello)
24pub const DEAD_INTERVAL: u32 = 40;
25
26/// Maximum LSA age in ticks
27pub const MAX_LSA_AGE: u16 = 3600;
28
29/// OSPF all-routers multicast (224.0.0.5)
30pub const OSPF_ALL_ROUTERS: Ipv4Address = Ipv4Address([224, 0, 0, 5]);
31
32/// OSPF all-DR multicast (224.0.0.6)
33pub const OSPF_ALL_DR: Ipv4Address = Ipv4Address([224, 0, 0, 6]);
34
35/// Cost representing infinity (unreachable)
36pub const OSPF_INFINITY: u32 = 0xFFFF_FFFF;
37
38/// OSPF packet types
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40#[repr(u8)]
41pub enum OspfPacketType {
42    Hello = 1,
43    DatabaseDescription = 2,
44    LinkStateRequest = 3,
45    LinkStateUpdate = 4,
46    LinkStateAck = 5,
47}
48
49impl OspfPacketType {
50    fn from_u8(v: u8) -> Option<Self> {
51        match v {
52            1 => Some(Self::Hello),
53            2 => Some(Self::DatabaseDescription),
54            3 => Some(Self::LinkStateRequest),
55            4 => Some(Self::LinkStateUpdate),
56            5 => Some(Self::LinkStateAck),
57            _ => None,
58        }
59    }
60}
61
62/// OSPF authentication type
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64#[repr(u16)]
65pub enum AuthType {
66    None = 0,
67    SimplePassword = 1,
68    CryptographicMd5 = 2,
69}
70
71/// OSPF packet header (24 bytes)
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub struct OspfHeader {
74    /// OSPF version (2)
75    pub version: u8,
76    /// Packet type
77    pub packet_type: OspfPacketType,
78    /// Packet length including header
79    pub packet_length: u16,
80    /// Router ID of the originating router
81    pub router_id: u32,
82    /// Area ID this packet belongs to
83    pub area_id: u32,
84    /// Checksum (IP-style)
85    pub checksum: u16,
86    /// Authentication type
87    pub auth_type: AuthType,
88}
89
90impl OspfHeader {
91    /// Create a new OSPF header
92    pub fn new(packet_type: OspfPacketType, router_id: u32, area_id: u32) -> Self {
93        Self {
94            version: OSPF_VERSION,
95            packet_type,
96            packet_length: 0, // filled during serialization
97            router_id,
98            area_id,
99            checksum: 0,
100            auth_type: AuthType::None,
101        }
102    }
103}
104
105/// OSPF Hello packet
106#[derive(Debug, Clone, PartialEq, Eq)]
107pub struct HelloPacket {
108    /// Network mask of the interface
109    pub network_mask: u32,
110    /// Hello interval in ticks
111    pub hello_interval: u32,
112    /// Router dead interval in ticks
113    pub dead_interval: u32,
114    /// Router priority for DR election
115    pub priority: u8,
116    /// Options field
117    pub options: u8,
118    /// Designated Router ID
119    pub designated_router: u32,
120    /// Backup Designated Router ID
121    pub backup_dr: u32,
122    /// List of neighbor router IDs seen on this interface
123    pub neighbors: Vec<u32>,
124}
125
126impl HelloPacket {
127    /// Create a new Hello packet with default intervals
128    pub fn new(network_mask: u32, priority: u8) -> Self {
129        Self {
130            network_mask,
131            hello_interval: HELLO_INTERVAL,
132            dead_interval: DEAD_INTERVAL,
133            priority,
134            options: 0x02, // E-bit (external routing capability)
135            designated_router: 0,
136            backup_dr: 0,
137            neighbors: Vec::new(),
138        }
139    }
140}
141
142/// OSPF neighbor states (RFC 2328 Section 10.1)
143#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
144pub enum NeighborState {
145    /// No information received from neighbor
146    #[default]
147    Down,
148    /// Hello received, but bidirectionality not established
149    Init,
150    /// Bidirectional communication established
151    TwoWay,
152    /// Master/slave negotiation
153    ExStart,
154    /// Database description exchange
155    Exchange,
156    /// Link state requests being sent
157    Loading,
158    /// Fully adjacent
159    Full,
160}
161
162impl NeighborState {
163    /// Transition on receiving a Hello packet
164    pub fn on_hello_received(self) -> Self {
165        match self {
166            Self::Down => Self::Init,
167            other => other,
168        }
169    }
170
171    /// Transition when bidirectionality is confirmed (our router_id seen in
172    /// neighbor's Hello)
173    pub fn on_two_way_received(self) -> Self {
174        match self {
175            Self::Init => Self::TwoWay,
176            other => other,
177        }
178    }
179
180    /// Transition to begin adjacency formation (DR/BDR or point-to-point)
181    pub fn on_adjacency_ok(self) -> Self {
182        match self {
183            Self::TwoWay => Self::ExStart,
184            other => other,
185        }
186    }
187
188    /// Transition when negotiation completes
189    pub fn on_negotiation_done(self) -> Self {
190        match self {
191            Self::ExStart => Self::Exchange,
192            other => other,
193        }
194    }
195
196    /// Transition when exchange completes
197    pub fn on_exchange_done(self) -> Self {
198        match self {
199            Self::Exchange => Self::Loading,
200            other => other,
201        }
202    }
203
204    /// Transition when loading completes
205    pub fn on_loading_done(self) -> Self {
206        match self {
207            Self::Loading => Self::Full,
208            other => other,
209        }
210    }
211
212    /// Transition on inactivity timeout
213    pub fn on_kill(self) -> Self {
214        Self::Down
215    }
216
217    /// Whether this state represents an established adjacency
218    pub fn is_adjacent(&self) -> bool {
219        matches!(self, Self::Full)
220    }
221}
222
223/// OSPF neighbor entry
224#[derive(Debug, Clone, PartialEq, Eq)]
225pub struct OspfNeighbor {
226    /// Neighbor's router ID
227    pub router_id: u32,
228    /// Current neighbor state
229    pub state: NeighborState,
230    /// Neighbor's IP address
231    pub address: Ipv4Address,
232    /// Neighbor's priority for DR election
233    pub priority: u8,
234    /// Tick when last Hello was received
235    pub last_hello: u64,
236    /// Neighbor's Designated Router value
237    pub designated_router: u32,
238    /// Neighbor's Backup DR value
239    pub backup_dr: u32,
240}
241
242/// LSA types
243#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
244#[repr(u8)]
245pub enum LsaType {
246    /// Router LSA (type 1)
247    RouterLsa = 1,
248    /// Network LSA (type 2)
249    NetworkLsa = 2,
250}
251
252impl LsaType {
253    #[allow(dead_code)]
254    fn from_u8(v: u8) -> Option<Self> {
255        match v {
256            1 => Some(Self::RouterLsa),
257            2 => Some(Self::NetworkLsa),
258            _ => None,
259        }
260    }
261}
262
263/// LSA header (20 bytes)
264#[derive(Debug, Clone, Copy, PartialEq, Eq)]
265pub struct LsaHeader {
266    /// Age of the LSA in ticks
267    pub ls_age: u16,
268    /// Options field
269    pub options: u8,
270    /// LSA type
271    pub ls_type: LsaType,
272    /// Link State ID
273    pub link_state_id: u32,
274    /// Advertising router ID
275    pub advertising_router: u32,
276    /// Sequence number (for versioning)
277    pub seq_number: u32,
278    /// Checksum
279    pub checksum: u16,
280    /// Total length of LSA including header
281    pub length: u16,
282}
283
284impl LsaHeader {
285    /// Create a new LSA header
286    pub fn new(ls_type: LsaType, link_state_id: u32, advertising_router: u32) -> Self {
287        Self {
288            ls_age: 0,
289            options: 0x02,
290            ls_type,
291            link_state_id,
292            advertising_router,
293            seq_number: 1,
294            checksum: 0,
295            length: 0,
296        }
297    }
298
299    /// Check if this LSA is newer than another
300    pub fn is_newer_than(&self, other: &Self) -> bool {
301        self.seq_number > other.seq_number
302    }
303}
304
305/// Router LSA link types
306#[derive(Debug, Clone, Copy, PartialEq, Eq)]
307#[repr(u8)]
308pub enum RouterLinkType {
309    /// Point-to-point connection to another router
310    PointToPoint = 1,
311    /// Connection to a transit network
312    Transit = 2,
313    /// Connection to a stub network
314    Stub = 3,
315    /// Virtual link
316    Virtual = 4,
317}
318
319/// A single link in a Router LSA
320#[derive(Debug, Clone, Copy, PartialEq, Eq)]
321pub struct RouterLink {
322    /// Link type
323    pub link_type: RouterLinkType,
324    /// Link ID (interpretation depends on link_type)
325    pub link_id: u32,
326    /// Link data (interpretation depends on link_type)
327    pub link_data: u32,
328    /// Cost metric
329    pub metric: u32,
330}
331
332/// Router LSA (type 1)
333#[derive(Debug, Clone, PartialEq, Eq)]
334pub struct RouterLsa {
335    /// LSA header
336    pub header: LsaHeader,
337    /// Router flags (V=virtual link, E=ASBR, B=ABR)
338    pub flags: u8,
339    /// List of router links
340    pub links: Vec<RouterLink>,
341}
342
343/// Network LSA (type 2)
344#[derive(Debug, Clone, PartialEq, Eq)]
345pub struct NetworkLsa {
346    /// LSA header
347    pub header: LsaHeader,
348    /// Network mask
349    pub network_mask: u32,
350    /// List of attached router IDs
351    pub attached_routers: Vec<u32>,
352}
353
354/// Stored LSA in the database
355#[derive(Debug, Clone, PartialEq, Eq)]
356pub enum Lsa {
357    Router(RouterLsa),
358    Network(NetworkLsa),
359}
360
361impl Lsa {
362    /// Get the LSA header
363    pub fn header(&self) -> &LsaHeader {
364        match self {
365            Lsa::Router(r) => &r.header,
366            Lsa::Network(n) => &n.header,
367        }
368    }
369
370    /// Get the LSA header mutably
371    pub fn header_mut(&mut self) -> &mut LsaHeader {
372        match self {
373            Lsa::Router(r) => &mut r.header,
374            Lsa::Network(n) => &mut n.header,
375        }
376    }
377}
378
379/// Key for LSA database lookup: (type, link_state_id, advertising_router)
380#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
381pub struct LsaKey {
382    pub ls_type: u8,
383    pub link_state_id: u32,
384    pub advertising_router: u32,
385}
386
387/// Link-State Database
388#[derive(Debug, Default)]
389pub struct LsDatabase {
390    /// LSAs indexed by (type, id, router)
391    lsas: BTreeMap<LsaKey, Lsa>,
392}
393
394impl LsDatabase {
395    /// Create a new empty database
396    pub fn new() -> Self {
397        Self {
398            lsas: BTreeMap::new(),
399        }
400    }
401
402    /// Insert or update an LSA. Returns true if the LSA was newer and was
403    /// installed.
404    pub fn install(&mut self, lsa: Lsa) -> bool {
405        let key = LsaKey {
406            ls_type: match lsa.header().ls_type {
407                LsaType::RouterLsa => 1,
408                LsaType::NetworkLsa => 2,
409            },
410            link_state_id: lsa.header().link_state_id,
411            advertising_router: lsa.header().advertising_router,
412        };
413
414        if let Some(existing) = self.lsas.get(&key) {
415            if !lsa.header().is_newer_than(existing.header()) {
416                return false;
417            }
418        }
419
420        self.lsas.insert(key, lsa);
421        true
422    }
423
424    /// Lookup an LSA
425    pub fn lookup(&self, ls_type: LsaType, link_state_id: u32, router: u32) -> Option<&Lsa> {
426        let key = LsaKey {
427            ls_type: ls_type as u8,
428            link_state_id,
429            advertising_router: router,
430        };
431        self.lsas.get(&key)
432    }
433
434    /// Get all LSAs
435    pub fn all_lsas(&self) -> Vec<&Lsa> {
436        self.lsas.values().collect()
437    }
438
439    /// Get all Router LSAs
440    pub fn router_lsas(&self) -> Vec<&RouterLsa> {
441        self.lsas
442            .values()
443            .filter_map(|lsa| match lsa {
444                Lsa::Router(r) => Some(r),
445                _ => None,
446            })
447            .collect()
448    }
449
450    /// Number of LSAs in the database
451    pub fn len(&self) -> usize {
452        self.lsas.len()
453    }
454
455    /// Whether the database is empty
456    pub fn is_empty(&self) -> bool {
457        self.lsas.is_empty()
458    }
459
460    /// Age all LSAs by the given number of ticks. Remove LSAs that exceed
461    /// MAX_LSA_AGE.
462    pub fn age_lsas(&mut self, ticks: u16) {
463        let mut to_remove = Vec::new();
464
465        for (key, lsa) in &mut self.lsas {
466            let header = lsa.header_mut();
467            header.ls_age = header.ls_age.saturating_add(ticks);
468            if header.ls_age >= MAX_LSA_AGE {
469                to_remove.push(*key);
470            }
471        }
472
473        for key in &to_remove {
474            self.lsas.remove(key);
475        }
476    }
477}
478
479/// Node in the SPF calculation
480#[derive(Debug, Clone, PartialEq, Eq)]
481struct SpfNode {
482    /// Router ID
483    router_id: u32,
484    /// Accumulated cost from root
485    cost: u32,
486    /// Next hop router for this destination (0 = directly connected)
487    next_hop: u32,
488}
489
490impl SpfNode {
491    fn new(router_id: u32, cost: u32, next_hop: u32) -> Self {
492        Self {
493            router_id,
494            cost,
495            next_hop,
496        }
497    }
498}
499
500/// SPF result entry
501#[derive(Debug, Clone, PartialEq, Eq)]
502pub struct SpfEntry {
503    /// Destination router ID
504    pub router_id: u32,
505    /// Total cost
506    pub cost: u32,
507    /// Next hop router ID (0 = directly connected)
508    pub next_hop: u32,
509}
510
511/// OSPF Router
512pub struct OspfRouter {
513    /// This router's ID
514    pub router_id: u32,
515    /// Area ID (0 for backbone)
516    pub area_id: u32,
517    /// Neighbor table
518    neighbors: BTreeMap<u32, OspfNeighbor>,
519    /// Link-state database
520    lsdb: LsDatabase,
521    /// Current tick
522    current_tick: u64,
523    /// Router priority for DR election
524    priority: u8,
525    /// Interface network mask
526    network_mask: u32,
527}
528
529impl OspfRouter {
530    /// Create a new OSPF router
531    pub fn new(router_id: u32, area_id: u32, priority: u8, network_mask: u32) -> Self {
532        Self {
533            router_id,
534            area_id,
535            neighbors: BTreeMap::new(),
536            lsdb: LsDatabase::new(),
537            current_tick: 0,
538            priority,
539            network_mask,
540        }
541    }
542
543    /// Advance the tick counter
544    pub fn tick(&mut self, ticks: u64) {
545        self.current_tick += ticks;
546    }
547
548    /// Get the link-state database
549    pub fn lsdb(&self) -> &LsDatabase {
550        &self.lsdb
551    }
552
553    /// Get the link-state database mutably
554    pub fn lsdb_mut(&mut self) -> &mut LsDatabase {
555        &mut self.lsdb
556    }
557
558    /// Get number of neighbors
559    pub fn neighbor_count(&self) -> usize {
560        self.neighbors.len()
561    }
562
563    /// Get a neighbor by router ID
564    pub fn get_neighbor(&self, router_id: u32) -> Option<&OspfNeighbor> {
565        self.neighbors.get(&router_id)
566    }
567
568    /// Get all neighbors
569    pub fn neighbors(&self) -> Vec<&OspfNeighbor> {
570        self.neighbors.values().collect()
571    }
572
573    /// Process an incoming Hello packet from a neighbor
574    pub fn process_hello(&mut self, header: &OspfHeader, hello: &HelloPacket, source: Ipv4Address) {
575        let neighbor_id = header.router_id;
576
577        // Check for compatible parameters
578        if hello.hello_interval != HELLO_INTERVAL || hello.dead_interval != DEAD_INTERVAL {
579            return; // Parameter mismatch, ignore
580        }
581
582        if let Some(neighbor) = self.neighbors.get_mut(&neighbor_id) {
583            // Existing neighbor: update and transition
584            neighbor.last_hello = self.current_tick;
585            neighbor.designated_router = hello.designated_router;
586            neighbor.backup_dr = hello.backup_dr;
587
588            // Check if our router_id appears in the neighbor's list (bidirectional)
589            if hello.neighbors.contains(&self.router_id) {
590                neighbor.state = neighbor.state.on_two_way_received();
591            } else {
592                neighbor.state = neighbor.state.on_hello_received();
593            }
594        } else {
595            // New neighbor discovered
596            let mut state = NeighborState::Down;
597            state = state.on_hello_received();
598
599            if hello.neighbors.contains(&self.router_id) {
600                state = state.on_two_way_received();
601            }
602
603            let neighbor = OspfNeighbor {
604                router_id: neighbor_id,
605                state,
606                address: source,
607                priority: hello.priority,
608                last_hello: self.current_tick,
609                designated_router: hello.designated_router,
610                backup_dr: hello.backup_dr,
611            };
612            self.neighbors.insert(neighbor_id, neighbor);
613        }
614    }
615
616    /// Process a Database Description packet (simplified: just advance state)
617    pub fn process_dbd(&mut self, header: &OspfHeader, _lsa_headers: &[LsaHeader]) {
618        let neighbor_id = header.router_id;
619
620        if let Some(neighbor) = self.neighbors.get_mut(&neighbor_id) {
621            match neighbor.state {
622                NeighborState::ExStart => {
623                    neighbor.state = neighbor.state.on_negotiation_done();
624                }
625                NeighborState::Exchange => {
626                    neighbor.state = neighbor.state.on_exchange_done();
627                }
628                NeighborState::Loading => {
629                    neighbor.state = neighbor.state.on_loading_done();
630                }
631                _ => {}
632            }
633        }
634    }
635
636    /// Install an LSA into the database and return whether SPF recalculation is
637    /// needed
638    pub fn install_lsa(&mut self, lsa: Lsa) -> bool {
639        self.lsdb.install(lsa)
640    }
641
642    /// Check for dead neighbors and remove them
643    pub fn check_dead_neighbors(&mut self) {
644        let dead_interval = u64::from(DEAD_INTERVAL);
645        let current = self.current_tick;
646        let mut dead_ids = Vec::new();
647
648        for (id, neighbor) in &self.neighbors {
649            if current.saturating_sub(neighbor.last_hello) >= dead_interval {
650                dead_ids.push(*id);
651            }
652        }
653
654        for id in &dead_ids {
655            self.neighbors.remove(id);
656        }
657    }
658
659    /// Generate a Hello packet for this interface
660    pub fn generate_hello(&self) -> HelloPacket {
661        let mut hello = HelloPacket::new(self.network_mask, self.priority);
662
663        // Elect DR/BDR from current neighbor knowledge
664        let (dr, bdr) = self.elect_dr_bdr();
665        hello.designated_router = dr;
666        hello.backup_dr = bdr;
667
668        // Include all known neighbor router IDs
669        for neighbor_id in self.neighbors.keys() {
670            hello.neighbors.push(*neighbor_id);
671        }
672
673        hello
674    }
675
676    /// Simple DR/BDR election based on priority and router ID
677    fn elect_dr_bdr(&self) -> (u32, u32) {
678        // Collect candidates: (priority, router_id) for all neighbors + self
679        // Only routers with priority > 0 are eligible
680        let mut candidates: Vec<(u8, u32)> = Vec::new();
681
682        if self.priority > 0 {
683            candidates.push((self.priority, self.router_id));
684        }
685
686        for neighbor in self.neighbors.values() {
687            if neighbor.priority > 0
688                && (neighbor.state == NeighborState::TwoWay
689                    || neighbor.state == NeighborState::Full)
690            {
691                candidates.push((neighbor.priority, neighbor.router_id));
692            }
693        }
694
695        if candidates.is_empty() {
696            return (0, 0);
697        }
698
699        // Sort by priority (descending), then router_id (descending) as tiebreaker
700        candidates.sort_by(|a, b| b.0.cmp(&a.0).then(b.1.cmp(&a.1)));
701
702        let dr = candidates[0].1;
703        let bdr = if candidates.len() > 1 {
704            candidates[1].1
705        } else {
706            0
707        };
708
709        (dr, bdr)
710    }
711
712    /// Run SPF (Dijkstra) algorithm on the LSDB and return routing table
713    /// entries
714    pub fn run_spf(&self) -> Vec<SpfEntry> {
715        let mut result = Vec::new();
716        let mut visited: BTreeMap<u32, SpfEntry> = BTreeMap::new();
717
718        // Priority queue: Vec-based min-heap (cost, node)
719        // We use a simple Vec and find minimum each iteration for correctness
720        let mut candidates: Vec<SpfNode> = Vec::new();
721
722        // Start with self at cost 0
723        candidates.push(SpfNode::new(self.router_id, 0, 0));
724
725        while !candidates.is_empty() {
726            // Find the candidate with minimum cost
727            let min_idx = find_min_cost(&candidates);
728            let current = candidates.swap_remove(min_idx);
729
730            // Skip if already visited
731            if visited.contains_key(&current.router_id) {
732                continue;
733            }
734
735            // Mark visited
736            let entry = SpfEntry {
737                router_id: current.router_id,
738                cost: current.cost,
739                next_hop: current.next_hop,
740            };
741            visited.insert(current.router_id, entry.clone());
742
743            if current.router_id != self.router_id {
744                result.push(entry);
745            }
746
747            // Find the Router LSA for this node and explore its links
748            let router_lsas = self.lsdb.router_lsas();
749            for rlsa in &router_lsas {
750                if rlsa.header.advertising_router != current.router_id {
751                    continue;
752                }
753
754                for link in &rlsa.links {
755                    match link.link_type {
756                        RouterLinkType::PointToPoint | RouterLinkType::Transit => {
757                            let neighbor_id = link.link_id;
758                            if visited.contains_key(&neighbor_id) {
759                                continue;
760                            }
761
762                            let new_cost = current.cost.saturating_add(link.metric);
763
764                            // Determine next_hop: if current is root, next_hop is the neighbor
765                            let next_hop = if current.router_id == self.router_id {
766                                neighbor_id
767                            } else {
768                                current.next_hop
769                            };
770
771                            candidates.push(SpfNode::new(neighbor_id, new_cost, next_hop));
772                        }
773                        RouterLinkType::Stub | RouterLinkType::Virtual => {
774                            // Stub networks don't participate in SPF graph
775                            // traversal
776                            // Virtual links not supported in single-area
777                        }
778                    }
779                }
780            }
781        }
782
783        result
784    }
785}
786
787/// Find index of minimum cost node in the candidate list
788fn find_min_cost(candidates: &[SpfNode]) -> usize {
789    let mut min_idx = 0;
790    let mut min_cost = candidates[0].cost;
791
792    for (i, node) in candidates.iter().enumerate().skip(1) {
793        if node.cost < min_cost {
794            min_cost = node.cost;
795            min_idx = i;
796        }
797    }
798
799    min_idx
800}
801
802/// Serialize an OSPF header to bytes
803pub fn serialize_header(header: &OspfHeader) -> Vec<u8> {
804    let mut buf = Vec::with_capacity(24);
805    buf.push(header.version);
806    buf.push(header.packet_type as u8);
807    buf.extend_from_slice(&header.packet_length.to_be_bytes());
808    buf.extend_from_slice(&header.router_id.to_be_bytes());
809    buf.extend_from_slice(&header.area_id.to_be_bytes());
810    buf.extend_from_slice(&header.checksum.to_be_bytes());
811    buf.extend_from_slice(&(header.auth_type as u16).to_be_bytes());
812    // 8 bytes of auth data (zeroed for no auth)
813    buf.extend_from_slice(&[0u8; 8]);
814    buf
815}
816
817/// Deserialize an OSPF header from bytes
818pub fn deserialize_header(data: &[u8]) -> Option<OspfHeader> {
819    if data.len() < 24 {
820        return None;
821    }
822
823    let version = data[0];
824    if version != OSPF_VERSION {
825        return None;
826    }
827
828    let packet_type = OspfPacketType::from_u8(data[1])?;
829    let packet_length = u16::from_be_bytes([data[2], data[3]]);
830    let router_id = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
831    let area_id = u32::from_be_bytes([data[8], data[9], data[10], data[11]]);
832    let checksum = u16::from_be_bytes([data[12], data[13]]);
833    let auth_type_val = u16::from_be_bytes([data[14], data[15]]);
834
835    let auth_type = match auth_type_val {
836        0 => AuthType::None,
837        1 => AuthType::SimplePassword,
838        2 => AuthType::CryptographicMd5,
839        _ => return None,
840    };
841
842    Some(OspfHeader {
843        version,
844        packet_type,
845        packet_length,
846        router_id,
847        area_id,
848        checksum,
849        auth_type,
850    })
851}
852
853#[cfg(test)]
854mod tests {
855    use super::*;
856
857    #[test]
858    fn test_neighbor_state_transitions() {
859        let state = NeighborState::Down;
860        assert_eq!(state.on_hello_received(), NeighborState::Init);
861        assert_eq!(
862            state.on_hello_received().on_two_way_received(),
863            NeighborState::TwoWay
864        );
865        assert_eq!(
866            NeighborState::TwoWay.on_adjacency_ok(),
867            NeighborState::ExStart
868        );
869        assert_eq!(
870            NeighborState::ExStart.on_negotiation_done(),
871            NeighborState::Exchange
872        );
873        assert_eq!(
874            NeighborState::Exchange.on_exchange_done(),
875            NeighborState::Loading
876        );
877        assert_eq!(
878            NeighborState::Loading.on_loading_done(),
879            NeighborState::Full
880        );
881    }
882
883    #[test]
884    fn test_neighbor_state_kill() {
885        assert_eq!(NeighborState::Full.on_kill(), NeighborState::Down);
886        assert_eq!(NeighborState::Exchange.on_kill(), NeighborState::Down);
887    }
888
889    #[test]
890    fn test_neighbor_state_is_adjacent() {
891        assert!(!NeighborState::Down.is_adjacent());
892        assert!(!NeighborState::Init.is_adjacent());
893        assert!(!NeighborState::TwoWay.is_adjacent());
894        assert!(NeighborState::Full.is_adjacent());
895    }
896
897    #[test]
898    fn test_lsa_header_newer() {
899        let h1 = LsaHeader {
900            ls_age: 0,
901            options: 0,
902            ls_type: LsaType::RouterLsa,
903            link_state_id: 1,
904            advertising_router: 1,
905            seq_number: 5,
906            checksum: 0,
907            length: 0,
908        };
909
910        let h2 = LsaHeader {
911            seq_number: 10,
912            ..h1
913        };
914
915        assert!(h2.is_newer_than(&h1));
916        assert!(!h1.is_newer_than(&h2));
917        assert!(!h1.is_newer_than(&h1));
918    }
919
920    #[test]
921    fn test_lsdb_install_and_lookup() {
922        let mut db = LsDatabase::new();
923
924        let lsa = Lsa::Router(RouterLsa {
925            header: LsaHeader::new(LsaType::RouterLsa, 1, 1),
926            flags: 0,
927            links: Vec::new(),
928        });
929
930        assert!(db.install(lsa));
931        assert_eq!(db.len(), 1);
932        assert!(db.lookup(LsaType::RouterLsa, 1, 1).is_some());
933        assert!(db.lookup(LsaType::RouterLsa, 2, 1).is_none());
934    }
935
936    #[test]
937    fn test_lsdb_reject_older() {
938        let mut db = LsDatabase::new();
939
940        let mut header = LsaHeader::new(LsaType::RouterLsa, 1, 1);
941        header.seq_number = 10;
942
943        let lsa = Lsa::Router(RouterLsa {
944            header,
945            flags: 0,
946            links: Vec::new(),
947        });
948        assert!(db.install(lsa));
949
950        // Try to install older version
951        let mut older_header = LsaHeader::new(LsaType::RouterLsa, 1, 1);
952        older_header.seq_number = 5;
953
954        let older_lsa = Lsa::Router(RouterLsa {
955            header: older_header,
956            flags: 0,
957            links: Vec::new(),
958        });
959        assert!(!db.install(older_lsa));
960        assert_eq!(db.len(), 1);
961    }
962
963    #[test]
964    fn test_lsdb_age() {
965        let mut db = LsDatabase::new();
966
967        let lsa = Lsa::Router(RouterLsa {
968            header: LsaHeader::new(LsaType::RouterLsa, 1, 1),
969            flags: 0,
970            links: Vec::new(),
971        });
972        db.install(lsa);
973
974        db.age_lsas(MAX_LSA_AGE);
975        assert!(db.is_empty());
976    }
977
978    #[test]
979    fn test_ospf_header_serialize_deserialize() {
980        let header = OspfHeader::new(OspfPacketType::Hello, 0x01020304, 0);
981        let bytes = serialize_header(&header);
982        assert_eq!(bytes.len(), 24);
983
984        let decoded = deserialize_header(&bytes).unwrap();
985        assert_eq!(decoded.version, OSPF_VERSION);
986        assert_eq!(decoded.packet_type, OspfPacketType::Hello);
987        assert_eq!(decoded.router_id, 0x01020304);
988        assert_eq!(decoded.area_id, 0);
989        assert_eq!(decoded.auth_type, AuthType::None);
990    }
991
992    #[test]
993    fn test_hello_generation() {
994        let router = OspfRouter::new(1, 0, 1, 0xFFFFFF00);
995        let hello = router.generate_hello();
996
997        assert_eq!(hello.network_mask, 0xFFFFFF00);
998        assert_eq!(hello.hello_interval, HELLO_INTERVAL);
999        assert_eq!(hello.dead_interval, DEAD_INTERVAL);
1000        assert!(hello.neighbors.is_empty());
1001    }
1002
1003    #[test]
1004    fn test_process_hello_new_neighbor() {
1005        let mut router = OspfRouter::new(1, 0, 1, 0xFFFFFF00);
1006
1007        let header = OspfHeader::new(OspfPacketType::Hello, 2, 0);
1008        let mut hello = HelloPacket::new(0xFFFFFF00, 1);
1009        hello.neighbors.push(1); // Neighbor sees us
1010
1011        router.process_hello(&header, &hello, Ipv4Address::new(10, 0, 0, 2));
1012        assert_eq!(router.neighbor_count(), 1);
1013
1014        let neighbor = router.get_neighbor(2).unwrap();
1015        assert_eq!(neighbor.state, NeighborState::TwoWay);
1016    }
1017
1018    #[test]
1019    fn test_process_hello_one_way() {
1020        let mut router = OspfRouter::new(1, 0, 1, 0xFFFFFF00);
1021
1022        let header = OspfHeader::new(OspfPacketType::Hello, 2, 0);
1023        let hello = HelloPacket::new(0xFFFFFF00, 1);
1024        // Neighbor does NOT list our router_id
1025
1026        router.process_hello(&header, &hello, Ipv4Address::new(10, 0, 0, 2));
1027        let neighbor = router.get_neighbor(2).unwrap();
1028        assert_eq!(neighbor.state, NeighborState::Init);
1029    }
1030
1031    #[test]
1032    fn test_dead_neighbor_removal() {
1033        let mut router = OspfRouter::new(1, 0, 1, 0xFFFFFF00);
1034
1035        let header = OspfHeader::new(OspfPacketType::Hello, 2, 0);
1036        let hello = HelloPacket::new(0xFFFFFF00, 1);
1037        router.process_hello(&header, &hello, Ipv4Address::new(10, 0, 0, 2));
1038        assert_eq!(router.neighbor_count(), 1);
1039
1040        router.tick(u64::from(DEAD_INTERVAL));
1041        router.check_dead_neighbors();
1042        assert_eq!(router.neighbor_count(), 0);
1043    }
1044
1045    #[test]
1046    fn test_dr_election() {
1047        let mut router = OspfRouter::new(1, 0, 1, 0xFFFFFF00);
1048
1049        // Add a neighbor with higher priority
1050        let header = OspfHeader::new(OspfPacketType::Hello, 2, 0);
1051        let mut hello = HelloPacket::new(0xFFFFFF00, 2); // priority 2
1052        hello.neighbors.push(1);
1053        router.process_hello(&header, &hello, Ipv4Address::new(10, 0, 0, 2));
1054
1055        let generated_hello = router.generate_hello();
1056        // Neighbor 2 has higher priority, should be DR
1057        assert_eq!(generated_hello.designated_router, 2);
1058        assert_eq!(generated_hello.backup_dr, 1);
1059    }
1060
1061    #[test]
1062    fn test_spf_simple_topology() {
1063        // Topology: R1 --10-- R2 --5-- R3
1064        let router = OspfRouter::new(1, 0, 1, 0xFFFFFF00);
1065
1066        // R1's Router LSA: link to R2 with cost 10
1067        let r1_lsa = Lsa::Router(RouterLsa {
1068            header: LsaHeader::new(LsaType::RouterLsa, 1, 1),
1069            flags: 0,
1070            links: alloc::vec![RouterLink {
1071                link_type: RouterLinkType::PointToPoint,
1072                link_id: 2,
1073                link_data: 0x0A000001,
1074                metric: 10,
1075            }],
1076        });
1077
1078        // R2's Router LSA: links to R1 (cost 10) and R3 (cost 5)
1079        let r2_lsa = Lsa::Router(RouterLsa {
1080            header: LsaHeader::new(LsaType::RouterLsa, 2, 2),
1081            flags: 0,
1082            links: alloc::vec![
1083                RouterLink {
1084                    link_type: RouterLinkType::PointToPoint,
1085                    link_id: 1,
1086                    link_data: 0x0A000002,
1087                    metric: 10,
1088                },
1089                RouterLink {
1090                    link_type: RouterLinkType::PointToPoint,
1091                    link_id: 3,
1092                    link_data: 0x0A000002,
1093                    metric: 5,
1094                },
1095            ],
1096        });
1097
1098        // R3's Router LSA: link to R2 with cost 5
1099        let r3_lsa = Lsa::Router(RouterLsa {
1100            header: LsaHeader::new(LsaType::RouterLsa, 3, 3),
1101            flags: 0,
1102            links: alloc::vec![RouterLink {
1103                link_type: RouterLinkType::PointToPoint,
1104                link_id: 2,
1105                link_data: 0x0A000003,
1106                metric: 5,
1107            }],
1108        });
1109
1110        // Build router with populated LSDB
1111        let mut test_router = OspfRouter::new(1, 0, 1, 0xFFFFFF00);
1112        test_router.install_lsa(r1_lsa);
1113        test_router.install_lsa(r2_lsa);
1114        test_router.install_lsa(r3_lsa);
1115
1116        let spf_result = test_router.run_spf();
1117
1118        // Should have entries for R2 and R3
1119        assert_eq!(spf_result.len(), 2);
1120
1121        let r2_entry = spf_result.iter().find(|e| e.router_id == 2).unwrap();
1122        assert_eq!(r2_entry.cost, 10);
1123        assert_eq!(r2_entry.next_hop, 2); // Direct neighbor
1124
1125        let r3_entry = spf_result.iter().find(|e| e.router_id == 3).unwrap();
1126        assert_eq!(r3_entry.cost, 15); // 10 + 5
1127        assert_eq!(r3_entry.next_hop, 2); // Via R2
1128    }
1129
1130    #[test]
1131    fn test_spf_shortest_path_preferred() {
1132        // Topology: R1 --1-- R2 --1-- R3
1133        //           R1 --10---------  R3
1134        // Shortest path to R3 should be via R2 (cost 2), not direct (cost 10)
1135        let mut router = OspfRouter::new(1, 0, 1, 0xFFFFFF00);
1136
1137        let r1_lsa = Lsa::Router(RouterLsa {
1138            header: LsaHeader::new(LsaType::RouterLsa, 1, 1),
1139            flags: 0,
1140            links: alloc::vec![
1141                RouterLink {
1142                    link_type: RouterLinkType::PointToPoint,
1143                    link_id: 2,
1144                    link_data: 0,
1145                    metric: 1,
1146                },
1147                RouterLink {
1148                    link_type: RouterLinkType::PointToPoint,
1149                    link_id: 3,
1150                    link_data: 0,
1151                    metric: 10,
1152                },
1153            ],
1154        });
1155
1156        let r2_lsa = Lsa::Router(RouterLsa {
1157            header: LsaHeader::new(LsaType::RouterLsa, 2, 2),
1158            flags: 0,
1159            links: alloc::vec![
1160                RouterLink {
1161                    link_type: RouterLinkType::PointToPoint,
1162                    link_id: 1,
1163                    link_data: 0,
1164                    metric: 1,
1165                },
1166                RouterLink {
1167                    link_type: RouterLinkType::PointToPoint,
1168                    link_id: 3,
1169                    link_data: 0,
1170                    metric: 1,
1171                },
1172            ],
1173        });
1174
1175        let r3_lsa = Lsa::Router(RouterLsa {
1176            header: LsaHeader::new(LsaType::RouterLsa, 3, 3),
1177            flags: 0,
1178            links: alloc::vec![
1179                RouterLink {
1180                    link_type: RouterLinkType::PointToPoint,
1181                    link_id: 2,
1182                    link_data: 0,
1183                    metric: 1,
1184                },
1185                RouterLink {
1186                    link_type: RouterLinkType::PointToPoint,
1187                    link_id: 1,
1188                    link_data: 0,
1189                    metric: 10,
1190                },
1191            ],
1192        });
1193
1194        router.install_lsa(r1_lsa);
1195        router.install_lsa(r2_lsa);
1196        router.install_lsa(r3_lsa);
1197
1198        let spf_result = router.run_spf();
1199        let r3_entry = spf_result.iter().find(|e| e.router_id == 3).unwrap();
1200        assert_eq!(r3_entry.cost, 2); // via R2: 1 + 1
1201        assert_eq!(r3_entry.next_hop, 2);
1202    }
1203}