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}