1use bacnet_encoding::primitives;
27use bacnet_encoding::tags;
28use bacnet_types::constructed::{
29 BACnetCalendarEntry, BACnetDateRange, BACnetSpecialEvent, BACnetTimeValue, BACnetWeekNDay,
30 SpecialEventPeriod,
31};
32use bacnet_types::error::Error;
33use bacnet_types::primitives::{Date, ObjectIdentifier, Time};
34use bytes::BytesMut;
35
36use crate::common::MAX_DECODED_ITEMS;
37
38pub fn encode_time_value(buf: &mut BytesMut, tv: &BACnetTimeValue) {
53 primitives::encode_app_time(buf, &tv.time);
54 buf.extend_from_slice(&tv.value);
55}
56
57pub fn decode_time_value(data: &[u8], offset: usize) -> Result<(BACnetTimeValue, usize), Error> {
60 let (tag, pos) = tags::decode_tag(data, offset)?;
62 if tag.class != tags::TagClass::Application
63 || tag.number != tags::app_tag::TIME
64 || tag.length != 4
65 {
66 return Err(Error::decoding(
67 offset,
68 "TimeValue: expected application-tagged Time (4 bytes)",
69 ));
70 }
71 let end = pos
72 .checked_add(4)
73 .ok_or_else(|| Error::decoding(pos, "TimeValue: time length overflow"))?;
74 if end > data.len() {
75 return Err(Error::buffer_too_short(end, data.len()));
76 }
77 let time = Time::decode(&data[pos..end])?;
78
79 let value_start = end;
82 let (val_tag, val_pos) = tags::decode_tag(data, value_start)?;
83 if val_tag.class != tags::TagClass::Application {
84 return Err(Error::decoding(
85 value_start,
86 format!(
87 "TimeValue: expected application tag for value, got context tag {}",
88 val_tag.number
89 ),
90 ));
91 }
92 if val_tag.is_opening || val_tag.is_closing {
93 return Err(Error::decoding(
94 value_start,
95 "TimeValue: unexpected opening/closing tag in value",
96 ));
97 }
98 let content_len = if val_tag.number == tags::app_tag::BOOLEAN {
102 0
103 } else {
104 val_tag.length as usize
105 };
106 let value_end = val_pos
107 .checked_add(content_len)
108 .ok_or_else(|| Error::decoding(val_pos, "TimeValue: value length overflow"))?;
109 if value_end > data.len() {
110 return Err(Error::buffer_too_short(value_end, data.len()));
111 }
112
113 Ok((
114 BACnetTimeValue {
115 time,
116 value: data[value_start..value_end].to_vec(),
117 },
118 value_end,
119 ))
120}
121
122pub fn encode_calendar_entry(buf: &mut BytesMut, e: &BACnetCalendarEntry) {
138 match e {
139 BACnetCalendarEntry::Date(d) => {
140 primitives::encode_ctx_date(buf, 0, d);
141 }
142 BACnetCalendarEntry::DateRange(dr) => {
143 tags::encode_opening_tag(buf, 1);
144 primitives::encode_app_date(buf, &dr.start_date);
145 primitives::encode_app_date(buf, &dr.end_date);
146 tags::encode_closing_tag(buf, 1);
147 }
148 BACnetCalendarEntry::WeekNDay(w) => {
149 primitives::encode_ctx_octet_string(buf, 2, &w.encode());
150 }
151 }
152}
153
154pub fn decode_calendar_entry(
156 data: &[u8],
157 offset: usize,
158) -> Result<(BACnetCalendarEntry, usize), Error> {
159 let (tag, pos) = tags::decode_tag(data, offset)?;
160 if tag.class != tags::TagClass::Context {
161 return Err(Error::decoding(
162 offset,
163 "CalendarEntry: expected context tag",
164 ));
165 }
166
167 match tag.number {
168 0 => {
169 if tag.length != 4 {
171 return Err(Error::decoding(
172 offset,
173 format!("CalendarEntry::Date: expected length 4, got {}", tag.length),
174 ));
175 }
176 let end = pos + 4;
177 if end > data.len() {
178 return Err(Error::buffer_too_short(end, data.len()));
179 }
180 let date = Date::decode(&data[pos..end])?;
181 Ok((BACnetCalendarEntry::Date(date), end))
182 }
183 1 => {
184 if !tag.is_opening {
186 return Err(Error::decoding(
187 offset,
188 "CalendarEntry::DateRange: expected [1] opening tag",
189 ));
190 }
191 let (start_date, p1) = read_app_date(data, pos)?;
192 let (end_date, p2) = read_app_date(data, p1)?;
193 let (close, p3) = tags::decode_tag(data, p2)?;
194 if !close.is_closing_tag(1) {
195 return Err(Error::decoding(
196 p2,
197 "CalendarEntry::DateRange: expected [1] closing tag",
198 ));
199 }
200 Ok((
201 BACnetCalendarEntry::DateRange(BACnetDateRange {
202 start_date,
203 end_date,
204 }),
205 p3,
206 ))
207 }
208 2 => {
209 if tag.length != 3 {
211 return Err(Error::decoding(
212 offset,
213 format!(
214 "CalendarEntry::WeekNDay: expected length 3, got {}",
215 tag.length
216 ),
217 ));
218 }
219 let end = pos + 3;
220 if end > data.len() {
221 return Err(Error::buffer_too_short(end, data.len()));
222 }
223 let w = BACnetWeekNDay::decode(&data[pos..end])?;
224 Ok((BACnetCalendarEntry::WeekNDay(w), end))
225 }
226 other => Err(Error::decoding(
227 offset,
228 format!("CalendarEntry: unknown CHOICE tag [{other}]"),
229 )),
230 }
231}
232
233pub fn encode_special_event_period(buf: &mut BytesMut, p: &SpecialEventPeriod) {
249 match p {
250 SpecialEventPeriod::CalendarEntry(e) => {
251 tags::encode_opening_tag(buf, 0);
252 encode_calendar_entry(buf, e);
253 tags::encode_closing_tag(buf, 0);
254 }
255 SpecialEventPeriod::CalendarReference(oid) => {
256 primitives::encode_ctx_object_id(buf, 1, oid);
257 }
258 }
259}
260
261pub fn decode_special_event_period(
263 data: &[u8],
264 offset: usize,
265) -> Result<(SpecialEventPeriod, usize), Error> {
266 let (tag, pos) = tags::decode_tag(data, offset)?;
267 if tag.class != tags::TagClass::Context {
268 return Err(Error::decoding(offset, "Period: expected context tag"));
269 }
270
271 match tag.number {
272 0 => {
273 if !tag.is_opening {
274 return Err(Error::decoding(
275 offset,
276 "Period::CalendarEntry: expected [0] opening tag",
277 ));
278 }
279 let (entry, p1) = decode_calendar_entry(data, pos)?;
280 let (close, p2) = tags::decode_tag(data, p1)?;
281 if !close.is_closing_tag(0) {
282 return Err(Error::decoding(
283 p1,
284 "Period::CalendarEntry: expected [0] closing tag",
285 ));
286 }
287 Ok((SpecialEventPeriod::CalendarEntry(entry), p2))
288 }
289 1 => {
290 if tag.length != 4 {
291 return Err(Error::decoding(
292 offset,
293 format!(
294 "Period::CalendarReference: expected length 4, got {}",
295 tag.length
296 ),
297 ));
298 }
299 let end = pos + 4;
300 if end > data.len() {
301 return Err(Error::buffer_too_short(end, data.len()));
302 }
303 let oid = ObjectIdentifier::decode(&data[pos..end])?;
304 Ok((SpecialEventPeriod::CalendarReference(oid), end))
305 }
306 other => Err(Error::decoding(
307 offset,
308 format!("Period: unknown CHOICE tag [{other}]"),
309 )),
310 }
311}
312
313pub fn encode_special_event(buf: &mut BytesMut, e: &BACnetSpecialEvent) {
325 encode_special_event_period(buf, &e.period);
326
327 tags::encode_opening_tag(buf, 2);
329 for tv in &e.list_of_time_values {
330 encode_time_value(buf, tv);
331 }
332 tags::encode_closing_tag(buf, 2);
333
334 primitives::encode_ctx_unsigned(buf, 3, e.event_priority as u64);
336}
337
338pub fn decode_special_event(
340 data: &[u8],
341 offset: usize,
342) -> Result<(BACnetSpecialEvent, usize), Error> {
343 let (period, mut pos) = decode_special_event_period(data, offset)?;
344
345 let (open, p1) = tags::decode_tag(data, pos)?;
347 if !open.is_opening_tag(2) {
348 return Err(Error::decoding(
349 pos,
350 "SpecialEvent: expected [2] opening tag for list-of-time-values",
351 ));
352 }
353 pos = p1;
354
355 let mut list_of_time_values = Vec::new();
356 loop {
357 let (peek, _) = tags::decode_tag(data, pos)?;
358 if peek.is_closing_tag(2) {
359 break;
360 }
361 if list_of_time_values.len() >= MAX_DECODED_ITEMS {
362 return Err(Error::decoding(
363 pos,
364 "SpecialEvent: list-of-time-values exceeds MAX_DECODED_ITEMS",
365 ));
366 }
367 let (tv, next) = decode_time_value(data, pos)?;
368 list_of_time_values.push(tv);
369 pos = next;
370 }
371 let (_close, p2) = tags::decode_tag(data, pos)?;
373 pos = p2;
374
375 let (prio_tag, p3) = tags::decode_tag(data, pos)?;
377 if !prio_tag.is_context(3) {
378 return Err(Error::decoding(
379 pos,
380 "SpecialEvent: expected [3] event-priority",
381 ));
382 }
383 let prio_end = p3 + prio_tag.length as usize;
384 if prio_end > data.len() {
385 return Err(Error::buffer_too_short(prio_end, data.len()));
386 }
387 let prio = primitives::decode_unsigned(&data[p3..prio_end])?;
388 let event_priority = validate_priority(prio, pos)?;
389
390 Ok((
391 BACnetSpecialEvent {
392 period,
393 list_of_time_values,
394 event_priority,
395 },
396 prio_end,
397 ))
398}
399
400fn validate_priority(raw: u64, offset: usize) -> Result<u8, Error> {
408 if !(1..=16).contains(&raw) {
409 return Err(Error::decoding(
410 offset,
411 format!("SpecialEvent: event-priority {raw} outside 1..=16"),
412 ));
413 }
414 Ok(raw as u8)
415}
416
417pub fn encode_weekly_schedule(buf: &mut BytesMut, days: &[Vec<BACnetTimeValue>; 7]) {
434 for day in days {
435 tags::encode_opening_tag(buf, 0);
436 for tv in day {
437 encode_time_value(buf, tv);
438 }
439 tags::encode_closing_tag(buf, 0);
440 }
441}
442
443pub fn decode_weekly_schedule(data: &[u8]) -> Result<[Vec<BACnetTimeValue>; 7], Error> {
449 let mut days: [Vec<BACnetTimeValue>; 7] = Default::default();
450 let mut pos = 0usize;
451 for (i, day) in days.iter_mut().enumerate() {
452 let (open, p1) = tags::decode_tag(data, pos)
453 .map_err(|e| Error::decoding(pos, format!("WeeklySchedule day {i}: {e}")))?;
454 if !open.is_opening_tag(0) {
455 return Err(Error::decoding(
456 pos,
457 format!("WeeklySchedule day {i}: expected [0] opening tag"),
458 ));
459 }
460 pos = p1;
461
462 loop {
463 let (peek, _) = tags::decode_tag(data, pos)
464 .map_err(|e| Error::decoding(pos, format!("WeeklySchedule day {i}: {e}")))?;
465 if peek.is_closing_tag(0) {
466 break;
467 }
468 if day.len() >= MAX_DECODED_ITEMS {
469 return Err(Error::decoding(
470 pos,
471 format!("WeeklySchedule day {i}: exceeds MAX_DECODED_ITEMS"),
472 ));
473 }
474 let (tv, next) = decode_time_value(data, pos)
475 .map_err(|e| Error::decoding(pos, format!("WeeklySchedule day {i}: {e}")))?;
476 day.push(tv);
477 pos = next;
478 }
479 let (_close, p2) = tags::decode_tag(data, pos)
480 .map_err(|e| Error::decoding(pos, format!("WeeklySchedule day {i}: {e}")))?;
481 pos = p2;
482 }
483
484 if pos != data.len() {
485 return Err(Error::decoding(
486 pos,
487 format!(
488 "WeeklySchedule: {} trailing byte(s) after 7 daily schedules",
489 data.len() - pos
490 ),
491 ));
492 }
493 Ok(days)
494}
495
496pub fn encode_exception_schedule(buf: &mut BytesMut, events: &[BACnetSpecialEvent]) {
502 for e in events {
503 encode_special_event(buf, e);
504 }
505}
506
507pub fn decode_exception_schedule(data: &[u8]) -> Result<Vec<BACnetSpecialEvent>, Error> {
510 let mut events = Vec::new();
511 let mut pos = 0usize;
512 while pos < data.len() {
513 if events.len() >= MAX_DECODED_ITEMS {
514 return Err(Error::decoding(
515 pos,
516 "ExceptionSchedule: exceeds MAX_DECODED_ITEMS",
517 ));
518 }
519 let (event, next) = decode_special_event(data, pos).map_err(|e| {
520 Error::decoding(
521 pos,
522 format!("ExceptionSchedule entry {}: {e}", events.len()),
523 )
524 })?;
525 events.push(event);
526 pos = next;
527 }
528 Ok(events)
529}
530
531fn read_app_date(data: &[u8], offset: usize) -> Result<(Date, usize), Error> {
536 let (tag, pos) = tags::decode_tag(data, offset)?;
537 if tag.class != tags::TagClass::Application
538 || tag.number != tags::app_tag::DATE
539 || tag.length != 4
540 {
541 return Err(Error::decoding(
542 offset,
543 "expected application-tagged Date (4 bytes)",
544 ));
545 }
546 let end = pos + 4;
547 if end > data.len() {
548 return Err(Error::buffer_too_short(end, data.len()));
549 }
550 Ok((Date::decode(&data[pos..end])?, end))
551}
552
553#[cfg(test)]
554mod tests {
555 use super::*;
556 use bacnet_types::enums::ObjectType;
557
558 fn d(year_offset: u8, month: u8, day: u8, dow: u8) -> Date {
559 Date {
560 year: year_offset,
561 month,
562 day,
563 day_of_week: dow,
564 }
565 }
566
567 fn t(h: u8, m: u8, s: u8) -> Time {
568 Time {
569 hour: h,
570 minute: m,
571 second: s,
572 hundredths: 0,
573 }
574 }
575
576 fn tv_real(hour: u8, minute: u8, value: f32) -> BACnetTimeValue {
578 let mut buf = BytesMut::new();
579 primitives::encode_app_real(&mut buf, value);
580 BACnetTimeValue {
581 time: t(hour, minute, 0),
582 value: buf.to_vec(),
583 }
584 }
585
586 fn tv_null(hour: u8, minute: u8) -> BACnetTimeValue {
588 let mut buf = BytesMut::new();
589 primitives::encode_app_null(&mut buf);
590 BACnetTimeValue {
591 time: t(hour, minute, 0),
592 value: buf.to_vec(),
593 }
594 }
595
596 #[test]
599 fn time_value_round_trip_real() {
600 let tv = tv_real(8, 30, 72.5);
601 let mut buf = BytesMut::new();
602 encode_time_value(&mut buf, &tv);
603 let (decoded, end) = decode_time_value(&buf, 0).unwrap();
604 assert_eq!(decoded, tv);
605 assert_eq!(end, buf.len());
606 }
607
608 #[test]
609 fn time_value_round_trip_null() {
610 let tv = tv_null(17, 0);
611 let mut buf = BytesMut::new();
612 encode_time_value(&mut buf, &tv);
613 let (decoded, end) = decode_time_value(&buf, 0).unwrap();
614 assert_eq!(decoded, tv);
615 assert_eq!(end, buf.len());
616 }
617
618 #[test]
619 fn time_value_rejects_context_tagged_value() {
620 let mut buf = BytesMut::new();
621 primitives::encode_app_time(&mut buf, &t(8, 0, 0));
622 primitives::encode_ctx_unsigned(&mut buf, 0, 1);
623 let err = decode_time_value(&buf, 0).unwrap_err();
624 assert!(format!("{err}").contains("expected application tag"));
625 }
626
627 #[test]
630 fn calendar_entry_date_round_trip() {
631 let e = BACnetCalendarEntry::Date(d(124, 7, 4, 4));
632 let mut buf = BytesMut::new();
633 encode_calendar_entry(&mut buf, &e);
634 let (decoded, end) = decode_calendar_entry(&buf, 0).unwrap();
635 assert_eq!(decoded, e);
636 assert_eq!(end, buf.len());
637 }
638
639 #[test]
640 fn calendar_entry_date_range_round_trip() {
641 let e = BACnetCalendarEntry::DateRange(BACnetDateRange {
642 start_date: d(124, 1, 1, 1),
643 end_date: d(124, 12, 31, 2),
644 });
645 let mut buf = BytesMut::new();
646 encode_calendar_entry(&mut buf, &e);
647 let (decoded, end) = decode_calendar_entry(&buf, 0).unwrap();
648 assert_eq!(decoded, e);
649 assert_eq!(end, buf.len());
650 }
651
652 #[test]
653 fn calendar_entry_week_n_day_round_trip() {
654 let e = BACnetCalendarEntry::WeekNDay(BACnetWeekNDay {
655 month: 0xFF,
656 week_of_month: 1,
657 day_of_week: 1,
658 });
659 let mut buf = BytesMut::new();
660 encode_calendar_entry(&mut buf, &e);
661 let (decoded, end) = decode_calendar_entry(&buf, 0).unwrap();
662 assert_eq!(decoded, e);
663 assert_eq!(end, buf.len());
664 }
665
666 #[test]
669 fn period_calendar_entry_round_trip() {
670 let p = SpecialEventPeriod::CalendarEntry(BACnetCalendarEntry::Date(d(124, 12, 25, 3)));
671 let mut buf = BytesMut::new();
672 encode_special_event_period(&mut buf, &p);
673 let (decoded, end) = decode_special_event_period(&buf, 0).unwrap();
674 assert_eq!(decoded, p);
675 assert_eq!(end, buf.len());
676 }
677
678 #[test]
679 fn period_calendar_reference_round_trip() {
680 let oid = ObjectIdentifier::new(ObjectType::CALENDAR, 1).unwrap();
681 let p = SpecialEventPeriod::CalendarReference(oid);
682 let mut buf = BytesMut::new();
683 encode_special_event_period(&mut buf, &p);
684 let (decoded, end) = decode_special_event_period(&buf, 0).unwrap();
685 assert_eq!(decoded, p);
686 assert_eq!(end, buf.len());
687 }
688
689 #[test]
692 fn special_event_round_trip_with_calendar_entry() {
693 let e = BACnetSpecialEvent {
694 period: SpecialEventPeriod::CalendarEntry(BACnetCalendarEntry::WeekNDay(
695 BACnetWeekNDay {
696 month: 11,
697 week_of_month: 4,
698 day_of_week: 4,
699 },
700 )),
701 list_of_time_values: vec![tv_real(0, 0, 1.0), tv_null(23, 59)],
702 event_priority: 5,
703 };
704 let mut buf = BytesMut::new();
705 encode_special_event(&mut buf, &e);
706 let (decoded, end) = decode_special_event(&buf, 0).unwrap();
707 assert_eq!(decoded, e);
708 assert_eq!(end, buf.len());
709 }
710
711 #[test]
712 fn special_event_round_trip_with_calendar_reference() {
713 let e = BACnetSpecialEvent {
714 period: SpecialEventPeriod::CalendarReference(
715 ObjectIdentifier::new(ObjectType::CALENDAR, 7).unwrap(),
716 ),
717 list_of_time_values: vec![tv_real(8, 0, 70.0), tv_real(18, 0, 60.0)],
718 event_priority: 16,
719 };
720 let mut buf = BytesMut::new();
721 encode_special_event(&mut buf, &e);
722 let (decoded, end) = decode_special_event(&buf, 0).unwrap();
723 assert_eq!(decoded, e);
724 assert_eq!(end, buf.len());
725 }
726
727 #[test]
728 fn special_event_priority_zero_is_rejected() {
729 let mut buf = BytesMut::new();
731 encode_special_event_period(
732 &mut buf,
733 &SpecialEventPeriod::CalendarReference(
734 ObjectIdentifier::new(ObjectType::CALENDAR, 1).unwrap(),
735 ),
736 );
737 tags::encode_opening_tag(&mut buf, 2);
738 tags::encode_closing_tag(&mut buf, 2);
739 primitives::encode_ctx_unsigned(&mut buf, 3, 0);
740
741 let err = decode_special_event(&buf, 0).unwrap_err();
742 assert!(format!("{err}").contains("event-priority 0"));
743 }
744
745 #[test]
746 fn special_event_priority_seventeen_is_rejected() {
747 let mut buf = BytesMut::new();
748 encode_special_event_period(
749 &mut buf,
750 &SpecialEventPeriod::CalendarReference(
751 ObjectIdentifier::new(ObjectType::CALENDAR, 1).unwrap(),
752 ),
753 );
754 tags::encode_opening_tag(&mut buf, 2);
755 tags::encode_closing_tag(&mut buf, 2);
756 primitives::encode_ctx_unsigned(&mut buf, 3, 17);
757
758 let err = decode_special_event(&buf, 0).unwrap_err();
759 assert!(format!("{err}").contains("event-priority 17"));
760 }
761
762 #[test]
765 fn weekly_schedule_empty_round_trip() {
766 let days: [Vec<BACnetTimeValue>; 7] = Default::default();
767 let mut buf = BytesMut::new();
768 encode_weekly_schedule(&mut buf, &days);
769 let decoded = decode_weekly_schedule(&buf).unwrap();
770 assert_eq!(decoded, days);
771 assert_eq!(buf.len(), 14);
773 }
774
775 #[test]
776 fn weekly_schedule_partially_populated_round_trip() {
777 let mut days: [Vec<BACnetTimeValue>; 7] = Default::default();
779 days[0] = vec![tv_real(6, 0, 70.0), tv_real(22, 0, 65.0)];
780 days[2] = vec![tv_real(8, 0, 72.0)];
781 let mut buf = BytesMut::new();
782 encode_weekly_schedule(&mut buf, &days);
783 let decoded = decode_weekly_schedule(&buf).unwrap();
784 assert_eq!(decoded, days);
785 }
786
787 #[test]
788 fn weekly_schedule_rejects_six_days() {
789 let mut buf = BytesMut::new();
791 for _ in 0..6 {
792 tags::encode_opening_tag(&mut buf, 0);
793 tags::encode_closing_tag(&mut buf, 0);
794 }
795 let err = decode_weekly_schedule(&buf).unwrap_err();
796 assert!(format!("{err}").contains("day 6"));
797 }
798
799 #[test]
800 fn weekly_schedule_rejects_trailing_bytes() {
801 let days: [Vec<BACnetTimeValue>; 7] = Default::default();
802 let mut buf = BytesMut::new();
803 encode_weekly_schedule(&mut buf, &days);
804 buf.extend_from_slice(&[0xAA, 0xBB]); let err = decode_weekly_schedule(&buf).unwrap_err();
806 assert!(format!("{err}").contains("trailing byte"));
807 }
808
809 #[test]
812 fn exception_schedule_empty_round_trip() {
813 let events: Vec<BACnetSpecialEvent> = Vec::new();
814 let mut buf = BytesMut::new();
815 encode_exception_schedule(&mut buf, &events);
816 assert!(buf.is_empty());
817 let decoded = decode_exception_schedule(&buf).unwrap();
818 assert!(decoded.is_empty());
819 }
820
821 #[test]
822 fn exception_schedule_two_events_round_trip() {
823 let events = vec![
824 BACnetSpecialEvent {
825 period: SpecialEventPeriod::CalendarEntry(BACnetCalendarEntry::Date(d(
826 124, 12, 25, 3,
827 ))),
828 list_of_time_values: vec![tv_real(0, 0, 1.0)],
829 event_priority: 1,
830 },
831 BACnetSpecialEvent {
832 period: SpecialEventPeriod::CalendarReference(
833 ObjectIdentifier::new(ObjectType::CALENDAR, 1).unwrap(),
834 ),
835 list_of_time_values: vec![tv_null(0, 0), tv_real(12, 0, 70.0)],
836 event_priority: 8,
837 },
838 ];
839 let mut buf = BytesMut::new();
840 encode_exception_schedule(&mut buf, &events);
841 let decoded = decode_exception_schedule(&buf).unwrap();
842 assert_eq!(decoded, events);
843 }
844}