Skip to main content

bacnet_bip_codec/ipv6/
message.rs

1//! B/IPv6 BVLL frames and messages: one-shot decode, and checked encode.
2
3use super::{Bip6Address, Bip6Vmac, Bvlc6Function, Bvlc6ResultCode, Bvll6Error, BVLC_TYPE_BIP6};
4use crate::{BvllSink, PayloadShape, HEADER_LENGTH, MAX_BVLL_LENGTH};
5
6/// A B/IPv6 BVLL message whose header checks out, before its payload is
7/// read.
8///
9/// [`Bvll6Frame::decode`] checks the BVLC type and the Length field and
10/// accepts any function code, so a receiver can see a function it doesn't
11/// know before [`Bvll6Frame::message`] reads the payload by the function's
12/// layout.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct Bvll6Frame<'a> {
15    /// The function octet.
16    pub function: Bvlc6Function,
17    /// The octets after the 4-octet header, from the first VMAC on.
18    pub payload: &'a [u8],
19}
20
21impl<'a> Bvll6Frame<'a> {
22    /// Check a datagram's BVLL header and split off its payload.
23    ///
24    /// The datagram must be one whole message: at least the 4-octet header,
25    /// BVLC type 0x82, and a Length field equal to the datagram's length,
26    /// since it counts the whole message (Annex U.2). Octets after the
27    /// declared length are refused rather than ignored, so a datagram can't
28    /// pass for a shorter message.
29    ///
30    /// ```
31    /// use bacnet_bip_codec::ipv6::{Bvlc6Function, Bvll6Error, Bvll6Frame};
32    ///
33    /// // A Virtual-Address-Resolution from VMAC 00 04 D2.
34    /// let frame = Bvll6Frame::decode(&[0x82, 0x06, 0x00, 0x07, 0x00, 0x04, 0xD2])?;
35    /// assert_eq!(frame.function, Bvlc6Function::VIRTUAL_ADDRESS_RESOLUTION);
36    /// assert_eq!(frame.payload, [0x00, 0x04, 0xD2]);
37    ///
38    /// // Function 0x0B still has a frame, but no message.
39    /// let frame = Bvll6Frame::decode(&[0x82, 0x0B, 0x00, 0x07, 0x00, 0x04, 0xD2])?;
40    /// assert_eq!(frame.message(), Err(Bvll6Error::UnknownFunction { function: 0x0B }));
41    ///
42    /// // A trailing octet the Length field doesn't count.
43    /// assert_eq!(
44    ///     Bvll6Frame::decode(&[0x82, 0x06, 0x00, 0x07, 0x00, 0x04, 0xD2, 0x00]),
45    ///     Err(Bvll6Error::LengthMismatch { declared: 7, received: 8 }),
46    /// );
47    /// # Ok::<(), Bvll6Error>(())
48    /// ```
49    pub fn decode(datagram: &'a [u8]) -> Result<Self, Bvll6Error> {
50        let [bvlc_type, function, high, low, payload @ ..] = datagram else {
51            return Err(Bvll6Error::TooShort {
52                length: datagram.len(),
53            });
54        };
55        if *bvlc_type != BVLC_TYPE_BIP6 {
56            return Err(Bvll6Error::NotBip6 {
57                bvlc_type: *bvlc_type,
58            });
59        }
60        let declared = usize::from(u16::from_be_bytes([*high, *low]));
61        if declared != datagram.len() {
62            return Err(Bvll6Error::LengthMismatch {
63                declared,
64                received: datagram.len(),
65            });
66        }
67        Ok(Self {
68            function: Bvlc6Function::from_raw(*function),
69            payload,
70        })
71    }
72
73    /// Read the payload by the function's layout (see
74    /// [`Bvlc6Function::payload_shape`]).
75    ///
76    /// Fails with [`Bvll6Error::UnknownFunction`] for a function Annex U
77    /// doesn't define, and with [`Bvll6Error::PayloadLength`] when the
78    /// payload is the wrong length for its function. Field values are not
79    /// judged: any VMAC or address, an undefined result code, a zero
80    /// time-to-live and an empty NPDU all decode.
81    pub fn message(self) -> Result<Bvll6<'a>, Bvll6Error> {
82        let function = self.function;
83        match read(function, self.payload) {
84            Some(message) => Ok(message),
85            None if function.is_defined() => Err(Bvll6Error::PayloadLength {
86                function,
87                length: self.payload.len(),
88            }),
89            None => Err(Bvll6Error::UnknownFunction {
90                function: function.to_raw(),
91            }),
92        }
93    }
94}
95
96/// Reads fields off the front of a payload, in wire order.
97struct Fields<'a>(&'a [u8]);
98
99impl<'a> Fields<'a> {
100    fn take<const N: usize>(&mut self) -> Option<[u8; N]> {
101        let (field, rest) = self.0.split_first_chunk::<N>()?;
102        self.0 = rest;
103        Some(*field)
104    }
105
106    fn vmac(&mut self) -> Option<Bip6Vmac> {
107        self.take().map(Bip6Vmac::new)
108    }
109
110    fn address(&mut self) -> Option<Bip6Address> {
111        self.take().map(Bip6Address::from_octets)
112    }
113
114    fn u16(&mut self) -> Option<u16> {
115        self.take().map(u16::from_be_bytes)
116    }
117
118    /// The rest of the payload: the NPDU.
119    fn npdu(self) -> &'a [u8] {
120        self.0
121    }
122
123    /// `message`, when no octets are left over.
124    fn end<T>(self, message: T) -> Option<T> {
125        self.0.is_empty().then_some(message)
126    }
127}
128
129/// The message `payload` holds for `function`, or `None` when it is the
130/// wrong length or the function is unknown.
131fn read(function: Bvlc6Function, payload: &[u8]) -> Option<Bvll6<'_>> {
132    let mut fields = Fields(payload);
133    let source = fields.vmac()?;
134    let message = match function {
135        Bvlc6Function::BVLC_RESULT => Bvll6::BvlcResult {
136            source,
137            code: Bvlc6ResultCode::from_raw(fields.u16()?),
138        },
139        Bvlc6Function::ORIGINAL_UNICAST_NPDU => {
140            let destination = fields.vmac()?;
141            return Some(Bvll6::OriginalUnicastNpdu {
142                source,
143                destination,
144                npdu: fields.npdu(),
145            });
146        }
147        Bvlc6Function::ORIGINAL_BROADCAST_NPDU => {
148            return Some(Bvll6::OriginalBroadcastNpdu {
149                source,
150                npdu: fields.npdu(),
151            })
152        }
153        Bvlc6Function::ADDRESS_RESOLUTION => Bvll6::AddressResolution {
154            source,
155            target: fields.vmac()?,
156        },
157        Bvlc6Function::FORWARDED_ADDRESS_RESOLUTION => {
158            let target = fields.vmac()?;
159            Bvll6::ForwardedAddressResolution {
160                original_source: source,
161                target,
162                original_address: fields.address()?,
163            }
164        }
165        Bvlc6Function::ADDRESS_RESOLUTION_ACK => Bvll6::AddressResolutionAck {
166            source,
167            destination: fields.vmac()?,
168        },
169        Bvlc6Function::VIRTUAL_ADDRESS_RESOLUTION => Bvll6::VirtualAddressResolution { source },
170        Bvlc6Function::VIRTUAL_ADDRESS_RESOLUTION_ACK => Bvll6::VirtualAddressResolutionAck {
171            source,
172            destination: fields.vmac()?,
173        },
174        Bvlc6Function::FORWARDED_NPDU => {
175            let original_address = fields.address()?;
176            return Some(Bvll6::ForwardedNpdu {
177                original_source: source,
178                original_address,
179                npdu: fields.npdu(),
180            });
181        }
182        Bvlc6Function::REGISTER_FOREIGN_DEVICE => Bvll6::RegisterForeignDevice {
183            source,
184            ttl: fields.u16()?,
185        },
186        Bvlc6Function::DELETE_FOREIGN_DEVICE_TABLE_ENTRY => Bvll6::DeleteForeignDeviceTableEntry {
187            source,
188            entry: fields.address()?,
189        },
190        Bvlc6Function::DISTRIBUTE_BROADCAST_TO_NETWORK => {
191            return Some(Bvll6::DistributeBroadcastToNetwork {
192                original_source: source,
193                npdu: fields.npdu(),
194            })
195        }
196        _ => return None,
197    };
198    fields.end(message)
199}
200
201/// Decode one datagram as a B/IPv6 BVLL message: [`Bvll6Frame::decode`],
202/// then [`Bvll6Frame::message`].
203///
204/// ```
205/// use core::net::Ipv6Addr;
206/// use bacnet_bip_codec::ipv6::{decode, Bip6Address, Bip6Vmac, Bvll6, Bvll6Error};
207///
208/// // A Forwarded-NPDU from VMAC 00 04 D2 at [2001:db8::1]:47808, carrying
209/// // the NPDU 01 00.
210/// let mut wire = vec![0x82, 0x08, 0x00, 0x1B, 0x00, 0x04, 0xD2];
211/// wire.extend_from_slice(&[0x20, 0x01, 0x0D, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
212/// wire.extend_from_slice(&[0xBA, 0xC0, 0x01, 0x00]);
213/// assert_eq!(
214///     decode(&wire)?,
215///     Bvll6::ForwardedNpdu {
216///         original_source: Bip6Vmac::new([0x00, 0x04, 0xD2]),
217///         original_address: Bip6Address::new(
218///             Ipv6Addr::new(0x2001, 0xDB8, 0, 0, 0, 0, 0, 1),
219///             47808,
220///         ),
221///         npdu: &[0x01, 0x00],
222///     },
223/// );
224///
225/// // A Register-Foreign-Device needs exactly a VMAC and a 2-octet
226/// // time-to-live.
227/// assert!(matches!(
228///     decode(&[0x82, 0x09, 0x00, 0x08, 0x00, 0x04, 0xD2, 0x3C]),
229///     Err(Bvll6Error::PayloadLength { length: 4, .. }),
230/// ));
231/// # Ok::<(), Bvll6Error>(())
232/// ```
233pub fn decode(datagram: &[u8]) -> Result<Bvll6<'_>, Bvll6Error> {
234    Bvll6Frame::decode(datagram)?.message()
235}
236
237/// One B/IPv6 BVLL message (Annex U.2), borrowing its NPDU.
238///
239/// [`decode`] produces one from a datagram, and
240/// [`encode_into`](Bvll6::encode_into) or
241/// [`encode_to_sink`](Bvll6::encode_to_sink) writes one. Each variant holds
242/// what its function's format clause lays out after the header, in wire
243/// order.
244#[derive(Debug, Clone, Copy, PartialEq, Eq)]
245pub enum Bvll6<'a> {
246    /// BVLC-Result (U.2.1).
247    BvlcResult {
248        /// The sender's VMAC.
249        source: Bip6Vmac,
250        /// Success, or the NAK of the request that failed.
251        code: Bvlc6ResultCode,
252    },
253    /// Original-Unicast-NPDU (U.2.2).
254    OriginalUnicastNpdu {
255        /// The sender's VMAC.
256        source: Bip6Vmac,
257        /// The recipient's VMAC. A node discards a message whose destination
258        /// VMAC isn't its own (U.3).
259        destination: Bip6Vmac,
260        /// The NPDU.
261        npdu: &'a [u8],
262    },
263    /// Original-Broadcast-NPDU (U.2.3).
264    OriginalBroadcastNpdu {
265        /// The sender's VMAC.
266        source: Bip6Vmac,
267        /// The NPDU.
268        npdu: &'a [u8],
269    },
270    /// Address-Resolution (U.2.4).
271    AddressResolution {
272        /// The sender's VMAC.
273        source: Bip6Vmac,
274        /// The VMAC whose B/IPv6 address the sender wants.
275        target: Bip6Vmac,
276    },
277    /// Forwarded-Address-Resolution (U.2.5).
278    ForwardedAddressResolution {
279        /// The asker's VMAC.
280        original_source: Bip6Vmac,
281        /// The VMAC whose B/IPv6 address it wants.
282        target: Bip6Vmac,
283        /// Where the answer goes: the asker's B/IPv6 address.
284        original_address: Bip6Address,
285    },
286    /// Address-Resolution-ACK (U.2.6).
287    AddressResolutionAck {
288        /// The sender's VMAC: the one that was resolved.
289        source: Bip6Vmac,
290        /// The asker's VMAC.
291        destination: Bip6Vmac,
292    },
293    /// Virtual-Address-Resolution (U.2.7).
294    VirtualAddressResolution {
295        /// The sender's VMAC.
296        source: Bip6Vmac,
297    },
298    /// Virtual-Address-Resolution-ACK (U.2.8).
299    VirtualAddressResolutionAck {
300        /// The sender's VMAC: the answer.
301        source: Bip6Vmac,
302        /// The asker's VMAC.
303        destination: Bip6Vmac,
304    },
305    /// Forwarded-NPDU (U.2.9).
306    ForwardedNpdu {
307        /// The originating node's VMAC, not the forwarding BBMD's.
308        original_source: Bip6Vmac,
309        /// Where the NPDU started: the originating node's B/IPv6 address.
310        original_address: Bip6Address,
311        /// The NPDU.
312        npdu: &'a [u8],
313    },
314    /// Register-Foreign-Device (U.2.10).
315    RegisterForeignDevice {
316        /// The sender's VMAC.
317        source: Bip6Vmac,
318        /// Seconds within which the device will register again.
319        ttl: u16,
320    },
321    /// Delete-Foreign-Device-Table-Entry (U.2.11).
322    DeleteForeignDeviceTableEntry {
323        /// The sender's VMAC.
324        source: Bip6Vmac,
325        /// Which entry to remove: its foreign device's B/IPv6 address.
326        entry: Bip6Address,
327    },
328    /// Distribute-Broadcast-To-Network (U.2.13).
329    DistributeBroadcastToNetwork {
330        /// The sending foreign device's VMAC.
331        original_source: Bip6Vmac,
332        /// The NPDU.
333        npdu: &'a [u8],
334    },
335}
336
337impl<'a> Bvll6<'a> {
338    /// The message's function code.
339    #[must_use]
340    pub const fn function(&self) -> Bvlc6Function {
341        match self {
342            Self::BvlcResult { .. } => Bvlc6Function::BVLC_RESULT,
343            Self::OriginalUnicastNpdu { .. } => Bvlc6Function::ORIGINAL_UNICAST_NPDU,
344            Self::OriginalBroadcastNpdu { .. } => Bvlc6Function::ORIGINAL_BROADCAST_NPDU,
345            Self::AddressResolution { .. } => Bvlc6Function::ADDRESS_RESOLUTION,
346            Self::ForwardedAddressResolution { .. } => Bvlc6Function::FORWARDED_ADDRESS_RESOLUTION,
347            Self::AddressResolutionAck { .. } => Bvlc6Function::ADDRESS_RESOLUTION_ACK,
348            Self::VirtualAddressResolution { .. } => Bvlc6Function::VIRTUAL_ADDRESS_RESOLUTION,
349            Self::VirtualAddressResolutionAck { .. } => {
350                Bvlc6Function::VIRTUAL_ADDRESS_RESOLUTION_ACK
351            }
352            Self::ForwardedNpdu { .. } => Bvlc6Function::FORWARDED_NPDU,
353            Self::RegisterForeignDevice { .. } => Bvlc6Function::REGISTER_FOREIGN_DEVICE,
354            Self::DeleteForeignDeviceTableEntry { .. } => {
355                Bvlc6Function::DELETE_FOREIGN_DEVICE_TABLE_ENTRY
356            }
357            Self::DistributeBroadcastToNetwork { .. } => {
358                Bvlc6Function::DISTRIBUTE_BROADCAST_TO_NETWORK
359            }
360        }
361    }
362
363    /// The VMAC every message starts with: the sender's, except in the two
364    /// messages a BBMD relays (Forwarded-NPDU and
365    /// Forwarded-Address-Resolution), where it is the originating node's.
366    /// Distribute-Broadcast-To-Network calls its VMAC the original source,
367    /// but the foreign device that sends one is that source.
368    #[must_use]
369    pub const fn source_vmac(&self) -> Bip6Vmac {
370        match *self {
371            Self::BvlcResult { source, .. }
372            | Self::OriginalUnicastNpdu { source, .. }
373            | Self::OriginalBroadcastNpdu { source, .. }
374            | Self::AddressResolution { source, .. }
375            | Self::AddressResolutionAck { source, .. }
376            | Self::VirtualAddressResolution { source }
377            | Self::VirtualAddressResolutionAck { source, .. }
378            | Self::RegisterForeignDevice { source, .. }
379            | Self::DeleteForeignDeviceTableEntry { source, .. }
380            | Self::ForwardedAddressResolution {
381                original_source: source,
382                ..
383            }
384            | Self::ForwardedNpdu {
385                original_source: source,
386                ..
387            }
388            | Self::DistributeBroadcastToNetwork {
389                original_source: source,
390                ..
391            } => source,
392        }
393    }
394
395    /// The recipient's VMAC, for the three messages that name one:
396    /// Original-Unicast-NPDU and the two ACKs. A node discards those when it
397    /// isn't theirs (U.3).
398    #[must_use]
399    pub const fn destination_vmac(&self) -> Option<Bip6Vmac> {
400        match *self {
401            Self::OriginalUnicastNpdu { destination, .. }
402            | Self::AddressResolutionAck { destination, .. }
403            | Self::VirtualAddressResolutionAck { destination, .. } => Some(destination),
404            _ => None,
405        }
406    }
407
408    /// The NPDU, for the four functions that carry one.
409    #[must_use]
410    pub const fn npdu(&self) -> Option<&'a [u8]> {
411        match *self {
412            Self::OriginalUnicastNpdu { npdu, .. }
413            | Self::OriginalBroadcastNpdu { npdu, .. }
414            | Self::ForwardedNpdu { npdu, .. }
415            | Self::DistributeBroadcastToNetwork { npdu, .. } => Some(npdu),
416            _ => None,
417        }
418    }
419
420    /// The originating node's B/IPv6 address, for the two messages a BBMD
421    /// relays with one: Forwarded-NPDU and Forwarded-Address-Resolution.
422    #[must_use]
423    pub const fn origin(&self) -> Option<Bip6Address> {
424        match *self {
425            Self::ForwardedNpdu {
426                original_address, ..
427            }
428            | Self::ForwardedAddressResolution {
429                original_address, ..
430            } => Some(original_address),
431            _ => None,
432        }
433    }
434
435    /// Octets after the header: the fixed fields
436    /// [`Bvlc6Function::payload_shape`] gives the function, then the NPDU of
437    /// the four functions that carry one.
438    const fn payload_length(&self) -> usize {
439        let fixed = match self.function().payload_shape() {
440            Some(PayloadShape::Fixed(length) | PayloadShape::Npdu { prefix: length }) => length,
441            // Unreachable: every variant's function is one Annex U defines,
442            // and no B/IPv6 message carries a table. The golden vectors
443            // check every variant's encoded length.
444            Some(PayloadShape::Table { .. }) | None => 0,
445        };
446        match self.npdu() {
447            Some(npdu) => fixed + npdu.len(),
448            None => fixed,
449        }
450    }
451
452    /// Check the message and return its wire length, header included.
453    ///
454    /// Encoding refuses two things: a message too long for the 16-bit Length
455    /// field ([`Bvll6Error::TooLong`]), and a BVLC-Result whose code Annex
456    /// U.2.1.1 doesn't define ([`Bvll6Error::UndefinedResultCode`]). Every
457    /// other shape the standard forbids can't be built: each variant holds
458    /// exactly its function's fields.
459    pub fn encoded_len(&self) -> Result<usize, Bvll6Error> {
460        if let Self::BvlcResult { code, .. } = self {
461            if !code.is_defined() {
462                return Err(Bvll6Error::UndefinedResultCode {
463                    code: code.to_raw(),
464                });
465            }
466        }
467        let length = HEADER_LENGTH + self.payload_length();
468        if length > MAX_BVLL_LENGTH {
469            return Err(Bvll6Error::TooLong { length });
470        }
471        Ok(length)
472    }
473
474    /// Encode the message at the start of `output` and return its wire
475    /// length. Nothing is written unless it passes the checks of
476    /// [`Bvll6::encoded_len`] and fits.
477    ///
478    /// ```
479    /// use bacnet_bip_codec::ipv6::{Bip6Vmac, Bvlc6ResultCode, Bvll6, Bvll6Error};
480    ///
481    /// let source = Bip6Vmac::new([0x00, 0x04, 0xD2]);
482    /// let mut wire = [0u8; 9];
483    /// let nak = Bvll6::BvlcResult {
484    ///     source,
485    ///     code: Bvlc6ResultCode::REGISTER_FOREIGN_DEVICE_NAK,
486    /// };
487    /// assert_eq!(nak.encode_into(&mut wire)?, 9);
488    /// assert_eq!(wire, [0x82, 0x00, 0x00, 0x09, 0x00, 0x04, 0xD2, 0x00, 0x90]);
489    ///
490    /// // 0x0010 is a B/IP result code, not one Annex U defines.
491    /// let undefined = Bvll6::BvlcResult { source, code: Bvlc6ResultCode::from_raw(0x0010) };
492    /// assert_eq!(
493    ///     undefined.encode_into(&mut wire),
494    ///     Err(Bvll6Error::UndefinedResultCode { code: 0x0010 }),
495    /// );
496    /// # Ok::<(), Bvll6Error>(())
497    /// ```
498    pub fn encode_into(&self, output: &mut [u8]) -> Result<usize, Bvll6Error> {
499        let mut sink = output;
500        self.encode_to_sink(&mut sink)
501    }
502
503    /// Encode the message into `sink` and return its wire length.
504    ///
505    /// The checks are those of [`Bvll6::encoded_len`]. When the sink reports
506    /// a fixed capacity, a message that does not fit fails before anything
507    /// is written. A growable sink is written directly, so if one of its
508    /// writes fails, the octets written before it stay in the sink; see
509    /// [`BvllSink`].
510    pub fn encode_to_sink<S: BvllSink + ?Sized>(&self, sink: &mut S) -> Result<usize, Bvll6Error> {
511        let length = self.encoded_len()?;
512        if let Some(available) = sink.remaining_capacity() {
513            if available < length {
514                return Err(Bvll6Error::BufferExhausted {
515                    needed: length,
516                    available,
517                });
518            }
519        }
520        // `encoded_len` capped the length at 0xFFFF.
521        let [high, low] = (length as u16).to_be_bytes();
522        let [v0, v1, v2] = self.source_vmac().octets();
523        sink.write(&[
524            BVLC_TYPE_BIP6,
525            self.function().to_raw(),
526            high,
527            low,
528            v0,
529            v1,
530            v2,
531        ])?;
532        match *self {
533            Self::VirtualAddressResolution { .. } => {}
534            Self::BvlcResult { code, .. } => sink.write(&code.to_raw().to_be_bytes())?,
535            Self::RegisterForeignDevice { ttl, .. } => sink.write(&ttl.to_be_bytes())?,
536            Self::AddressResolution { target: second, .. }
537            | Self::AddressResolutionAck {
538                destination: second,
539                ..
540            }
541            | Self::VirtualAddressResolutionAck {
542                destination: second,
543                ..
544            } => sink.write(&second.octets())?,
545            Self::ForwardedAddressResolution {
546                target,
547                original_address,
548                ..
549            } => {
550                sink.write(&target.octets())?;
551                sink.write(&original_address.to_octets())?;
552            }
553            Self::DeleteForeignDeviceTableEntry { entry, .. } => sink.write(&entry.to_octets())?,
554            Self::OriginalUnicastNpdu {
555                destination, npdu, ..
556            } => {
557                sink.write(&destination.octets())?;
558                sink.write(npdu)?;
559            }
560            Self::ForwardedNpdu {
561                original_address,
562                npdu,
563                ..
564            } => {
565                sink.write(&original_address.to_octets())?;
566                sink.write(npdu)?;
567            }
568            Self::OriginalBroadcastNpdu { npdu, .. }
569            | Self::DistributeBroadcastToNetwork { npdu, .. } => sink.write(npdu)?,
570        }
571        Ok(length)
572    }
573}