pcapsql_core/protocol/
ospf.rs

1//! OSPF (Open Shortest Path First) protocol parser.
2//!
3//! OSPF is a link-state routing protocol for IP networks that uses
4//! a shortest path first (SPF) algorithm for finding the best path.
5//!
6//! RFC 2328: OSPF Version 2
7//! RFC 5340: OSPF for IPv6
8
9use compact_str::CompactString;
10use smallvec::SmallVec;
11
12use super::{FieldValue, ParseContext, ParseResult, Protocol};
13use crate::schema::{DataKind, FieldDescriptor};
14
15/// IP protocol number for OSPF.
16pub const IP_PROTOCOL_OSPF: u8 = 89;
17
18/// OSPF packet types.
19pub mod packet_type {
20    pub const HELLO: u8 = 1;
21    pub const DATABASE_DESCRIPTION: u8 = 2;
22    pub const LINK_STATE_REQUEST: u8 = 3;
23    pub const LINK_STATE_UPDATE: u8 = 4;
24    pub const LINK_STATE_ACK: u8 = 5;
25}
26
27/// OSPF LSA types (RFC 2328).
28pub mod lsa_type {
29    /// Router-LSA: Describes router's links within an area.
30    pub const ROUTER: u8 = 1;
31    /// Network-LSA: Describes transit network.
32    pub const NETWORK: u8 = 2;
33    /// Summary-LSA (IP network): Describes route to network.
34    pub const SUMMARY_NETWORK: u8 = 3;
35    /// Summary-LSA (ASBR): Describes route to ASBR.
36    pub const SUMMARY_ASBR: u8 = 4;
37    /// AS-External-LSA: Describes route to external network.
38    pub const AS_EXTERNAL: u8 = 5;
39}
40
41/// Get the name of an LSA type.
42fn lsa_type_name(ls_type: u8) -> &'static str {
43    match ls_type {
44        lsa_type::ROUTER => "Router-LSA",
45        lsa_type::NETWORK => "Network-LSA",
46        lsa_type::SUMMARY_NETWORK => "Summary-LSA-Network",
47        lsa_type::SUMMARY_ASBR => "Summary-LSA-ASBR",
48        lsa_type::AS_EXTERNAL => "AS-External-LSA",
49        _ => "Unknown",
50    }
51}
52
53/// Get the name of an OSPF packet type.
54fn packet_type_name(pkt_type: u8) -> &'static str {
55    match pkt_type {
56        packet_type::HELLO => "Hello",
57        packet_type::DATABASE_DESCRIPTION => "Database Description",
58        packet_type::LINK_STATE_REQUEST => "Link State Request",
59        packet_type::LINK_STATE_UPDATE => "Link State Update",
60        packet_type::LINK_STATE_ACK => "Link State Acknowledgment",
61        _ => "Unknown",
62    }
63}
64
65/// OSPF protocol parser.
66#[derive(Debug, Clone, Copy)]
67pub struct OspfProtocol;
68
69impl Protocol for OspfProtocol {
70    fn name(&self) -> &'static str {
71        "ospf"
72    }
73
74    fn display_name(&self) -> &'static str {
75        "OSPF"
76    }
77
78    fn can_parse(&self, context: &ParseContext) -> Option<u32> {
79        // Match when IP protocol hint equals 89
80        match context.hint("ip_protocol") {
81            Some(proto) if proto == IP_PROTOCOL_OSPF as u64 => Some(100),
82            _ => None,
83        }
84    }
85
86    fn parse<'a>(&self, data: &'a [u8], _context: &ParseContext) -> ParseResult<'a> {
87        // OSPF header is 24 bytes minimum
88        if data.len() < 24 {
89            return ParseResult::error("OSPF header too short".to_string(), data);
90        }
91
92        let mut fields = SmallVec::new();
93
94        // Byte 0: Version
95        let version = data[0];
96        fields.push(("version", FieldValue::UInt8(version)));
97
98        // Byte 1: Type
99        let msg_type = data[1];
100        fields.push(("message_type", FieldValue::UInt8(msg_type)));
101        fields.push((
102            "message_type_name",
103            FieldValue::Str(packet_type_name(msg_type)),
104        ));
105
106        // Bytes 2-3: Packet Length
107        let length = u16::from_be_bytes([data[2], data[3]]);
108        fields.push(("length", FieldValue::UInt16(length)));
109
110        // Bytes 4-7: Router ID
111        let router_id = format!("{}.{}.{}.{}", data[4], data[5], data[6], data[7]);
112        fields.push((
113            "router_id",
114            FieldValue::OwnedString(CompactString::new(router_id)),
115        ));
116
117        // Bytes 8-11: Area ID
118        let area_id = format!("{}.{}.{}.{}", data[8], data[9], data[10], data[11]);
119        fields.push((
120            "area_id",
121            FieldValue::OwnedString(CompactString::new(area_id)),
122        ));
123
124        // Bytes 12-13: Checksum
125        let checksum = u16::from_be_bytes([data[12], data[13]]);
126        fields.push(("checksum", FieldValue::UInt16(checksum)));
127
128        // Bytes 14-15: AuType (Authentication Type)
129        let auth_type = u16::from_be_bytes([data[14], data[15]]);
130        fields.push(("auth_type", FieldValue::UInt16(auth_type)));
131
132        // Bytes 16-23: Authentication (8 bytes)
133        // We skip detailed authentication parsing
134
135        // Parse packet-specific fields
136        // Note: length field comes from the packet and may be malicious/invalid
137        // We must ensure length >= 24 (header size) to avoid slice panic
138        let packet_data = if length as usize > 24 && data.len() >= length as usize {
139            &data[24..length as usize]
140        } else if data.len() > 24 {
141            &data[24..]
142        } else {
143            &[]
144        };
145
146        match (msg_type, version) {
147            (packet_type::HELLO, 2) => {
148                self.parse_hello_v2(packet_data, &mut fields);
149            }
150            (packet_type::DATABASE_DESCRIPTION, 2) => {
151                self.parse_db_description_v2(packet_data, &mut fields);
152            }
153            (packet_type::LINK_STATE_UPDATE, 2) => {
154                self.parse_ls_update_v2(packet_data, &mut fields);
155            }
156            (packet_type::LINK_STATE_ACK, 2) => {
157                self.parse_ls_ack_v2(packet_data, &mut fields);
158            }
159            _ => {
160                // Unknown packet type or version
161            }
162        }
163
164        // Calculate remaining data
165        let consumed = std::cmp::min(length as usize, data.len());
166        ParseResult::success(fields, &data[consumed..], SmallVec::new())
167    }
168
169    fn schema_fields(&self) -> Vec<FieldDescriptor> {
170        vec![
171            // Common header fields
172            FieldDescriptor::new("ospf.version", DataKind::UInt8).set_nullable(true),
173            FieldDescriptor::new("ospf.message_type", DataKind::UInt8).set_nullable(true),
174            FieldDescriptor::new("ospf.message_type_name", DataKind::String).set_nullable(true),
175            FieldDescriptor::new("ospf.length", DataKind::UInt16).set_nullable(true),
176            FieldDescriptor::new("ospf.router_id", DataKind::String).set_nullable(true),
177            FieldDescriptor::new("ospf.area_id", DataKind::String).set_nullable(true),
178            FieldDescriptor::new("ospf.auth_type", DataKind::UInt16).set_nullable(true),
179            // Hello packet fields
180            FieldDescriptor::new("ospf.hello_interval", DataKind::UInt16).set_nullable(true),
181            FieldDescriptor::new("ospf.dead_interval", DataKind::UInt32).set_nullable(true),
182            FieldDescriptor::new("ospf.designated_router", DataKind::String).set_nullable(true),
183            FieldDescriptor::new("ospf.backup_dr", DataKind::String).set_nullable(true),
184            FieldDescriptor::new("ospf.neighbor_count", DataKind::UInt16).set_nullable(true),
185            // Database Description fields
186            FieldDescriptor::new("ospf.dd_interface_mtu", DataKind::UInt16).set_nullable(true),
187            FieldDescriptor::new("ospf.dd_options", DataKind::UInt8).set_nullable(true),
188            FieldDescriptor::new("ospf.dd_flags", DataKind::UInt8).set_nullable(true),
189            FieldDescriptor::new("ospf.dd_sequence", DataKind::UInt32).set_nullable(true),
190            FieldDescriptor::new("ospf.dd_lsa_count", DataKind::UInt16).set_nullable(true),
191            // LS Update fields
192            FieldDescriptor::new("ospf.lsu_lsa_count", DataKind::UInt32).set_nullable(true),
193            // LSA header fields (from first LSA in LS Update/Ack)
194            FieldDescriptor::new("ospf.lsa_age", DataKind::UInt16).set_nullable(true),
195            FieldDescriptor::new("ospf.lsa_type", DataKind::UInt8).set_nullable(true),
196            FieldDescriptor::new("ospf.lsa_type_name", DataKind::String).set_nullable(true),
197            FieldDescriptor::new("ospf.lsa_id", DataKind::String).set_nullable(true),
198            FieldDescriptor::new("ospf.lsa_advertising_router", DataKind::String)
199                .set_nullable(true),
200            FieldDescriptor::new("ospf.lsa_sequence", DataKind::UInt32).set_nullable(true),
201            // LS Ack fields
202            FieldDescriptor::new("ospf.lsa_ack_count", DataKind::UInt16).set_nullable(true),
203        ]
204    }
205
206    fn child_protocols(&self) -> &[&'static str] {
207        &[]
208    }
209
210    fn dependencies(&self) -> &'static [&'static str] {
211        &["ipv4"] // OSPF runs directly over IPv4 (IP protocol 89)
212    }
213}
214
215impl OspfProtocol {
216    /// Parse OSPF v2 Hello packet.
217    fn parse_hello_v2(&self, data: &[u8], fields: &mut SmallVec<[(&'static str, FieldValue); 16]>) {
218        if data.len() < 20 {
219            return;
220        }
221
222        // Bytes 0-3: Network Mask (skip)
223
224        // Bytes 4-5: Hello Interval
225        let hello_interval = u16::from_be_bytes([data[4], data[5]]);
226        fields.push(("hello_interval", FieldValue::UInt16(hello_interval)));
227
228        // Bytes 6-7: Options and Router Priority (skip)
229
230        // Bytes 8-11: Router Dead Interval
231        let dead_interval = u32::from_be_bytes([data[8], data[9], data[10], data[11]]);
232        fields.push(("dead_interval", FieldValue::UInt32(dead_interval)));
233
234        // Bytes 12-15: Designated Router
235        let dr = format!("{}.{}.{}.{}", data[12], data[13], data[14], data[15]);
236        fields.push((
237            "designated_router",
238            FieldValue::OwnedString(CompactString::new(dr)),
239        ));
240
241        // Bytes 16-19: Backup Designated Router
242        let bdr = format!("{}.{}.{}.{}", data[16], data[17], data[18], data[19]);
243        fields.push((
244            "backup_dr",
245            FieldValue::OwnedString(CompactString::new(bdr)),
246        ));
247
248        // Count neighbors (remaining data is list of neighbor router IDs)
249        let neighbor_data = &data[20..];
250        let neighbor_count = (neighbor_data.len() / 4) as u16;
251        if neighbor_count > 0 {
252            fields.push(("neighbor_count", FieldValue::UInt16(neighbor_count)));
253        }
254    }
255
256    /// Parse OSPF v2 Database Description packet.
257    fn parse_db_description_v2(
258        &self,
259        data: &[u8],
260        fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
261    ) {
262        if data.len() < 8 {
263            return;
264        }
265
266        // Bytes 0-1: Interface MTU
267        let interface_mtu = u16::from_be_bytes([data[0], data[1]]);
268        fields.push(("dd_interface_mtu", FieldValue::UInt16(interface_mtu)));
269
270        // Byte 2: Options
271        let options = data[2];
272        fields.push(("dd_options", FieldValue::UInt8(options)));
273
274        // Byte 3: DD Flags (I/M/MS bits)
275        let dd_flags = data[3];
276        fields.push(("dd_flags", FieldValue::UInt8(dd_flags)));
277
278        // Bytes 4-7: DD Sequence Number
279        let dd_sequence = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
280        fields.push(("dd_sequence", FieldValue::UInt32(dd_sequence)));
281
282        // LSA headers follow (each 20 bytes)
283        let lsa_data = &data[8..];
284        let lsa_count = (lsa_data.len() / 20) as u16;
285        if lsa_count > 0 {
286            fields.push(("dd_lsa_count", FieldValue::UInt16(lsa_count)));
287            // Parse the first LSA header
288            self.parse_lsa_header(&lsa_data[..20.min(lsa_data.len())], fields);
289        }
290    }
291
292    /// Parse OSPF v2 LS Update packet.
293    fn parse_ls_update_v2(
294        &self,
295        data: &[u8],
296        fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
297    ) {
298        if data.len() < 4 {
299            return;
300        }
301
302        // Bytes 0-3: Number of LSAs
303        let lsa_count = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
304        fields.push(("lsu_lsa_count", FieldValue::UInt32(lsa_count)));
305
306        // LSAs follow (each with 20-byte header + variable body)
307        if lsa_count > 0 && data.len() >= 24 {
308            // Parse the first LSA header
309            self.parse_lsa_header(&data[4..24], fields);
310        }
311    }
312
313    /// Parse OSPF v2 LS Acknowledgment packet.
314    fn parse_ls_ack_v2(
315        &self,
316        data: &[u8],
317        fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
318    ) {
319        // LS Ack contains a list of LSA headers (each 20 bytes)
320        let lsa_count = (data.len() / 20) as u16;
321        if lsa_count > 0 {
322            fields.push(("lsa_ack_count", FieldValue::UInt16(lsa_count)));
323            // Parse the first LSA header
324            self.parse_lsa_header(&data[..20.min(data.len())], fields);
325        }
326    }
327
328    /// Parse a single LSA header (20 bytes).
329    ///
330    /// LSA Header Format:
331    /// ```text
332    ///  0                   1                   2                   3
333    ///  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
334    /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
335    /// |            LS Age             |    Options    |    LS Type    |
336    /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
337    /// |                        Link State ID                          |
338    /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
339    /// |                     Advertising Router                        |
340    /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
341    /// |                     LS Sequence Number                        |
342    /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
343    /// |         LS Checksum           |             Length            |
344    /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
345    /// ```
346    fn parse_lsa_header(
347        &self,
348        data: &[u8],
349        fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
350    ) {
351        if data.len() < 20 {
352            return;
353        }
354
355        // Bytes 0-1: LS Age
356        let ls_age = u16::from_be_bytes([data[0], data[1]]);
357        fields.push(("lsa_age", FieldValue::UInt16(ls_age)));
358
359        // Byte 2: Options (skip)
360        // Byte 3: LS Type
361        let ls_type = data[3];
362        fields.push(("lsa_type", FieldValue::UInt8(ls_type)));
363        fields.push(("lsa_type_name", FieldValue::Str(lsa_type_name(ls_type))));
364
365        // Bytes 4-7: Link State ID
366        let ls_id = format!("{}.{}.{}.{}", data[4], data[5], data[6], data[7]);
367        fields.push(("lsa_id", FieldValue::OwnedString(CompactString::new(ls_id))));
368
369        // Bytes 8-11: Advertising Router
370        let adv_router = format!("{}.{}.{}.{}", data[8], data[9], data[10], data[11]);
371        fields.push((
372            "lsa_advertising_router",
373            FieldValue::OwnedString(CompactString::new(adv_router)),
374        ));
375
376        // Bytes 12-15: LS Sequence Number
377        let ls_sequence = u32::from_be_bytes([data[12], data[13], data[14], data[15]]);
378        fields.push(("lsa_sequence", FieldValue::UInt32(ls_sequence)));
379
380        // Bytes 16-17: LS Checksum (skip)
381        // Bytes 18-19: Length (skip)
382    }
383}
384
385#[cfg(test)]
386mod tests {
387    use super::*;
388
389    /// Create an OSPF v2 header.
390    fn create_ospf_header(
391        version: u8,
392        msg_type: u8,
393        length: u16,
394        router_id: [u8; 4],
395        area_id: [u8; 4],
396    ) -> Vec<u8> {
397        let mut header = Vec::new();
398
399        // Version
400        header.push(version);
401
402        // Type
403        header.push(msg_type);
404
405        // Length
406        header.extend_from_slice(&length.to_be_bytes());
407
408        // Router ID
409        header.extend_from_slice(&router_id);
410
411        // Area ID
412        header.extend_from_slice(&area_id);
413
414        // Checksum
415        header.extend_from_slice(&[0x00, 0x00]);
416
417        // AuType
418        header.extend_from_slice(&[0x00, 0x00]);
419
420        // Authentication (8 bytes)
421        header.extend_from_slice(&[0x00; 8]);
422
423        header
424    }
425
426    /// Create an OSPF v2 Hello packet.
427    fn create_ospf_hello(
428        router_id: [u8; 4],
429        area_id: [u8; 4],
430        hello_interval: u16,
431        dead_interval: u32,
432        dr: [u8; 4],
433        bdr: [u8; 4],
434    ) -> Vec<u8> {
435        let mut pkt = create_ospf_header(2, packet_type::HELLO, 44, router_id, area_id);
436
437        // Network Mask
438        pkt.extend_from_slice(&[255, 255, 255, 0]);
439
440        // Hello Interval
441        pkt.extend_from_slice(&hello_interval.to_be_bytes());
442
443        // Options
444        pkt.push(0x02);
445
446        // Router Priority
447        pkt.push(1);
448
449        // Router Dead Interval
450        pkt.extend_from_slice(&dead_interval.to_be_bytes());
451
452        // Designated Router
453        pkt.extend_from_slice(&dr);
454
455        // Backup Designated Router
456        pkt.extend_from_slice(&bdr);
457
458        pkt
459    }
460
461    // Test 1: can_parse with IP protocol 89
462    #[test]
463    fn test_can_parse_with_ip_protocol_89() {
464        let parser = OspfProtocol;
465
466        // Without hint
467        let ctx1 = ParseContext::new(1);
468        assert!(parser.can_parse(&ctx1).is_none());
469
470        // With wrong protocol
471        let mut ctx2 = ParseContext::new(1);
472        ctx2.insert_hint("ip_protocol", 6); // TCP
473        assert!(parser.can_parse(&ctx2).is_none());
474
475        // With OSPF protocol
476        let mut ctx3 = ParseContext::new(1);
477        ctx3.insert_hint("ip_protocol", 89);
478        assert!(parser.can_parse(&ctx3).is_some());
479        assert_eq!(parser.can_parse(&ctx3), Some(100));
480    }
481
482    // Test 2: OSPF header parsing
483    #[test]
484    fn test_ospf_header_parsing() {
485        let parser = OspfProtocol;
486        let mut context = ParseContext::new(1);
487        context.insert_hint("ip_protocol", 89);
488
489        let pkt = create_ospf_header(2, packet_type::HELLO, 24, [192, 168, 1, 1], [0, 0, 0, 0]);
490
491        let result = parser.parse(&pkt, &context);
492
493        assert!(result.is_ok());
494        assert_eq!(result.get("version"), Some(&FieldValue::UInt8(2)));
495        assert_eq!(
496            result.get("message_type"),
497            Some(&FieldValue::UInt8(packet_type::HELLO))
498        );
499        assert_eq!(result.get("length"), Some(&FieldValue::UInt16(24)));
500    }
501
502    // Test 3: Version detection (v2 vs v3)
503    #[test]
504    fn test_version_detection() {
505        let parser = OspfProtocol;
506        let mut context = ParseContext::new(1);
507        context.insert_hint("ip_protocol", 89);
508
509        // OSPFv2
510        let pkt_v2 = create_ospf_header(2, packet_type::HELLO, 24, [1, 1, 1, 1], [0, 0, 0, 0]);
511        let result_v2 = parser.parse(&pkt_v2, &context);
512        assert!(result_v2.is_ok());
513        assert_eq!(result_v2.get("version"), Some(&FieldValue::UInt8(2)));
514
515        // OSPFv3
516        let pkt_v3 = create_ospf_header(3, packet_type::HELLO, 24, [1, 1, 1, 1], [0, 0, 0, 0]);
517        let result_v3 = parser.parse(&pkt_v3, &context);
518        assert!(result_v3.is_ok());
519        assert_eq!(result_v3.get("version"), Some(&FieldValue::UInt8(3)));
520    }
521
522    // Test 4: Hello packet parsing
523    #[test]
524    fn test_hello_packet_parsing() {
525        let parser = OspfProtocol;
526        let mut context = ParseContext::new(1);
527        context.insert_hint("ip_protocol", 89);
528
529        let pkt = create_ospf_hello(
530            [192, 168, 1, 1],
531            [0, 0, 0, 0],
532            10,               // Hello interval
533            40,               // Dead interval
534            [192, 168, 1, 1], // DR
535            [192, 168, 1, 2], // BDR
536        );
537
538        let result = parser.parse(&pkt, &context);
539
540        assert!(result.is_ok());
541        assert_eq!(result.get("hello_interval"), Some(&FieldValue::UInt16(10)));
542        assert_eq!(result.get("dead_interval"), Some(&FieldValue::UInt32(40)));
543        assert_eq!(
544            result.get("designated_router"),
545            Some(&FieldValue::OwnedString(CompactString::new("192.168.1.1")))
546        );
547        assert_eq!(
548            result.get("backup_dr"),
549            Some(&FieldValue::OwnedString(CompactString::new("192.168.1.2")))
550        );
551    }
552
553    // Test 5: Router ID extraction
554    #[test]
555    fn test_router_id_extraction() {
556        let parser = OspfProtocol;
557        let mut context = ParseContext::new(1);
558        context.insert_hint("ip_protocol", 89);
559
560        let pkt = create_ospf_header(2, packet_type::HELLO, 24, [10, 0, 0, 1], [0, 0, 0, 0]);
561
562        let result = parser.parse(&pkt, &context);
563
564        assert!(result.is_ok());
565        assert_eq!(
566            result.get("router_id"),
567            Some(&FieldValue::OwnedString(CompactString::new("10.0.0.1")))
568        );
569    }
570
571    // Test 6: Area ID extraction
572    #[test]
573    fn test_area_id_extraction() {
574        let parser = OspfProtocol;
575        let mut context = ParseContext::new(1);
576        context.insert_hint("ip_protocol", 89);
577
578        // Backbone area (0.0.0.0)
579        let pkt1 = create_ospf_header(2, packet_type::HELLO, 24, [1, 1, 1, 1], [0, 0, 0, 0]);
580        let result1 = parser.parse(&pkt1, &context);
581        assert!(result1.is_ok());
582        assert_eq!(
583            result1.get("area_id"),
584            Some(&FieldValue::OwnedString(CompactString::new("0.0.0.0")))
585        );
586
587        // Non-backbone area
588        let pkt2 = create_ospf_header(2, packet_type::HELLO, 24, [1, 1, 1, 1], [0, 0, 0, 1]);
589        let result2 = parser.parse(&pkt2, &context);
590        assert!(result2.is_ok());
591        assert_eq!(
592            result2.get("area_id"),
593            Some(&FieldValue::OwnedString(CompactString::new("0.0.0.1")))
594        );
595    }
596
597    // Test 7: Message type name mapping
598    #[test]
599    fn test_message_type_name_mapping() {
600        let parser = OspfProtocol;
601        let mut context = ParseContext::new(1);
602        context.insert_hint("ip_protocol", 89);
603
604        let test_types = [
605            (packet_type::HELLO, "Hello"),
606            (packet_type::DATABASE_DESCRIPTION, "Database Description"),
607            (packet_type::LINK_STATE_REQUEST, "Link State Request"),
608            (packet_type::LINK_STATE_UPDATE, "Link State Update"),
609            (packet_type::LINK_STATE_ACK, "Link State Acknowledgment"),
610        ];
611
612        for (pkt_type, name) in test_types {
613            let pkt = create_ospf_header(2, pkt_type, 24, [1, 1, 1, 1], [0, 0, 0, 0]);
614            let result = parser.parse(&pkt, &context);
615
616            assert!(result.is_ok());
617            assert_eq!(
618                result.get("message_type"),
619                Some(&FieldValue::UInt8(pkt_type))
620            );
621            assert_eq!(
622                result.get("message_type_name"),
623                Some(&FieldValue::Str(name))
624            );
625        }
626    }
627
628    // Test 8: Too short packet
629    #[test]
630    fn test_ospf_too_short() {
631        let parser = OspfProtocol;
632        let mut context = ParseContext::new(1);
633        context.insert_hint("ip_protocol", 89);
634
635        let short_pkt = [2u8, 1, 0, 24]; // Only 4 bytes
636        let result = parser.parse(&short_pkt, &context);
637
638        assert!(!result.is_ok());
639        assert!(result.error.is_some());
640    }
641
642    // Test 9: Authentication type
643    #[test]
644    fn test_auth_type() {
645        let parser = OspfProtocol;
646        let mut context = ParseContext::new(1);
647        context.insert_hint("ip_protocol", 89);
648
649        let mut pkt = create_ospf_header(2, packet_type::HELLO, 24, [1, 1, 1, 1], [0, 0, 0, 0]);
650        // Set auth type to MD5 (2)
651        pkt[14] = 0;
652        pkt[15] = 2;
653
654        let result = parser.parse(&pkt, &context);
655
656        assert!(result.is_ok());
657        assert_eq!(result.get("auth_type"), Some(&FieldValue::UInt16(2)));
658    }
659
660    // Test 10: Schema fields
661    #[test]
662    fn test_ospf_schema_fields() {
663        let parser = OspfProtocol;
664        let fields = parser.schema_fields();
665
666        assert!(!fields.is_empty());
667        let field_names: Vec<&str> = fields.iter().map(|f| f.name).collect();
668        assert!(field_names.contains(&"ospf.version"));
669        assert!(field_names.contains(&"ospf.message_type"));
670        assert!(field_names.contains(&"ospf.message_type_name"));
671        assert!(field_names.contains(&"ospf.length"));
672        assert!(field_names.contains(&"ospf.router_id"));
673        assert!(field_names.contains(&"ospf.area_id"));
674        assert!(field_names.contains(&"ospf.auth_type"));
675        assert!(field_names.contains(&"ospf.hello_interval"));
676        assert!(field_names.contains(&"ospf.dead_interval"));
677        assert!(field_names.contains(&"ospf.designated_router"));
678        assert!(field_names.contains(&"ospf.backup_dr"));
679    }
680
681    // Test 11: Database Description parsing
682    #[test]
683    fn test_database_description_parsing() {
684        let parser = OspfProtocol;
685        let mut context = ParseContext::new(1);
686        context.insert_hint("ip_protocol", 89);
687
688        // Build DD packet
689        let mut pkt = create_ospf_header(
690            2,
691            packet_type::DATABASE_DESCRIPTION,
692            32,
693            [10, 0, 0, 1],
694            [0, 0, 0, 0],
695        );
696
697        // DD specific fields
698        pkt.extend_from_slice(&1500u16.to_be_bytes()); // Interface MTU
699        pkt.push(0x02); // Options
700        pkt.push(0x07); // DD Flags (I/M/MS all set)
701        pkt.extend_from_slice(&0x12345678u32.to_be_bytes()); // DD Sequence
702
703        let result = parser.parse(&pkt, &context);
704
705        assert!(result.is_ok());
706        assert_eq!(
707            result.get("dd_interface_mtu"),
708            Some(&FieldValue::UInt16(1500))
709        );
710        assert_eq!(result.get("dd_options"), Some(&FieldValue::UInt8(0x02)));
711        assert_eq!(result.get("dd_flags"), Some(&FieldValue::UInt8(0x07)));
712        assert_eq!(
713            result.get("dd_sequence"),
714            Some(&FieldValue::UInt32(0x12345678))
715        );
716    }
717
718    // Test 12: Database Description with LSA headers
719    #[test]
720    fn test_database_description_with_lsa() {
721        let parser = OspfProtocol;
722        let mut context = ParseContext::new(1);
723        context.insert_hint("ip_protocol", 89);
724
725        // Build DD packet
726        let mut pkt = create_ospf_header(
727            2,
728            packet_type::DATABASE_DESCRIPTION,
729            52, // 24 header + 8 DD + 20 LSA header
730            [10, 0, 0, 1],
731            [0, 0, 0, 0],
732        );
733
734        // DD specific fields
735        pkt.extend_from_slice(&1500u16.to_be_bytes()); // Interface MTU
736        pkt.push(0x02); // Options
737        pkt.push(0x01); // DD Flags (MS only)
738        pkt.extend_from_slice(&1u32.to_be_bytes()); // DD Sequence
739
740        // LSA Header (20 bytes)
741        pkt.extend_from_slice(&100u16.to_be_bytes()); // LS Age = 100
742        pkt.push(0x00); // Options
743        pkt.push(lsa_type::ROUTER); // LS Type = Router-LSA
744        pkt.extend_from_slice(&[10, 0, 0, 1]); // Link State ID
745        pkt.extend_from_slice(&[10, 0, 0, 1]); // Advertising Router
746        pkt.extend_from_slice(&0x80000001u32.to_be_bytes()); // LS Sequence
747        pkt.extend_from_slice(&[0x00, 0x00]); // LS Checksum
748        pkt.extend_from_slice(&36u16.to_be_bytes()); // Length
749
750        let result = parser.parse(&pkt, &context);
751
752        assert!(result.is_ok());
753        assert_eq!(result.get("dd_lsa_count"), Some(&FieldValue::UInt16(1)));
754        assert_eq!(result.get("lsa_age"), Some(&FieldValue::UInt16(100)));
755        assert_eq!(
756            result.get("lsa_type"),
757            Some(&FieldValue::UInt8(lsa_type::ROUTER))
758        );
759        assert_eq!(
760            result.get("lsa_type_name"),
761            Some(&FieldValue::Str("Router-LSA"))
762        );
763    }
764
765    // Test 13: LS Update parsing
766    #[test]
767    fn test_ls_update_parsing() {
768        let parser = OspfProtocol;
769        let mut context = ParseContext::new(1);
770        context.insert_hint("ip_protocol", 89);
771
772        // Build LS Update packet
773        let mut pkt = create_ospf_header(
774            2,
775            packet_type::LINK_STATE_UPDATE,
776            48, // 24 header + 4 count + 20 LSA header
777            [10, 0, 0, 1],
778            [0, 0, 0, 0],
779        );
780
781        // Number of LSAs
782        pkt.extend_from_slice(&2u32.to_be_bytes()); // 2 LSAs
783
784        // First LSA Header (20 bytes)
785        pkt.extend_from_slice(&500u16.to_be_bytes()); // LS Age
786        pkt.push(0x00); // Options
787        pkt.push(lsa_type::NETWORK); // LS Type = Network-LSA
788        pkt.extend_from_slice(&[192, 168, 1, 0]); // Link State ID
789        pkt.extend_from_slice(&[192, 168, 1, 1]); // Advertising Router
790        pkt.extend_from_slice(&0x80000002u32.to_be_bytes()); // LS Sequence
791        pkt.extend_from_slice(&[0x00, 0x00]); // LS Checksum
792        pkt.extend_from_slice(&32u16.to_be_bytes()); // Length
793
794        let result = parser.parse(&pkt, &context);
795
796        assert!(result.is_ok());
797        assert_eq!(result.get("lsu_lsa_count"), Some(&FieldValue::UInt32(2)));
798        assert_eq!(result.get("lsa_age"), Some(&FieldValue::UInt16(500)));
799        assert_eq!(
800            result.get("lsa_type"),
801            Some(&FieldValue::UInt8(lsa_type::NETWORK))
802        );
803        assert_eq!(
804            result.get("lsa_type_name"),
805            Some(&FieldValue::Str("Network-LSA"))
806        );
807        assert_eq!(
808            result.get("lsa_id"),
809            Some(&FieldValue::OwnedString(CompactString::new("192.168.1.0")))
810        );
811        assert_eq!(
812            result.get("lsa_advertising_router"),
813            Some(&FieldValue::OwnedString(CompactString::new("192.168.1.1")))
814        );
815    }
816
817    // Test 14: LS Acknowledgment parsing
818    #[test]
819    fn test_ls_ack_parsing() {
820        let parser = OspfProtocol;
821        let mut context = ParseContext::new(1);
822        context.insert_hint("ip_protocol", 89);
823
824        // Build LS Ack packet
825        let mut pkt = create_ospf_header(
826            2,
827            packet_type::LINK_STATE_ACK,
828            64, // 24 header + 40 (2 LSA headers)
829            [10, 0, 0, 1],
830            [0, 0, 0, 0],
831        );
832
833        // First LSA Header (20 bytes)
834        pkt.extend_from_slice(&200u16.to_be_bytes()); // LS Age
835        pkt.push(0x00); // Options
836        pkt.push(lsa_type::AS_EXTERNAL); // LS Type = AS-External-LSA
837        pkt.extend_from_slice(&[0, 0, 0, 0]); // Link State ID
838        pkt.extend_from_slice(&[172, 16, 0, 1]); // Advertising Router
839        pkt.extend_from_slice(&0x80000010u32.to_be_bytes()); // LS Sequence
840        pkt.extend_from_slice(&[0x00, 0x00]); // LS Checksum
841        pkt.extend_from_slice(&36u16.to_be_bytes()); // Length
842
843        // Second LSA Header (20 bytes)
844        pkt.extend_from_slice(&300u16.to_be_bytes()); // LS Age
845        pkt.push(0x00); // Options
846        pkt.push(lsa_type::SUMMARY_NETWORK); // LS Type
847        pkt.extend_from_slice(&[10, 1, 0, 0]); // Link State ID
848        pkt.extend_from_slice(&[10, 0, 0, 1]); // Advertising Router
849        pkt.extend_from_slice(&0x80000005u32.to_be_bytes()); // LS Sequence
850        pkt.extend_from_slice(&[0x00, 0x00]); // LS Checksum
851        pkt.extend_from_slice(&28u16.to_be_bytes()); // Length
852
853        let result = parser.parse(&pkt, &context);
854
855        assert!(result.is_ok());
856        assert_eq!(result.get("lsa_ack_count"), Some(&FieldValue::UInt16(2)));
857        // First LSA header fields
858        assert_eq!(result.get("lsa_age"), Some(&FieldValue::UInt16(200)));
859        assert_eq!(
860            result.get("lsa_type"),
861            Some(&FieldValue::UInt8(lsa_type::AS_EXTERNAL))
862        );
863        assert_eq!(
864            result.get("lsa_type_name"),
865            Some(&FieldValue::Str("AS-External-LSA"))
866        );
867    }
868
869    // Test 15: LSA type names
870    #[test]
871    fn test_lsa_type_names() {
872        let parser = OspfProtocol;
873        let mut context = ParseContext::new(1);
874        context.insert_hint("ip_protocol", 89);
875
876        let test_cases = [
877            (lsa_type::ROUTER, "Router-LSA"),
878            (lsa_type::NETWORK, "Network-LSA"),
879            (lsa_type::SUMMARY_NETWORK, "Summary-LSA-Network"),
880            (lsa_type::SUMMARY_ASBR, "Summary-LSA-ASBR"),
881            (lsa_type::AS_EXTERNAL, "AS-External-LSA"),
882        ];
883
884        for (ls_type, expected_name) in test_cases {
885            let mut pkt = create_ospf_header(
886                2,
887                packet_type::LINK_STATE_UPDATE,
888                48,
889                [10, 0, 0, 1],
890                [0, 0, 0, 0],
891            );
892
893            pkt.extend_from_slice(&1u32.to_be_bytes()); // 1 LSA
894
895            // LSA Header
896            pkt.extend_from_slice(&100u16.to_be_bytes()); // LS Age
897            pkt.push(0x00); // Options
898            pkt.push(ls_type); // LS Type
899            pkt.extend_from_slice(&[10, 0, 0, 0]); // Link State ID
900            pkt.extend_from_slice(&[10, 0, 0, 1]); // Advertising Router
901            pkt.extend_from_slice(&0x80000001u32.to_be_bytes()); // LS Sequence
902            pkt.extend_from_slice(&[0x00, 0x00]); // LS Checksum
903            pkt.extend_from_slice(&20u16.to_be_bytes()); // Length
904
905            let result = parser.parse(&pkt, &context);
906            assert!(result.is_ok());
907            assert_eq!(result.get("lsa_type"), Some(&FieldValue::UInt8(ls_type)));
908            assert_eq!(
909                result.get("lsa_type_name"),
910                Some(&FieldValue::Str(expected_name))
911            );
912        }
913    }
914
915    // Test 16: LSA sequence number
916    #[test]
917    fn test_lsa_sequence_number() {
918        let parser = OspfProtocol;
919        let mut context = ParseContext::new(1);
920        context.insert_hint("ip_protocol", 89);
921
922        // Build LS Update with specific sequence number
923        let mut pkt = create_ospf_header(
924            2,
925            packet_type::LINK_STATE_UPDATE,
926            48,
927            [10, 0, 0, 1],
928            [0, 0, 0, 0],
929        );
930
931        pkt.extend_from_slice(&1u32.to_be_bytes()); // 1 LSA
932
933        // LSA Header with specific sequence 0x80001234
934        pkt.extend_from_slice(&100u16.to_be_bytes()); // LS Age
935        pkt.push(0x00); // Options
936        pkt.push(lsa_type::ROUTER); // LS Type
937        pkt.extend_from_slice(&[10, 0, 0, 1]); // Link State ID
938        pkt.extend_from_slice(&[10, 0, 0, 1]); // Advertising Router
939        pkt.extend_from_slice(&0x80001234u32.to_be_bytes()); // LS Sequence
940        pkt.extend_from_slice(&[0x00, 0x00]); // LS Checksum
941        pkt.extend_from_slice(&20u16.to_be_bytes()); // Length
942
943        let result = parser.parse(&pkt, &context);
944
945        assert!(result.is_ok());
946        assert_eq!(
947            result.get("lsa_sequence"),
948            Some(&FieldValue::UInt32(0x80001234))
949        );
950    }
951
952    // Test 17: Hello with neighbors
953    #[test]
954    fn test_hello_with_neighbors() {
955        let parser = OspfProtocol;
956        let mut context = ParseContext::new(1);
957        context.insert_hint("ip_protocol", 89);
958
959        // Build Hello packet with 3 neighbors
960        // Need to manually create header with correct length (44 base + 12 neighbors = 56)
961        let mut pkt = create_ospf_header(
962            2,
963            packet_type::HELLO,
964            56, // 24 header + 20 hello + 12 neighbors
965            [192, 168, 1, 1],
966            [0, 0, 0, 0],
967        );
968
969        // Network Mask
970        pkt.extend_from_slice(&[255, 255, 255, 0]);
971        // Hello Interval
972        pkt.extend_from_slice(&10u16.to_be_bytes());
973        // Options
974        pkt.push(0x02);
975        // Router Priority
976        pkt.push(1);
977        // Router Dead Interval
978        pkt.extend_from_slice(&40u32.to_be_bytes());
979        // Designated Router
980        pkt.extend_from_slice(&[192, 168, 1, 1]);
981        // Backup Designated Router
982        pkt.extend_from_slice(&[192, 168, 1, 2]);
983
984        // Add neighbor router IDs
985        pkt.extend_from_slice(&[192, 168, 1, 2]); // Neighbor 1
986        pkt.extend_from_slice(&[192, 168, 1, 3]); // Neighbor 2
987        pkt.extend_from_slice(&[192, 168, 1, 4]); // Neighbor 3
988
989        let result = parser.parse(&pkt, &context);
990
991        assert!(result.is_ok());
992        assert_eq!(result.get("neighbor_count"), Some(&FieldValue::UInt16(3)));
993    }
994
995    // Test 18: Malformed packet with length < 24 (regression test for fuzz crash)
996    #[test]
997    fn test_malformed_length_less_than_header() {
998        let parser = OspfProtocol;
999        let mut context = ParseContext::new(1);
1000        context.insert_hint("ip_protocol", 89);
1001
1002        // Crash input from fuzzer: length field is 0 which is less than header size (24)
1003        // This should not panic - it should handle gracefully
1004        let crash_input: [u8; 32] = [
1005            2, 2, 0, 0, // version=2, type=2, length=0 (invalid!)
1006            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 105, 112, 95, 112, 114, 111, 116, 111, 99, 111, 108,
1007            2, 7, 0, 0, 0, 1,
1008        ];
1009
1010        // Should not panic, should parse header fields
1011        let result = parser.parse(&crash_input, &context);
1012        assert!(result.is_ok());
1013        assert_eq!(result.get("version"), Some(&FieldValue::UInt8(2)));
1014        assert_eq!(result.get("message_type"), Some(&FieldValue::UInt8(2)));
1015        assert_eq!(result.get("length"), Some(&FieldValue::UInt16(0)));
1016    }
1017
1018    // Test 19: LS Age field in LSA
1019    #[test]
1020    fn test_lsa_age_field() {
1021        let parser = OspfProtocol;
1022        let mut context = ParseContext::new(1);
1023        context.insert_hint("ip_protocol", 89);
1024
1025        // Build LS Update with specific LS Age
1026        let mut pkt = create_ospf_header(
1027            2,
1028            packet_type::LINK_STATE_UPDATE,
1029            48,
1030            [10, 0, 0, 1],
1031            [0, 0, 0, 0],
1032        );
1033
1034        pkt.extend_from_slice(&1u32.to_be_bytes()); // 1 LSA
1035
1036        // LSA Header with LS Age = 1800 (halfway to MaxAge)
1037        pkt.extend_from_slice(&1800u16.to_be_bytes()); // LS Age
1038        pkt.push(0x00); // Options
1039        pkt.push(lsa_type::ROUTER); // LS Type
1040        pkt.extend_from_slice(&[10, 0, 0, 1]); // Link State ID
1041        pkt.extend_from_slice(&[10, 0, 0, 1]); // Advertising Router
1042        pkt.extend_from_slice(&0x80000001u32.to_be_bytes()); // LS Sequence
1043        pkt.extend_from_slice(&[0x00, 0x00]); // LS Checksum
1044        pkt.extend_from_slice(&20u16.to_be_bytes()); // Length
1045
1046        let result = parser.parse(&pkt, &context);
1047
1048        assert!(result.is_ok());
1049        assert_eq!(result.get("lsa_age"), Some(&FieldValue::UInt16(1800)));
1050    }
1051}