Skip to main content

packet_strata/packet/tunnel/
pbb.rs

1//! PBB (Provider Backbone Bridge) / MAC-in-MAC protocol parser
2//!
3//! This module implements parsing for PBB as defined in IEEE 802.1ah.
4//! PBB provides a scalable solution for extending Ethernet networks by
5//! encapsulating customer Ethernet frames within provider backbone frames.
6//!
7//! # PBB Frame Format (IEEE 802.1ah)
8//!
9//! ```text
10//! +-------------------+-------------------+-------+-------+-----------------+
11//! | B-DA (6 bytes)    | B-SA (6 bytes)    | B-Tag | I-Tag | Customer Frame  |
12//! +-------------------+-------------------+-------+-------+-----------------+
13//!
14//! B-Tag (4 bytes) - Backbone VLAN Tag:
15//!  0                   1                   2                   3
16//!  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
17//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
18//! |         EtherType (0x88A8)    |PCP|D|         B-VID           |
19//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20//!
21//! I-Tag (6 bytes) - Instance Service Tag:
22//!  0                   1                   2                   3
23//!  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
24//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
25//! |         EtherType (0x88E7)    |I-PCP|D|U|Res|    I-SID        |
26//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
27//! |                    I-SID (continued)        |
28//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
29//! ```
30//!
31//! # Key characteristics
32//!
33//! - B-Tag EtherType: 0x88A8 (same as 802.1ad S-Tag)
34//! - I-Tag EtherType: 0x88E7
35//! - I-SID (Service Instance Identifier): 24 bits, supports up to 16 million service instances
36//! - Encapsulates complete customer Ethernet frames (including customer VLANs)
37//!
38//! # Examples
39//!
40//! ## Basic I-Tag parsing
41//!
42//! ```
43//! use packet_strata::packet::tunnel::pbb::PbbITag;
44//! use packet_strata::packet::HeaderParser;
45//!
46//! // I-Tag with I-SID = 100
47//! let packet = vec![
48//!     0x88, 0xE7,              // EtherType
49//!     0x00, 0x00, 0x00, 0x64,  // Flags + I-SID = 100
50//!     // Customer Ethernet frame follows...
51//!     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
52//! ];
53//!
54//! let (itag, payload) = PbbITag::from_bytes(&packet).unwrap();
55//! assert_eq!(itag.isid(), 100);
56//! ```
57//!
58//! ## I-Tag with priority
59//!
60//! ```
61//! use packet_strata::packet::tunnel::pbb::PbbITag;
62//! use packet_strata::packet::HeaderParser;
63//!
64//! // I-Tag with I-PCP = 5, I-DEI = 1, I-SID = 0x123456
65//! let packet = vec![
66//!     0x88, 0xE7,              // EtherType
67//!     0xB0, 0x12, 0x34, 0x56,  // I-PCP=5, DEI=1, I-SID=0x123456
68//!     // Payload
69//!     0x00, 0x00,
70//! ];
71//!
72//! let (itag, _) = PbbITag::from_bytes(&packet).unwrap();
73//! assert_eq!(itag.ipcp(), 5);
74//! assert!(itag.dei());
75//! assert_eq!(itag.isid(), 0x123456);
76//! ```
77
78use std::fmt::{self, Formatter};
79
80use zerocopy::byteorder::{BigEndian, U16};
81use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
82
83use crate::packet::protocol::EtherProto;
84use crate::packet::{HeaderParser, PacketHeader};
85
86/// I-Tag EtherType (802.1ah Backbone Service Tag)
87pub const PBB_ITAG_ETHERTYPE: u16 = 0x88E7;
88
89/// B-Tag EtherType (802.1ad Provider Bridge / S-Tag)
90pub const PBB_BTAG_ETHERTYPE: u16 = 0x88A8;
91
92/// Maximum I-SID value (24-bit field)
93pub const PBB_MAX_ISID: u32 = 0xFFFFFF;
94
95/// Check if EtherType is I-Tag (PBB)
96#[inline]
97pub fn is_pbb_itag_ethertype(ethertype: u16) -> bool {
98    ethertype == PBB_ITAG_ETHERTYPE
99}
100
101/// Check if EtherType is B-Tag
102#[inline]
103pub fn is_pbb_btag_ethertype(ethertype: u16) -> bool {
104    ethertype == PBB_BTAG_ETHERTYPE
105}
106
107/// PBB I-Tag (Instance Service Tag) as defined in IEEE 802.1ah
108///
109/// The I-Tag is 6 bytes and contains the service instance identifier (I-SID)
110/// that identifies the service instance for customer traffic.
111///
112/// Format:
113/// ```text
114///  0                   1                   2                   3
115///  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
116/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
117/// |         EtherType (0x88E7)    |I-PCP|D|U|Res|    I-SID (high) |
118/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
119/// |          I-SID (low)          |
120/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
121/// ```
122///
123/// Fields:
124/// - I-PCP (3 bits): Priority Code Point
125/// - I-DEI (1 bit): Drop Eligible Indicator
126/// - UCA (1 bit): Use Customer Address
127/// - Reserved (3 bits): Reserved for future use
128/// - I-SID (24 bits): Service Instance Identifier
129#[repr(C, packed)]
130#[derive(FromBytes, IntoBytes, Unaligned, Debug, Clone, Copy, KnownLayout, Immutable)]
131pub struct PbbITag {
132    ethertype: U16<BigEndian>,
133    tci_isid_high: U16<BigEndian>,
134    isid_low: U16<BigEndian>,
135}
136
137impl PbbITag {
138    /// I-Tag EtherType value
139    pub const ETHERTYPE: u16 = PBB_ITAG_ETHERTYPE;
140
141    /// Header size in bytes
142    pub const HEADER_LEN: usize = 6;
143
144    // TCI field masks (in the tci_isid_high field)
145    const IPCP_MASK: u16 = 0xE000;
146    const IPCP_SHIFT: u32 = 13;
147    const DEI_MASK: u16 = 0x1000;
148    const UCA_MASK: u16 = 0x0800;
149    const RESERVED_MASK: u16 = 0x0700;
150    const ISID_HIGH_MASK: u16 = 0x00FF;
151
152    #[allow(unused)]
153    const NAME: &'static str = "PBB-I-Tag";
154
155    /// Returns the EtherType field
156    #[inline]
157    pub fn ethertype(&self) -> u16 {
158        self.ethertype.get()
159    }
160
161    /// Returns the Priority Code Point (I-PCP) - 3 bits
162    ///
163    /// Values 0-7 indicate the priority level for the frame.
164    #[inline]
165    pub fn ipcp(&self) -> u8 {
166        ((self.tci_isid_high.get() & Self::IPCP_MASK) >> Self::IPCP_SHIFT) as u8
167    }
168
169    /// Returns the Drop Eligible Indicator (I-DEI)
170    ///
171    /// When true, the frame may be dropped in case of congestion.
172    #[inline]
173    pub fn dei(&self) -> bool {
174        (self.tci_isid_high.get() & Self::DEI_MASK) != 0
175    }
176
177    /// Returns the Use Customer Address (UCA) flag
178    ///
179    /// When true, the customer destination address is used for forwarding decisions.
180    #[inline]
181    pub fn uca(&self) -> bool {
182        (self.tci_isid_high.get() & Self::UCA_MASK) != 0
183    }
184
185    /// Returns the reserved bits (should be 0)
186    #[inline]
187    pub fn reserved(&self) -> u8 {
188        ((self.tci_isid_high.get() & Self::RESERVED_MASK) >> 8) as u8
189    }
190
191    /// Returns the Service Instance Identifier (I-SID) - 24 bits
192    ///
193    /// The I-SID identifies the service instance. Valid values are 0 to 16,777,215.
194    #[inline]
195    pub fn isid(&self) -> u32 {
196        let high = (self.tci_isid_high.get() & Self::ISID_HIGH_MASK) as u32;
197        let low = self.isid_low.get() as u32;
198        (high << 16) | low
199    }
200
201    /// Validates the I-Tag header
202    #[inline]
203    fn is_valid(&self) -> bool {
204        self.ethertype.get() == Self::ETHERTYPE
205    }
206
207    /// Returns a string representation of the flags
208    pub fn flags_string(&self) -> String {
209        let mut flags = Vec::new();
210        if self.dei() {
211            flags.push("DEI");
212        }
213        if self.uca() {
214            flags.push("UCA");
215        }
216        if flags.is_empty() {
217            "none".to_string()
218        } else {
219            flags.join("|")
220        }
221    }
222}
223
224impl PacketHeader for PbbITag {
225    const NAME: &'static str = "PBB-I-Tag";
226
227    type InnerType = EtherProto;
228
229    #[inline]
230    fn inner_type(&self) -> Self::InnerType {
231        // I-Tag encapsulates customer Ethernet frames
232        EtherProto::TEB
233    }
234
235    #[inline]
236    fn total_len(&self, _buf: &[u8]) -> usize {
237        Self::HEADER_LEN
238    }
239
240    #[inline]
241    fn is_valid(&self) -> bool {
242        PbbITag::is_valid(self)
243    }
244}
245
246impl HeaderParser for PbbITag {
247    type Output<'a> = &'a PbbITag;
248
249    #[inline]
250    fn into_view<'a>(header: &'a Self, _raw_options: &'a [u8]) -> Self::Output<'a> {
251        header
252    }
253}
254
255impl fmt::Display for PbbITag {
256    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
257        write!(
258            f,
259            "PBB I-Tag I-SID={} I-PCP={} flags=[{}]",
260            self.isid(),
261            self.ipcp(),
262            self.flags_string()
263        )
264    }
265}
266
267/// PBB B-Tag (Backbone VLAN Tag) as defined in IEEE 802.1ah
268///
269/// The B-Tag is essentially an 802.1ad S-Tag used in the provider backbone.
270/// It is 4 bytes and contains the Backbone VLAN ID (B-VID).
271///
272/// Format:
273/// ```text
274///  0                   1                   2                   3
275///  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
276/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
277/// |         EtherType (0x88A8)    |PCP|D|         B-VID           |
278/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
279/// ```
280#[repr(C, packed)]
281#[derive(FromBytes, IntoBytes, Unaligned, Debug, Clone, Copy, KnownLayout, Immutable)]
282pub struct PbbBTag {
283    ethertype: U16<BigEndian>,
284    tci: U16<BigEndian>,
285}
286
287impl PbbBTag {
288    /// B-Tag EtherType value
289    pub const ETHERTYPE: u16 = PBB_BTAG_ETHERTYPE;
290
291    /// Header size in bytes
292    pub const HEADER_LEN: usize = 4;
293
294    // TCI field masks
295    const PCP_MASK: u16 = 0xE000;
296    const PCP_SHIFT: u32 = 13;
297    const DEI_MASK: u16 = 0x1000;
298    const VID_MASK: u16 = 0x0FFF;
299
300    #[allow(unused)]
301    const NAME: &'static str = "PBB-B-Tag";
302
303    /// Returns the EtherType field
304    #[inline]
305    pub fn ethertype(&self) -> u16 {
306        self.ethertype.get()
307    }
308
309    /// Returns the Priority Code Point (PCP) - 3 bits
310    #[inline]
311    pub fn pcp(&self) -> u8 {
312        ((self.tci.get() & Self::PCP_MASK) >> Self::PCP_SHIFT) as u8
313    }
314
315    /// Returns the Drop Eligible Indicator (DEI)
316    #[inline]
317    pub fn dei(&self) -> bool {
318        (self.tci.get() & Self::DEI_MASK) != 0
319    }
320
321    /// Returns the Backbone VLAN ID (B-VID) - 12 bits
322    #[inline]
323    pub fn bvid(&self) -> u16 {
324        self.tci.get() & Self::VID_MASK
325    }
326
327    /// Returns the raw TCI field
328    #[inline]
329    pub fn tci(&self) -> u16 {
330        self.tci.get()
331    }
332
333    /// Validates the B-Tag header
334    #[inline]
335    fn is_valid(&self) -> bool {
336        self.ethertype.get() == Self::ETHERTYPE
337    }
338}
339
340impl PacketHeader for PbbBTag {
341    const NAME: &'static str = "PBB-B-Tag";
342
343    type InnerType = EtherProto;
344
345    #[inline]
346    fn inner_type(&self) -> Self::InnerType {
347        // B-Tag is followed by I-Tag
348        EtherProto::VLAN_8021AH
349    }
350
351    #[inline]
352    fn total_len(&self, _buf: &[u8]) -> usize {
353        Self::HEADER_LEN
354    }
355
356    #[inline]
357    fn is_valid(&self) -> bool {
358        PbbBTag::is_valid(self)
359    }
360}
361
362impl HeaderParser for PbbBTag {
363    type Output<'a> = &'a PbbBTag;
364
365    #[inline]
366    fn into_view<'a>(header: &'a Self, _raw_options: &'a [u8]) -> Self::Output<'a> {
367        header
368    }
369}
370
371impl fmt::Display for PbbBTag {
372    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
373        write!(
374            f,
375            "PBB B-Tag B-VID={} PCP={}{}",
376            self.bvid(),
377            self.pcp(),
378            if self.dei() { " DEI" } else { "" }
379        )
380    }
381}
382
383/// Complete PBB header combining B-Tag and I-Tag
384///
385/// This structure represents the full PBB encapsulation header
386/// (excluding the outer Ethernet addresses which are part of the
387/// backbone Ethernet header).
388#[derive(Debug, Clone)]
389pub struct PbbHeader<'a> {
390    /// B-Tag (Backbone VLAN Tag) - optional
391    pub btag: Option<&'a PbbBTag>,
392    /// I-Tag (Instance Service Tag)
393    pub itag: &'a PbbITag,
394}
395
396impl<'a> PbbHeader<'a> {
397    /// Get the I-SID from the I-Tag
398    #[inline]
399    pub fn isid(&self) -> u32 {
400        self.itag.isid()
401    }
402
403    /// Get the B-VID from the B-Tag (if present)
404    #[inline]
405    pub fn bvid(&self) -> Option<u16> {
406        self.btag.map(|b| b.bvid())
407    }
408
409    /// Get the I-PCP from the I-Tag
410    #[inline]
411    pub fn ipcp(&self) -> u8 {
412        self.itag.ipcp()
413    }
414
415    /// Check if DEI is set on the I-Tag
416    #[inline]
417    pub fn dei(&self) -> bool {
418        self.itag.dei()
419    }
420
421    /// Check if UCA is set on the I-Tag
422    #[inline]
423    pub fn uca(&self) -> bool {
424        self.itag.uca()
425    }
426
427    /// Get the total header length
428    #[inline]
429    pub fn header_len(&self) -> usize {
430        let btag_len = if self.btag.is_some() {
431            PbbBTag::HEADER_LEN
432        } else {
433            0
434        };
435        btag_len + PbbITag::HEADER_LEN
436    }
437
438    /// Parse PBB headers from a buffer
439    ///
440    /// This function attempts to parse both B-Tag and I-Tag.
441    /// The buffer should start at the EtherType field after the
442    /// outer (backbone) Ethernet addresses.
443    pub fn parse(buf: &'a [u8]) -> Result<(Self, &'a [u8]), crate::packet::PacketHeaderError> {
444        use crate::packet::PacketHeaderError;
445
446        if buf.len() < 2 {
447            return Err(PacketHeaderError::TooShort("PBB"));
448        }
449
450        let first_ethertype = u16::from_be_bytes([buf[0], buf[1]]);
451
452        // Check if we have a B-Tag first
453        if first_ethertype == PBB_BTAG_ETHERTYPE {
454            // Parse B-Tag
455            if buf.len() < PbbBTag::HEADER_LEN {
456                return Err(PacketHeaderError::TooShort("PBB-B-Tag"));
457            }
458
459            let btag = zerocopy::Ref::<_, PbbBTag>::from_prefix(buf)
460                .map_err(|_| PacketHeaderError::TooShort("PBB-B-Tag"))?;
461            let btag = zerocopy::Ref::into_ref(btag.0);
462
463            if !btag.is_valid() {
464                return Err(PacketHeaderError::Invalid("PBB-B-Tag"));
465            }
466
467            // Parse I-Tag after B-Tag
468            let itag_buf = &buf[PbbBTag::HEADER_LEN..];
469            if itag_buf.len() < PbbITag::HEADER_LEN {
470                return Err(PacketHeaderError::TooShort("PBB-I-Tag"));
471            }
472
473            let itag = zerocopy::Ref::<_, PbbITag>::from_prefix(itag_buf)
474                .map_err(|_| PacketHeaderError::TooShort("PBB-I-Tag"))?;
475            let itag = zerocopy::Ref::into_ref(itag.0);
476
477            if !itag.is_valid() {
478                return Err(PacketHeaderError::Invalid("PBB-I-Tag"));
479            }
480
481            let payload = &itag_buf[PbbITag::HEADER_LEN..];
482
483            Ok((
484                PbbHeader {
485                    btag: Some(btag),
486                    itag,
487                },
488                payload,
489            ))
490        } else if first_ethertype == PBB_ITAG_ETHERTYPE {
491            // Only I-Tag, no B-Tag
492            if buf.len() < PbbITag::HEADER_LEN {
493                return Err(PacketHeaderError::TooShort("PBB-I-Tag"));
494            }
495
496            let itag = zerocopy::Ref::<_, PbbITag>::from_prefix(buf)
497                .map_err(|_| PacketHeaderError::TooShort("PBB-I-Tag"))?;
498            let itag = zerocopy::Ref::into_ref(itag.0);
499
500            if !itag.is_valid() {
501                return Err(PacketHeaderError::Invalid("PBB-I-Tag"));
502            }
503
504            let payload = &buf[PbbITag::HEADER_LEN..];
505
506            Ok((PbbHeader { btag: None, itag }, payload))
507        } else {
508            Err(PacketHeaderError::Invalid("PBB: unknown EtherType"))
509        }
510    }
511}
512
513impl fmt::Display for PbbHeader<'_> {
514    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
515        if let Some(btag) = self.btag {
516            write!(f, "{} -> {}", btag, self.itag)
517        } else {
518            write!(f, "{}", self.itag)
519        }
520    }
521}
522
523/// PBB-TE (Provider Backbone Bridge - Traffic Engineering) support
524///
525/// PBB-TE (IEEE 802.1Qay) extends PBB with traffic engineering capabilities
526/// by using explicit paths rather than spanning tree.
527#[derive(Debug, Clone, Copy, PartialEq, Eq)]
528pub enum PbbTeEspType {
529    /// Working path
530    Working,
531    /// Protection path
532    Protection,
533    /// Unknown ESP type
534    Unknown(u8),
535}
536
537impl From<u8> for PbbTeEspType {
538    fn from(value: u8) -> Self {
539        match value {
540            0 => PbbTeEspType::Working,
541            1 => PbbTeEspType::Protection,
542            v => PbbTeEspType::Unknown(v),
543        }
544    }
545}
546
547impl fmt::Display for PbbTeEspType {
548    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
549        match self {
550            PbbTeEspType::Working => write!(f, "Working"),
551            PbbTeEspType::Protection => write!(f, "Protection"),
552            PbbTeEspType::Unknown(v) => write!(f, "Unknown({})", v),
553        }
554    }
555}
556
557#[cfg(test)]
558mod tests {
559    use super::*;
560    use crate::packet::HeaderParser;
561
562    #[test]
563    fn test_pbb_itag_header_size() {
564        assert_eq!(std::mem::size_of::<PbbITag>(), 6);
565        assert_eq!(PbbITag::HEADER_LEN, 6);
566    }
567
568    #[test]
569    fn test_pbb_btag_header_size() {
570        assert_eq!(std::mem::size_of::<PbbBTag>(), 4);
571        assert_eq!(PbbBTag::HEADER_LEN, 4);
572    }
573
574    #[test]
575    fn test_pbb_itag_basic() {
576        // I-Tag with I-SID = 100
577        let packet = vec![
578            0x88, 0xE7, // EtherType
579            0x00, 0x00, 0x00, 0x64, // I-SID = 100
580            // Payload
581            0xFF, 0xFF,
582        ];
583
584        let (itag, payload) = PbbITag::from_bytes(&packet).unwrap();
585        assert_eq!(itag.ethertype(), PBB_ITAG_ETHERTYPE);
586        assert_eq!(itag.isid(), 100);
587        assert_eq!(itag.ipcp(), 0);
588        assert!(!itag.dei());
589        assert!(!itag.uca());
590        assert_eq!(payload.len(), 2);
591    }
592
593    #[test]
594    fn test_pbb_itag_with_priority() {
595        // I-Tag with I-PCP = 5, DEI = 1, I-SID = 0x123456
596        let packet = vec![
597            0x88, 0xE7, // EtherType
598            0xB0, 0x12, // I-PCP=5, DEI=1, UCA=0, I-SID high
599            0x34, 0x56, // I-SID low
600        ];
601
602        let (itag, _) = PbbITag::from_bytes(&packet).unwrap();
603        assert_eq!(itag.ipcp(), 5);
604        assert!(itag.dei());
605        assert!(!itag.uca());
606        assert_eq!(itag.isid(), 0x123456);
607    }
608
609    #[test]
610    fn test_pbb_itag_with_uca() {
611        // I-Tag with UCA = 1
612        let packet = vec![
613            0x88, 0xE7, // EtherType
614            0x08, 0x00, // UCA=1
615            0x00, 0x01, // I-SID = 1
616        ];
617
618        let (itag, _) = PbbITag::from_bytes(&packet).unwrap();
619        assert!(itag.uca());
620        assert_eq!(itag.isid(), 1);
621    }
622
623    #[test]
624    fn test_pbb_itag_max_isid() {
625        // I-Tag with max I-SID
626        let packet = vec![
627            0x88, 0xE7, // EtherType
628            0x00, 0xFF, // I-SID high
629            0xFF, 0xFF, // I-SID low
630        ];
631
632        let (itag, _) = PbbITag::from_bytes(&packet).unwrap();
633        assert_eq!(itag.isid(), PBB_MAX_ISID);
634    }
635
636    #[test]
637    fn test_pbb_itag_invalid_ethertype() {
638        let packet = vec![
639            0x08, 0x00, // Wrong EtherType (IPv4)
640            0x00, 0x00, 0x00, 0x64,
641        ];
642
643        let result = PbbITag::from_bytes(&packet);
644        assert!(result.is_err());
645    }
646
647    #[test]
648    fn test_pbb_itag_too_short() {
649        let packet = vec![0x88, 0xE7, 0x00, 0x00];
650        let result = PbbITag::from_bytes(&packet);
651        assert!(result.is_err());
652    }
653
654    #[test]
655    fn test_pbb_itag_display() {
656        let packet = vec![0x88, 0xE7, 0xA0, 0x00, 0x00, 0x64];
657
658        let (itag, _) = PbbITag::from_bytes(&packet).unwrap();
659        let display = format!("{}", itag);
660        assert!(display.contains("PBB I-Tag"));
661        assert!(display.contains("I-SID=100"));
662        assert!(display.contains("I-PCP=5"));
663    }
664
665    #[test]
666    fn test_pbb_itag_flags_string() {
667        // With DEI and UCA
668        let packet = vec![0x88, 0xE7, 0x18, 0x00, 0x00, 0x01];
669
670        let (itag, _) = PbbITag::from_bytes(&packet).unwrap();
671        let flags = itag.flags_string();
672        assert!(flags.contains("DEI"));
673        assert!(flags.contains("UCA"));
674
675        // No flags
676        let packet2 = vec![0x88, 0xE7, 0x00, 0x00, 0x00, 0x01];
677        let (itag2, _) = PbbITag::from_bytes(&packet2).unwrap();
678        assert_eq!(itag2.flags_string(), "none");
679    }
680
681    #[test]
682    fn test_pbb_btag_basic() {
683        // B-Tag with B-VID = 100
684        let packet = vec![
685            0x88, 0xA8, // EtherType
686            0x00, 0x64, // B-VID = 100
687            // Payload (would be I-Tag)
688            0x88, 0xE7,
689        ];
690
691        let (btag, payload) = PbbBTag::from_bytes(&packet).unwrap();
692        assert_eq!(btag.ethertype(), PBB_BTAG_ETHERTYPE);
693        assert_eq!(btag.bvid(), 100);
694        assert_eq!(btag.pcp(), 0);
695        assert!(!btag.dei());
696        assert_eq!(payload.len(), 2);
697    }
698
699    #[test]
700    fn test_pbb_btag_with_priority() {
701        // B-Tag with PCP = 7, DEI = 1, B-VID = 4095
702        let packet = vec![
703            0x88, 0xA8, // EtherType
704            0xFF, 0xFF, // PCP=7, DEI=1, B-VID=4095
705        ];
706
707        let (btag, _) = PbbBTag::from_bytes(&packet).unwrap();
708        assert_eq!(btag.pcp(), 7);
709        assert!(btag.dei());
710        assert_eq!(btag.bvid(), 4095);
711    }
712
713    #[test]
714    fn test_pbb_btag_display() {
715        let packet = vec![0x88, 0xA8, 0xA0, 0x64];
716
717        let (btag, _) = PbbBTag::from_bytes(&packet).unwrap();
718        let display = format!("{}", btag);
719        assert!(display.contains("PBB B-Tag"));
720        assert!(display.contains("B-VID=100"));
721        assert!(display.contains("PCP=5"));
722    }
723
724    #[test]
725    fn test_pbb_header_parse_with_btag() {
726        // Full PBB header: B-Tag + I-Tag
727        let packet = vec![
728            // B-Tag
729            0x88, 0xA8, // EtherType
730            0xA0, 0x64, // PCP=5, B-VID=100
731            // I-Tag
732            0x88, 0xE7, // EtherType
733            0x60, 0x00, 0x01, 0x00, // I-PCP=3, I-SID=256
734            // Payload
735            0xFF, 0xFF, 0xFF, 0xFF,
736        ];
737
738        let (header, payload) = PbbHeader::parse(&packet).unwrap();
739        assert!(header.btag.is_some());
740        assert_eq!(header.bvid(), Some(100));
741        assert_eq!(header.isid(), 256);
742        assert_eq!(header.ipcp(), 3);
743        assert_eq!(header.header_len(), 10);
744        assert_eq!(payload.len(), 4);
745    }
746
747    #[test]
748    fn test_pbb_header_parse_itag_only() {
749        // I-Tag only (no B-Tag)
750        let packet = vec![
751            0x88, 0xE7, // EtherType
752            0x00, 0x00, 0x00, 0x64, // I-SID = 100
753            // Payload
754            0xFF, 0xFF,
755        ];
756
757        let (header, payload) = PbbHeader::parse(&packet).unwrap();
758        assert!(header.btag.is_none());
759        assert_eq!(header.bvid(), None);
760        assert_eq!(header.isid(), 100);
761        assert_eq!(header.header_len(), 6);
762        assert_eq!(payload.len(), 2);
763    }
764
765    #[test]
766    fn test_pbb_header_parse_invalid() {
767        // Unknown EtherType
768        let packet = vec![0x08, 0x00, 0x00, 0x00, 0x00, 0x00];
769        let result = PbbHeader::parse(&packet);
770        assert!(result.is_err());
771    }
772
773    #[test]
774    fn test_pbb_header_display() {
775        let packet = vec![
776            0x88, 0xA8, 0x00, 0x64, // B-Tag
777            0x88, 0xE7, 0x00, 0x00, 0x01, 0x00, // I-Tag
778        ];
779
780        let (header, _) = PbbHeader::parse(&packet).unwrap();
781        let display = format!("{}", header);
782        assert!(display.contains("B-Tag"));
783        assert!(display.contains("I-Tag"));
784        assert!(display.contains("->"));
785    }
786
787    #[test]
788    fn test_pbb_ethertype_helpers() {
789        assert!(is_pbb_itag_ethertype(0x88E7));
790        assert!(!is_pbb_itag_ethertype(0x88A8));
791
792        assert!(is_pbb_btag_ethertype(0x88A8));
793        assert!(!is_pbb_btag_ethertype(0x88E7));
794    }
795
796    #[test]
797    fn test_pbb_inner_type() {
798        let packet = vec![0x88, 0xE7, 0x00, 0x00, 0x00, 0x01];
799        let (itag, _) = PbbITag::from_bytes(&packet).unwrap();
800        assert_eq!(itag.inner_type(), EtherProto::TEB);
801
802        let packet2 = vec![0x88, 0xA8, 0x00, 0x01];
803        let (btag, _) = PbbBTag::from_bytes(&packet2).unwrap();
804        assert_eq!(btag.inner_type(), EtherProto::VLAN_8021AH);
805    }
806
807    #[test]
808    fn test_pbb_te_esp_type() {
809        assert_eq!(PbbTeEspType::from(0), PbbTeEspType::Working);
810        assert_eq!(PbbTeEspType::from(1), PbbTeEspType::Protection);
811        assert_eq!(PbbTeEspType::from(2), PbbTeEspType::Unknown(2));
812
813        assert_eq!(format!("{}", PbbTeEspType::Working), "Working");
814        assert_eq!(format!("{}", PbbTeEspType::Protection), "Protection");
815        assert_eq!(format!("{}", PbbTeEspType::Unknown(5)), "Unknown(5)");
816    }
817
818    #[test]
819    fn test_pbb_real_world_scenario() {
820        // Simulate a real PBB frame structure
821        // Outer: B-DA, B-SA, B-Tag, I-Tag, Customer frame
822        let mut packet = Vec::new();
823
824        // B-Tag: PCP=4, B-VID=1000
825        packet.extend_from_slice(&[0x88, 0xA8]);
826        packet.extend_from_slice(&[0x83, 0xE8]); // PCP=4, DEI=0, VID=1000
827
828        // I-Tag: I-PCP=6, I-SID=50000
829        packet.extend_from_slice(&[0x88, 0xE7]);
830        packet.extend_from_slice(&[0xC0, 0x00]); // I-PCP=6, I-SID high
831        packet.extend_from_slice(&[0xC3, 0x50]); // I-SID low (50000 = 0x00C350)
832
833        // Customer Ethernet header (14 bytes)
834        packet.extend_from_slice(&[
835            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Dst MAC
836            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, // Src MAC
837            0x08, 0x00, // EtherType (IPv4)
838        ]);
839
840        let (header, payload) = PbbHeader::parse(&packet).unwrap();
841        assert_eq!(header.bvid(), Some(1000));
842        assert_eq!(header.isid(), 50000);
843        assert_eq!(header.btag.unwrap().pcp(), 4);
844        assert_eq!(header.ipcp(), 6);
845        assert_eq!(payload.len(), 14); // Customer Ethernet header
846    }
847
848    #[test]
849    fn test_pbb_itag_all_priorities() {
850        for pcp in 0..8u8 {
851            // I-PCP is in bits 15-13 of tci_isid_high field
852            let tci_high = (pcp as u16) << 13;
853            let packet = vec![
854                0x88,
855                0xE7,
856                (tci_high >> 8) as u8,
857                (tci_high & 0xFF) as u8,
858                0x00,
859                0x01,
860            ];
861
862            let (itag, _) = PbbITag::from_bytes(&packet).unwrap();
863            assert_eq!(itag.ipcp(), pcp);
864        }
865    }
866
867    #[test]
868    fn test_pbb_btag_all_priorities() {
869        for pcp in 0..8u8 {
870            let tci = (pcp as u16) << 13;
871            let packet = vec![0x88, 0xA8, (tci >> 8) as u8, tci as u8];
872
873            let (btag, _) = PbbBTag::from_bytes(&packet).unwrap();
874            assert_eq!(btag.pcp(), pcp);
875        }
876    }
877}