Skip to main content

bacnet_encoding/primitives/
timestamp.rs

1//! Bounded timestamp assembly shared by the public buffer and sink encoders.
2
3use bacnet_types::primitives::BACnetTimeStamp;
4
5use crate::sink::{EncodeSink, SinkFull};
6use crate::tags::{self, TagClass};
7
8use super::{encode_app_date_to_sink, encode_app_time_to_sink, encode_ctx_unsigned_to_sink};
9
10/// Append a complete bare `BACnetTimeStamp` CHOICE in one sink write.
11///
12/// This has the wire layout and accepted inputs of `encode_timestamp_choice`.
13/// It stages at most twelve output bytes before writing to the caller sink.
14/// A known shortage returns the total required and available lengths without
15/// calling `write`; an arbitrary sink refusal is returned unchanged. Earlier
16/// output, the cursor and unused tail remain unchanged on failure.
17///
18/// Date/Time fields retain their existing unvalidated behavior. The existing
19/// Time alternative remains accepted; no semantic validation is added.
20/// The staging bound is not a full-stack bound, and the crate still requires
21/// `std` or `alloc` even when the output sink is bounded.
22pub fn encode_timestamp_choice_to_sink<S: EncodeSink + ?Sized>(
23    sink: &mut S,
24    timestamp: &BACnetTimeStamp,
25) -> Result<usize, SinkFull> {
26    let mut encoded = [0; 12];
27    let used = stage_choice(&mut &mut encoded[..], timestamp)
28        .expect("each timestamp choice fits twelve bytes");
29    write_timestamp(sink, &encoded[..used])
30}
31
32/// Append an outer context frame and its complete timestamp in one sink write.
33///
34/// This preserves `encode_timestamp`'s layout, including both nested
35/// context-2 frames when a DateTime is enclosed by outer tag 2. It stages at
36/// most sixteen output bytes. The [bare encoder's storage contract](encode_timestamp_choice_to_sink)
37/// applies to the entire outer frame, not separately to its markers and value.
38/// The tag number keeps its existing unvalidated input behavior.
39///
40/// ```
41/// use bacnet_encoding::primitives::encode_timestamp_to_sink;
42/// use bacnet_types::primitives::BACnetTimeStamp;
43///
44/// let mut storage = [0xA5; 7];
45/// let mut tail = &mut storage[..];
46/// let stamp = BACnetTimeStamp::SequenceNumber(256);
47/// assert_eq!(encode_timestamp_to_sink(&mut tail, 15, &stamp)?, 7);
48/// assert!(tail.is_empty());
49/// assert_eq!(storage, [0xFE, 0x0F, 0x1A, 0x01, 0x00, 0xFF, 0x0F]);
50/// # Ok::<(), bacnet_encoding::sink::SinkFull>(())
51/// ```
52pub fn encode_timestamp_to_sink<S: EncodeSink + ?Sized>(
53    sink: &mut S,
54    tag_number: u8,
55    timestamp: &BACnetTimeStamp,
56) -> Result<usize, SinkFull> {
57    let mut encoded = [0; 16];
58    let mut tail = &mut encoded[..];
59    let opening = tags::encode_opening_tag_to_sink(&mut tail, tag_number)
60        .expect("an opening marker fits the timestamp staging buffer");
61    let inner = stage_choice(&mut tail, timestamp)
62        .expect("the timestamp choice fits after its opening marker");
63    let closing = tags::encode_closing_tag_to_sink(&mut tail, tag_number)
64        .expect("the closing marker fits after the timestamp choice");
65    let used = opening + inner + closing;
66    write_timestamp(sink, &encoded[..used])
67}
68
69// These writes target only private staging storage, never the caller sink.
70fn stage_choice(tail: &mut &mut [u8], timestamp: &BACnetTimeStamp) -> Result<usize, SinkFull> {
71    let before = tail.len();
72    match timestamp {
73        BACnetTimeStamp::Time(time) => {
74            tags::encode_tag_to_sink(tail, 0, TagClass::Context, 4)?;
75            tail.write(&time.encode())?;
76        }
77        BACnetTimeStamp::SequenceNumber(number) => {
78            encode_ctx_unsigned_to_sink(tail, 1, u64::from(*number))?;
79        }
80        BACnetTimeStamp::DateTime { date, time } => {
81            tags::encode_opening_tag_to_sink(tail, 2)?;
82            encode_app_date_to_sink(tail, date)?;
83            encode_app_time_to_sink(tail, time)?;
84            tags::encode_closing_tag_to_sink(tail, 2)?;
85        }
86    }
87    Ok(before - tail.len())
88}
89
90fn write_timestamp<S: EncodeSink + ?Sized>(
91    sink: &mut S,
92    encoded: &[u8],
93) -> Result<usize, SinkFull> {
94    if let Some(available) = sink.remaining_capacity()
95        && available < encoded.len()
96    {
97        return Err(SinkFull {
98            needed: encoded.len(),
99            available,
100        });
101    }
102    sink.write(encoded)?;
103    Ok(encoded.len())
104}