smoltcp/wire/sixlowpan/
nhc.rs

1//! Implementation of Next Header Compression from [RFC 6282 § 4].
2//!
3//! [RFC 6282 § 4]: https://datatracker.ietf.org/doc/html/rfc6282#section-4
4use super::{Error, NextHeader, Result, DISPATCH_EXT_HEADER, DISPATCH_UDP_HEADER};
5use crate::{
6    phy::ChecksumCapabilities,
7    wire::{ip::checksum, ipv6, udp::Repr as UdpRepr, IpProtocol},
8};
9use byteorder::{ByteOrder, NetworkEndian};
10use ipv6::Address;
11
12macro_rules! get_field {
13    ($name:ident, $mask:expr, $shift:expr) => {
14        fn $name(&self) -> u8 {
15            let data = self.buffer.as_ref();
16            let raw = &data[0];
17            ((raw >> $shift) & $mask) as u8
18        }
19    };
20}
21
22macro_rules! set_field {
23    ($name:ident, $mask:expr, $shift:expr) => {
24        fn $name(&mut self, val: u8) {
25            let data = self.buffer.as_mut();
26            let mut raw = data[0];
27            raw = (raw & !($mask << $shift)) | (val << $shift);
28            data[0] = raw;
29        }
30    };
31}
32
33#[derive(Debug, Clone)]
34#[cfg_attr(feature = "defmt", derive(defmt::Format))]
35/// A read/write wrapper around a 6LoWPAN_NHC Header.
36/// [RFC 6282 § 4.2] specifies the format of the header.
37///
38/// The header has the following format:
39/// ```txt
40///   0   1   2   3   4   5   6   7
41/// +---+---+---+---+---+---+---+---+
42/// | 1 | 1 | 1 | 0 |    EID    |NH |
43/// +---+---+---+---+---+---+---+---+
44/// ```
45///
46/// With:
47/// - EID: the extension header ID
48/// - NH: Next Header
49///
50/// [RFC 6282 § 4.2]: https://datatracker.ietf.org/doc/html/rfc6282#section-4.2
51pub enum NhcPacket {
52    ExtHeader,
53    UdpHeader,
54}
55
56impl NhcPacket {
57    /// Returns the type of the Next Header header.
58    /// This can either be an Extension header or an 6LoWPAN Udp header.
59    ///
60    /// # Errors
61    /// Returns `[Error::Unrecognized]` when neither the Extension Header dispatch or the Udp
62    /// dispatch is recognized.
63    pub fn dispatch(buffer: impl AsRef<[u8]>) -> Result<Self> {
64        let raw = buffer.as_ref();
65        if raw.is_empty() {
66            return Err(Error);
67        }
68
69        if raw[0] >> 4 == DISPATCH_EXT_HEADER {
70            // We have a compressed IPv6 Extension Header.
71            Ok(Self::ExtHeader)
72        } else if raw[0] >> 3 == DISPATCH_UDP_HEADER {
73            // We have a compressed UDP header.
74            Ok(Self::UdpHeader)
75        } else {
76            Err(Error)
77        }
78    }
79}
80
81#[derive(Debug, PartialEq, Eq, Clone, Copy)]
82#[cfg_attr(feature = "defmt", derive(defmt::Format))]
83pub enum ExtHeaderId {
84    HopByHopHeader,
85    RoutingHeader,
86    FragmentHeader,
87    DestinationOptionsHeader,
88    MobilityHeader,
89    Header,
90    Reserved,
91}
92
93impl From<ExtHeaderId> for IpProtocol {
94    fn from(val: ExtHeaderId) -> Self {
95        match val {
96            ExtHeaderId::HopByHopHeader => Self::HopByHop,
97            ExtHeaderId::RoutingHeader => Self::Ipv6Route,
98            ExtHeaderId::FragmentHeader => Self::Ipv6Frag,
99            ExtHeaderId::DestinationOptionsHeader => Self::Ipv6Opts,
100            ExtHeaderId::MobilityHeader => Self::Unknown(0),
101            ExtHeaderId::Header => Self::Unknown(0),
102            ExtHeaderId::Reserved => Self::Unknown(0),
103        }
104    }
105}
106
107/// A read/write wrapper around a 6LoWPAN NHC Extension header.
108#[derive(Debug, Clone)]
109#[cfg_attr(feature = "defmt", derive(defmt::Format))]
110pub struct ExtHeaderPacket<T: AsRef<[u8]>> {
111    buffer: T,
112}
113
114impl<T: AsRef<[u8]>> ExtHeaderPacket<T> {
115    /// Input a raw octet buffer with a 6LoWPAN NHC Extension Header structure.
116    pub const fn new_unchecked(buffer: T) -> Self {
117        ExtHeaderPacket { buffer }
118    }
119
120    /// Shorthand for a combination of [new_unchecked] and [check_len].
121    ///
122    /// [new_unchecked]: #method.new_unchecked
123    /// [check_len]: #method.check_len
124    pub fn new_checked(buffer: T) -> Result<Self> {
125        let packet = Self::new_unchecked(buffer);
126        packet.check_len()?;
127
128        if packet.eid_field() > 7 {
129            return Err(Error);
130        }
131
132        Ok(packet)
133    }
134
135    /// Ensure that no accessor method will panic if called.
136    /// Returns `Err(Error)` if the buffer is too short.
137    pub fn check_len(&self) -> Result<()> {
138        let buffer = self.buffer.as_ref();
139
140        if buffer.is_empty() {
141            return Err(Error);
142        }
143
144        let mut len = 2;
145        len += self.next_header_size();
146
147        if len <= buffer.len() {
148            Ok(())
149        } else {
150            Err(Error)
151        }
152    }
153
154    /// Consumes the frame, returning the underlying buffer.
155    pub fn into_inner(self) -> T {
156        self.buffer
157    }
158
159    get_field!(dispatch_field, 0b1111, 4);
160    get_field!(eid_field, 0b111, 1);
161    get_field!(nh_field, 0b1, 0);
162
163    /// Return the Extension Header ID.
164    pub fn extension_header_id(&self) -> ExtHeaderId {
165        match self.eid_field() {
166            0 => ExtHeaderId::HopByHopHeader,
167            1 => ExtHeaderId::RoutingHeader,
168            2 => ExtHeaderId::FragmentHeader,
169            3 => ExtHeaderId::DestinationOptionsHeader,
170            4 => ExtHeaderId::MobilityHeader,
171            5 | 6 => ExtHeaderId::Reserved,
172            7 => ExtHeaderId::Header,
173            _ => unreachable!(),
174        }
175    }
176
177    /// Return the length field.
178    pub fn length(&self) -> u8 {
179        self.buffer.as_ref()[1 + self.next_header_size()]
180    }
181
182    /// Parse the next header field.
183    pub fn next_header(&self) -> NextHeader {
184        if self.nh_field() == 1 {
185            NextHeader::Compressed
186        } else {
187            // The full 8 bits for Next Header are carried in-line.
188            NextHeader::Uncompressed(IpProtocol::from(self.buffer.as_ref()[1]))
189        }
190    }
191
192    /// Return the size of the Next Header field.
193    fn next_header_size(&self) -> usize {
194        // If nh is set, then the Next Header is compressed using LOWPAN_NHC
195        match self.nh_field() {
196            0 => 1,
197            1 => 0,
198            _ => unreachable!(),
199        }
200    }
201}
202
203impl<'a, T: AsRef<[u8]> + ?Sized> ExtHeaderPacket<&'a T> {
204    /// Return a pointer to the payload.
205    pub fn payload(&self) -> &'a [u8] {
206        let start = 2 + self.next_header_size();
207        let len = self.length() as usize;
208        &self.buffer.as_ref()[start..][..len]
209    }
210}
211
212impl<T: AsRef<[u8]> + AsMut<[u8]>> ExtHeaderPacket<T> {
213    /// Return a mutable pointer to the payload.
214    pub fn payload_mut(&mut self) -> &mut [u8] {
215        let start = 2 + self.next_header_size();
216        let len = self.length() as usize;
217        &mut self.buffer.as_mut()[start..][..len]
218    }
219
220    /// Set the dispatch field to `0b1110`.
221    fn set_dispatch_field(&mut self) {
222        let data = self.buffer.as_mut();
223        data[0] = (data[0] & !(0b1111 << 4)) | (DISPATCH_EXT_HEADER << 4);
224    }
225
226    set_field!(set_eid_field, 0b111, 1);
227    set_field!(set_nh_field, 0b1, 0);
228
229    /// Set the Extension Header ID field.
230    fn set_extension_header_id(&mut self, ext_header_id: ExtHeaderId) {
231        let id = match ext_header_id {
232            ExtHeaderId::HopByHopHeader => 0,
233            ExtHeaderId::RoutingHeader => 1,
234            ExtHeaderId::FragmentHeader => 2,
235            ExtHeaderId::DestinationOptionsHeader => 3,
236            ExtHeaderId::MobilityHeader => 4,
237            ExtHeaderId::Reserved => 5,
238            ExtHeaderId::Header => 7,
239        };
240
241        self.set_eid_field(id);
242    }
243
244    /// Set the Next Header.
245    fn set_next_header(&mut self, next_header: NextHeader) {
246        match next_header {
247            NextHeader::Compressed => self.set_nh_field(0b1),
248            NextHeader::Uncompressed(nh) => {
249                self.set_nh_field(0b0);
250
251                let start = 1;
252                let data = self.buffer.as_mut();
253                data[start] = nh.into();
254            }
255        }
256    }
257
258    /// Set the length.
259    fn set_length(&mut self, length: u8) {
260        let start = 1 + self.next_header_size();
261
262        let data = self.buffer.as_mut();
263        data[start] = length;
264    }
265}
266
267/// A high-level representation of an 6LoWPAN NHC Extension header.
268#[derive(Debug, PartialEq, Eq, Clone, Copy)]
269#[cfg_attr(feature = "defmt", derive(defmt::Format))]
270pub struct ExtHeaderRepr {
271    pub ext_header_id: ExtHeaderId,
272    pub next_header: NextHeader,
273    pub length: u8,
274}
275
276impl ExtHeaderRepr {
277    /// Parse a 6LoWPAN NHC Extension Header packet and return a high-level representation.
278    pub fn parse<T: AsRef<[u8]> + ?Sized>(packet: &ExtHeaderPacket<&T>) -> Result<Self> {
279        // Ensure basic accessors will work.
280        packet.check_len()?;
281
282        if packet.dispatch_field() != DISPATCH_EXT_HEADER {
283            return Err(Error);
284        }
285
286        Ok(Self {
287            ext_header_id: packet.extension_header_id(),
288            next_header: packet.next_header(),
289            length: packet.length(),
290        })
291    }
292
293    /// Return the length of a header that will be emitted from this high-level representation.
294    pub fn buffer_len(&self) -> usize {
295        let mut len = 1; // The minimal header size
296
297        if self.next_header != NextHeader::Compressed {
298            len += 1;
299        }
300
301        len += 1; // The length
302
303        len
304    }
305
306    /// Emit a high-level representation into a 6LoWPAN NHC Extension Header packet.
307    pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut ExtHeaderPacket<T>) {
308        packet.set_dispatch_field();
309        packet.set_extension_header_id(self.ext_header_id);
310        packet.set_next_header(self.next_header);
311        packet.set_length(self.length);
312    }
313}
314
315#[cfg(test)]
316mod tests {
317    use super::*;
318
319    use crate::wire::{Ipv6RoutingHeader, Ipv6RoutingRepr};
320
321    #[cfg(feature = "proto-rpl")]
322    use crate::wire::{
323        Ipv6Option, Ipv6OptionRepr, Ipv6OptionsIterator, RplHopByHopRepr, RplInstanceId,
324    };
325
326    #[cfg(feature = "proto-rpl")]
327    const RPL_HOP_BY_HOP_PACKET: [u8; 9] = [0xe0, 0x3a, 0x06, 0x63, 0x04, 0x00, 0x1e, 0x03, 0x00];
328
329    const ROUTING_SR_PACKET: [u8; 32] = [
330        0xe3, 0x1e, 0x03, 0x03, 0x99, 0x30, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05,
331        0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00,
332        0x00, 0x00,
333    ];
334
335    #[test]
336    #[cfg(feature = "proto-rpl")]
337    fn test_rpl_hop_by_hop_option_deconstruct() {
338        let header = ExtHeaderPacket::new_checked(&RPL_HOP_BY_HOP_PACKET).unwrap();
339        assert_eq!(
340            header.next_header(),
341            NextHeader::Uncompressed(IpProtocol::Icmpv6)
342        );
343        assert_eq!(header.extension_header_id(), ExtHeaderId::HopByHopHeader);
344
345        let options = header.payload();
346        let mut options = Ipv6OptionsIterator::new(options);
347        let rpl_repr = options.next().unwrap();
348        let rpl_repr = rpl_repr.unwrap();
349
350        match rpl_repr {
351            Ipv6OptionRepr::Rpl(rpl) => {
352                assert_eq!(
353                    rpl,
354                    RplHopByHopRepr {
355                        down: false,
356                        rank_error: false,
357                        forwarding_error: false,
358                        instance_id: RplInstanceId::from(0x1e),
359                        sender_rank: 0x0300,
360                    }
361                );
362            }
363            _ => unreachable!(),
364        }
365    }
366
367    #[test]
368    #[cfg(feature = "proto-rpl")]
369    fn test_rpl_hop_by_hop_option_emit() {
370        let repr = Ipv6OptionRepr::Rpl(RplHopByHopRepr {
371            down: false,
372            rank_error: false,
373            forwarding_error: false,
374            instance_id: RplInstanceId::from(0x1e),
375            sender_rank: 0x0300,
376        });
377
378        let ext_hdr = ExtHeaderRepr {
379            ext_header_id: ExtHeaderId::HopByHopHeader,
380            next_header: NextHeader::Uncompressed(IpProtocol::Icmpv6),
381            length: repr.buffer_len() as u8,
382        };
383
384        let mut buffer = vec![0u8; ext_hdr.buffer_len() + repr.buffer_len()];
385        ext_hdr.emit(&mut ExtHeaderPacket::new_unchecked(
386            &mut buffer[..ext_hdr.buffer_len()],
387        ));
388        repr.emit(&mut Ipv6Option::new_unchecked(
389            &mut buffer[ext_hdr.buffer_len()..],
390        ));
391
392        assert_eq!(&buffer[..], RPL_HOP_BY_HOP_PACKET);
393    }
394
395    #[test]
396    fn test_source_routing_deconstruct() {
397        let header = ExtHeaderPacket::new_checked(&ROUTING_SR_PACKET).unwrap();
398        assert_eq!(header.next_header(), NextHeader::Compressed);
399        assert_eq!(header.extension_header_id(), ExtHeaderId::RoutingHeader);
400
401        let routing_hdr = Ipv6RoutingHeader::new_checked(header.payload()).unwrap();
402        let repr = Ipv6RoutingRepr::parse(&routing_hdr).unwrap();
403        assert_eq!(
404            repr,
405            Ipv6RoutingRepr::Rpl {
406                segments_left: 3,
407                cmpr_i: 9,
408                cmpr_e: 9,
409                pad: 3,
410                addresses: &[
411                    0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00,
412                    0x06, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00
413                ],
414            }
415        );
416    }
417
418    #[test]
419    fn test_source_routing_emit() {
420        let routing_hdr = Ipv6RoutingRepr::Rpl {
421            segments_left: 3,
422            cmpr_i: 9,
423            cmpr_e: 9,
424            pad: 3,
425            addresses: &[
426                0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06,
427                0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
428            ],
429        };
430
431        let ext_hdr = ExtHeaderRepr {
432            ext_header_id: ExtHeaderId::RoutingHeader,
433            next_header: NextHeader::Compressed,
434            length: routing_hdr.buffer_len() as u8,
435        };
436
437        let mut buffer = vec![0u8; ext_hdr.buffer_len() + routing_hdr.buffer_len()];
438        ext_hdr.emit(&mut ExtHeaderPacket::new_unchecked(
439            &mut buffer[..ext_hdr.buffer_len()],
440        ));
441        routing_hdr.emit(&mut Ipv6RoutingHeader::new_unchecked(
442            &mut buffer[ext_hdr.buffer_len()..],
443        ));
444
445        assert_eq!(&buffer[..], ROUTING_SR_PACKET);
446    }
447}
448
449/// A read/write wrapper around a 6LoWPAN_NHC UDP frame.
450/// [RFC 6282 § 4.3] specifies the format of the header.
451///
452/// The base header has the following format:
453/// ```txt
454///   0   1   2   3   4   5   6   7
455/// +---+---+---+---+---+---+---+---+
456/// | 1 | 1 | 1 | 1 | 0 | C |   P   |
457/// +---+---+---+---+---+---+---+---+
458/// With:
459/// - C: checksum, specifies if the checksum is elided.
460/// - P: ports, specifies if the ports are elided.
461/// ```
462///
463/// [RFC 6282 § 4.3]: https://datatracker.ietf.org/doc/html/rfc6282#section-4.3
464#[derive(Debug, Clone)]
465#[cfg_attr(feature = "defmt", derive(defmt::Format))]
466pub struct UdpNhcPacket<T: AsRef<[u8]>> {
467    buffer: T,
468}
469
470impl<T: AsRef<[u8]>> UdpNhcPacket<T> {
471    /// Input a raw octet buffer with a LOWPAN_NHC frame structure for UDP.
472    pub const fn new_unchecked(buffer: T) -> Self {
473        Self { buffer }
474    }
475
476    /// Shorthand for a combination of [new_unchecked] and [check_len].
477    ///
478    /// [new_unchecked]: #method.new_unchecked
479    /// [check_len]: #method.check_len
480    pub fn new_checked(buffer: T) -> Result<Self> {
481        let packet = Self::new_unchecked(buffer);
482        packet.check_len()?;
483        Ok(packet)
484    }
485
486    /// Ensure that no accessor method will panic if called.
487    /// Returns `Err(Error::Truncated)` if the buffer is too short.
488    pub fn check_len(&self) -> Result<()> {
489        let buffer = self.buffer.as_ref();
490
491        if buffer.is_empty() {
492            return Err(Error);
493        }
494
495        let index = 1 + self.ports_size() + self.checksum_size();
496        if index > buffer.len() {
497            return Err(Error);
498        }
499
500        Ok(())
501    }
502
503    /// Consumes the frame, returning the underlying buffer.
504    pub fn into_inner(self) -> T {
505        self.buffer
506    }
507
508    get_field!(dispatch_field, 0b11111, 3);
509    get_field!(checksum_field, 0b1, 2);
510    get_field!(ports_field, 0b11, 0);
511
512    /// Returns the index of the start of the next header compressed fields.
513    const fn nhc_fields_start(&self) -> usize {
514        1
515    }
516
517    /// Return the source port number.
518    pub fn src_port(&self) -> u16 {
519        match self.ports_field() {
520            0b00 | 0b01 => {
521                // The full 16 bits are carried in-line.
522                let data = self.buffer.as_ref();
523                let start = self.nhc_fields_start();
524
525                NetworkEndian::read_u16(&data[start..start + 2])
526            }
527            0b10 => {
528                // The first 8 bits are elided.
529                let data = self.buffer.as_ref();
530                let start = self.nhc_fields_start();
531
532                0xf000 + data[start] as u16
533            }
534            0b11 => {
535                // The first 12 bits are elided.
536                let data = self.buffer.as_ref();
537                let start = self.nhc_fields_start();
538
539                0xf0b0 + (data[start] >> 4) as u16
540            }
541            _ => unreachable!(),
542        }
543    }
544
545    /// Return the destination port number.
546    pub fn dst_port(&self) -> u16 {
547        match self.ports_field() {
548            0b00 => {
549                // The full 16 bits are carried in-line.
550                let data = self.buffer.as_ref();
551                let idx = self.nhc_fields_start();
552
553                NetworkEndian::read_u16(&data[idx + 2..idx + 4])
554            }
555            0b01 => {
556                // The first 8 bits are elided.
557                let data = self.buffer.as_ref();
558                let idx = self.nhc_fields_start();
559
560                0xf000 + data[idx] as u16
561            }
562            0b10 => {
563                // The full 16 bits are carried in-line.
564                let data = self.buffer.as_ref();
565                let idx = self.nhc_fields_start();
566
567                NetworkEndian::read_u16(&data[idx + 1..idx + 1 + 2])
568            }
569            0b11 => {
570                // The first 12 bits are elided.
571                let data = self.buffer.as_ref();
572                let start = self.nhc_fields_start();
573
574                0xf0b0 + (data[start] & 0xff) as u16
575            }
576            _ => unreachable!(),
577        }
578    }
579
580    /// Return the checksum.
581    pub fn checksum(&self) -> Option<u16> {
582        if self.checksum_field() == 0b0 {
583            // The first 12 bits are elided.
584            let data = self.buffer.as_ref();
585            let start = self.nhc_fields_start() + self.ports_size();
586            Some(NetworkEndian::read_u16(&data[start..start + 2]))
587        } else {
588            // The checksum is elided and needs to be recomputed on the 6LoWPAN termination point.
589            None
590        }
591    }
592
593    // Return the size of the checksum field.
594    pub(crate) fn checksum_size(&self) -> usize {
595        match self.checksum_field() {
596            0b0 => 2,
597            0b1 => 0,
598            _ => unreachable!(),
599        }
600    }
601
602    /// Returns the total size of both port numbers.
603    pub(crate) fn ports_size(&self) -> usize {
604        match self.ports_field() {
605            0b00 => 4, // 16 bits + 16 bits
606            0b01 => 3, // 16 bits + 8 bits
607            0b10 => 3, // 8 bits + 16 bits
608            0b11 => 1, // 4 bits + 4 bits
609            _ => unreachable!(),
610        }
611    }
612}
613
614impl<'a, T: AsRef<[u8]> + ?Sized> UdpNhcPacket<&'a T> {
615    /// Return a pointer to the payload.
616    pub fn payload(&self) -> &'a [u8] {
617        let start = 1 + self.ports_size() + self.checksum_size();
618        &self.buffer.as_ref()[start..]
619    }
620}
621
622impl<T: AsRef<[u8]> + AsMut<[u8]>> UdpNhcPacket<T> {
623    /// Return a mutable pointer to the payload.
624    pub fn payload_mut(&mut self) -> &mut [u8] {
625        let start = 1 + self.ports_size() + 2; // XXX(thvdveld): we assume we put the checksum inlined.
626        &mut self.buffer.as_mut()[start..]
627    }
628
629    /// Set the dispatch field to `0b11110`.
630    fn set_dispatch_field(&mut self) {
631        let data = self.buffer.as_mut();
632        data[0] = (data[0] & !(0b11111 << 3)) | (DISPATCH_UDP_HEADER << 3);
633    }
634
635    set_field!(set_checksum_field, 0b1, 2);
636    set_field!(set_ports_field, 0b11, 0);
637
638    fn set_ports(&mut self, src_port: u16, dst_port: u16) {
639        let mut idx = 1;
640
641        match (src_port, dst_port) {
642            (0xf0b0..=0xf0bf, 0xf0b0..=0xf0bf) => {
643                // We can compress both the source and destination ports.
644                self.set_ports_field(0b11);
645                let data = self.buffer.as_mut();
646                data[idx] = (((src_port - 0xf0b0) as u8) << 4) & ((dst_port - 0xf0b0) as u8);
647            }
648            (0xf000..=0xf0ff, _) => {
649                // We can compress the source port, but not the destination port.
650                self.set_ports_field(0b10);
651                let data = self.buffer.as_mut();
652                data[idx] = (src_port - 0xf000) as u8;
653                idx += 1;
654
655                NetworkEndian::write_u16(&mut data[idx..idx + 2], dst_port);
656            }
657            (_, 0xf000..=0xf0ff) => {
658                // We can compress the destination port, but not the source port.
659                self.set_ports_field(0b01);
660                let data = self.buffer.as_mut();
661                NetworkEndian::write_u16(&mut data[idx..idx + 2], src_port);
662                idx += 2;
663                data[idx] = (dst_port - 0xf000) as u8;
664            }
665            (_, _) => {
666                // We cannot compress any port.
667                self.set_ports_field(0b00);
668                let data = self.buffer.as_mut();
669                NetworkEndian::write_u16(&mut data[idx..idx + 2], src_port);
670                idx += 2;
671                NetworkEndian::write_u16(&mut data[idx..idx + 2], dst_port);
672            }
673        };
674    }
675
676    fn set_checksum(&mut self, checksum: u16) {
677        self.set_checksum_field(0b0);
678        let idx = 1 + self.ports_size();
679        let data = self.buffer.as_mut();
680        NetworkEndian::write_u16(&mut data[idx..idx + 2], checksum);
681    }
682}
683
684/// A high-level representation of a 6LoWPAN NHC UDP header.
685#[derive(Debug, PartialEq, Eq, Clone, Copy)]
686#[cfg_attr(feature = "defmt", derive(defmt::Format))]
687pub struct UdpNhcRepr(pub UdpRepr);
688
689impl<'a> UdpNhcRepr {
690    /// Parse a 6LoWPAN NHC UDP packet and return a high-level representation.
691    pub fn parse<T: AsRef<[u8]> + ?Sized>(
692        packet: &UdpNhcPacket<&'a T>,
693        src_addr: &ipv6::Address,
694        dst_addr: &ipv6::Address,
695        checksum_caps: &ChecksumCapabilities,
696    ) -> Result<Self> {
697        packet.check_len()?;
698
699        if packet.dispatch_field() != DISPATCH_UDP_HEADER {
700            return Err(Error);
701        }
702
703        if checksum_caps.udp.rx() {
704            let payload_len = packet.payload().len();
705            let chk_sum = !checksum::combine(&[
706                checksum::pseudo_header_v6(
707                    src_addr,
708                    dst_addr,
709                    crate::wire::ip::Protocol::Udp,
710                    payload_len as u32 + 8,
711                ),
712                packet.src_port(),
713                packet.dst_port(),
714                payload_len as u16 + 8,
715                checksum::data(packet.payload()),
716            ]);
717
718            if let Some(checksum) = packet.checksum() {
719                if chk_sum != checksum {
720                    return Err(Error);
721                }
722            }
723        }
724
725        Ok(Self(UdpRepr {
726            src_port: packet.src_port(),
727            dst_port: packet.dst_port(),
728        }))
729    }
730
731    /// Return the length of a packet that will be emitted from this high-level representation.
732    pub fn header_len(&self) -> usize {
733        let mut len = 1; // The minimal header size
734
735        len += 2; // XXX We assume we will add the checksum at the end
736
737        // Check if we can compress the source and destination ports
738        match (self.src_port, self.dst_port) {
739            (0xf0b0..=0xf0bf, 0xf0b0..=0xf0bf) => len + 1,
740            (0xf000..=0xf0ff, _) | (_, 0xf000..=0xf0ff) => len + 3,
741            (_, _) => len + 4,
742        }
743    }
744
745    /// Emit a high-level representation into a LOWPAN_NHC UDP header.
746    pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(
747        &self,
748        packet: &mut UdpNhcPacket<T>,
749        src_addr: &Address,
750        dst_addr: &Address,
751        payload_len: usize,
752        emit_payload: impl FnOnce(&mut [u8]),
753        checksum_caps: &ChecksumCapabilities,
754    ) {
755        packet.set_dispatch_field();
756        packet.set_ports(self.src_port, self.dst_port);
757        emit_payload(packet.payload_mut());
758
759        if checksum_caps.udp.tx() {
760            let chk_sum = !checksum::combine(&[
761                checksum::pseudo_header_v6(
762                    src_addr,
763                    dst_addr,
764                    crate::wire::ip::Protocol::Udp,
765                    payload_len as u32 + 8,
766                ),
767                self.src_port,
768                self.dst_port,
769                payload_len as u16 + 8,
770                checksum::data(packet.payload_mut()),
771            ]);
772
773            packet.set_checksum(chk_sum);
774        }
775    }
776}
777
778impl core::ops::Deref for UdpNhcRepr {
779    type Target = UdpRepr;
780
781    fn deref(&self) -> &Self::Target {
782        &self.0
783    }
784}
785
786impl core::ops::DerefMut for UdpNhcRepr {
787    fn deref_mut(&mut self) -> &mut Self::Target {
788        &mut self.0
789    }
790}
791
792#[cfg(test)]
793mod test {
794    use super::*;
795
796    #[test]
797    fn ext_header_nh_inlined() {
798        let bytes = [0xe2, 0x3a, 0x6, 0x3, 0x0, 0xff, 0x0, 0x0, 0x0];
799
800        let packet = ExtHeaderPacket::new_checked(&bytes[..]).unwrap();
801        assert_eq!(packet.next_header_size(), 1);
802        assert_eq!(packet.length(), 6);
803        assert_eq!(packet.dispatch_field(), DISPATCH_EXT_HEADER);
804        assert_eq!(packet.extension_header_id(), ExtHeaderId::RoutingHeader);
805        assert_eq!(
806            packet.next_header(),
807            NextHeader::Uncompressed(IpProtocol::Icmpv6)
808        );
809
810        assert_eq!(packet.payload(), [0x03, 0x00, 0xff, 0x00, 0x00, 0x00]);
811    }
812
813    #[test]
814    fn ext_header_nh_elided() {
815        let bytes = [0xe3, 0x06, 0x03, 0x00, 0xff, 0x00, 0x00, 0x00];
816
817        let packet = ExtHeaderPacket::new_checked(&bytes[..]).unwrap();
818        assert_eq!(packet.next_header_size(), 0);
819        assert_eq!(packet.length(), 6);
820        assert_eq!(packet.dispatch_field(), DISPATCH_EXT_HEADER);
821        assert_eq!(packet.extension_header_id(), ExtHeaderId::RoutingHeader);
822        assert_eq!(packet.next_header(), NextHeader::Compressed);
823
824        assert_eq!(packet.payload(), [0x03, 0x00, 0xff, 0x00, 0x00, 0x00]);
825    }
826
827    #[test]
828    fn ext_header_emit() {
829        let ext_header = ExtHeaderRepr {
830            ext_header_id: ExtHeaderId::RoutingHeader,
831            next_header: NextHeader::Compressed,
832            length: 6,
833        };
834
835        let len = ext_header.buffer_len();
836        let mut buffer = [0u8; 127];
837        let mut packet = ExtHeaderPacket::new_unchecked(&mut buffer[..len]);
838        ext_header.emit(&mut packet);
839
840        assert_eq!(packet.dispatch_field(), DISPATCH_EXT_HEADER);
841        assert_eq!(packet.next_header(), NextHeader::Compressed);
842        assert_eq!(packet.extension_header_id(), ExtHeaderId::RoutingHeader);
843    }
844
845    #[test]
846    fn udp_nhc_fields() {
847        let bytes = [0xf0, 0x16, 0x2e, 0x22, 0x3d, 0x28, 0xc4];
848
849        let packet = UdpNhcPacket::new_checked(&bytes[..]).unwrap();
850        assert_eq!(packet.dispatch_field(), DISPATCH_UDP_HEADER);
851        assert_eq!(packet.checksum(), Some(0x28c4));
852        assert_eq!(packet.src_port(), 5678);
853        assert_eq!(packet.dst_port(), 8765);
854    }
855
856    #[test]
857    fn udp_emit() {
858        let udp = UdpNhcRepr(UdpRepr {
859            src_port: 0xf0b1,
860            dst_port: 0xf001,
861        });
862
863        let payload = b"Hello World!";
864
865        let src_addr = ipv6::Address::UNSPECIFIED;
866        let dst_addr = ipv6::Address::UNSPECIFIED;
867
868        let len = udp.header_len() + payload.len();
869        let mut buffer = [0u8; 127];
870        let mut packet = UdpNhcPacket::new_unchecked(&mut buffer[..len]);
871        udp.emit(
872            &mut packet,
873            &src_addr,
874            &dst_addr,
875            payload.len(),
876            |buf| buf.copy_from_slice(&payload[..]),
877            &ChecksumCapabilities::default(),
878        );
879
880        assert_eq!(packet.dispatch_field(), DISPATCH_UDP_HEADER);
881        assert_eq!(packet.src_port(), 0xf0b1);
882        assert_eq!(packet.dst_port(), 0xf001);
883        assert_eq!(packet.payload_mut(), b"Hello World!");
884    }
885}