1use crate::ClientDataValue;
9use rustbac_core::apdu::{
10 ApduType, ComplexAckHeader, ConfirmedRequestHeader, SimpleAck, UnconfirmedRequestHeader,
11};
12use rustbac_core::encoding::{
13 primitives::{decode_unsigned, encode_ctx_unsigned},
14 reader::Reader,
15 tag::Tag,
16 writer::Writer,
17};
18use rustbac_core::npdu::Npdu;
19use rustbac_core::services::i_am::IAmRequest;
20use rustbac_core::services::read_property::SERVICE_READ_PROPERTY;
21use rustbac_core::services::read_property_multiple::SERVICE_READ_PROPERTY_MULTIPLE;
22use rustbac_core::services::value_codec::encode_application_data_value;
23use rustbac_core::services::write_property::SERVICE_WRITE_PROPERTY;
24use rustbac_core::types::{ObjectId, PropertyId};
25use rustbac_datalink::{DataLink, DataLinkAddress};
26use std::collections::HashMap;
27use std::future::Future;
28use std::sync::{Arc, Mutex};
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum BacnetServiceError {
37 UnknownObject,
39 UnknownProperty,
41 WriteAccessDenied,
43 InvalidDataType,
45}
46
47impl BacnetServiceError {
48 fn to_error_class_code(self) -> (u8, u8) {
50 match self {
51 BacnetServiceError::UnknownObject => (1, 31),
53 BacnetServiceError::UnknownProperty => (2, 32),
55 BacnetServiceError::WriteAccessDenied => (2, 40),
57 BacnetServiceError::InvalidDataType => (2, 9),
59 }
60 }
61}
62
63pub trait ServiceHandler: Send + Sync + 'static {
69 fn read_property(
71 &self,
72 object_id: ObjectId,
73 property_id: PropertyId,
74 array_index: Option<u32>,
75 ) -> Result<ClientDataValue, BacnetServiceError>;
76
77 fn write_property(
79 &self,
80 object_id: ObjectId,
81 property_id: PropertyId,
82 array_index: Option<u32>,
83 value: ClientDataValue,
84 priority: Option<u8>,
85 ) -> Result<(), BacnetServiceError>;
86}
87
88pub struct ObjectStore {
94 inner: Mutex<HashMap<ObjectId, HashMap<PropertyId, ClientDataValue>>>,
95}
96
97impl ObjectStore {
98 pub fn new() -> Self {
100 Self {
101 inner: Mutex::new(HashMap::new()),
102 }
103 }
104
105 pub fn set(&self, object_id: ObjectId, property_id: PropertyId, value: ClientDataValue) {
107 let mut map = self.inner.lock().expect("ObjectStore lock poisoned");
108 map.entry(object_id).or_default().insert(property_id, value);
109 }
110
111 pub fn get(&self, object_id: ObjectId, property_id: PropertyId) -> Option<ClientDataValue> {
113 let map = self.inner.lock().expect("ObjectStore lock poisoned");
114 map.get(&object_id)?.get(&property_id).cloned()
115 }
116
117 pub fn remove_object(&self, object_id: ObjectId) {
119 let mut map = self.inner.lock().expect("ObjectStore lock poisoned");
120 map.remove(&object_id);
121 }
122
123 pub fn object_ids(&self) -> Vec<ObjectId> {
125 let map = self.inner.lock().expect("ObjectStore lock poisoned");
126 map.keys().copied().collect()
127 }
128}
129
130impl Default for ObjectStore {
131 fn default() -> Self {
132 Self::new()
133 }
134}
135
136pub struct ObjectStoreHandler {
145 store: Arc<ObjectStore>,
146}
147
148impl ObjectStoreHandler {
149 pub fn new(store: Arc<ObjectStore>) -> Self {
151 Self { store }
152 }
153}
154
155impl ServiceHandler for ObjectStoreHandler {
156 fn read_property(
157 &self,
158 object_id: ObjectId,
159 property_id: PropertyId,
160 _array_index: Option<u32>,
161 ) -> Result<ClientDataValue, BacnetServiceError> {
162 let map = self.store.inner.lock().expect("ObjectStore lock poisoned");
164 let props = map
165 .get(&object_id)
166 .ok_or(BacnetServiceError::UnknownObject)?;
167 props
168 .get(&property_id)
169 .cloned()
170 .ok_or(BacnetServiceError::UnknownProperty)
171 }
172
173 fn write_property(
174 &self,
175 object_id: ObjectId,
176 property_id: PropertyId,
177 _array_index: Option<u32>,
178 value: ClientDataValue,
179 _priority: Option<u8>,
180 ) -> Result<(), BacnetServiceError> {
181 let mut map = self.store.inner.lock().expect("ObjectStore lock poisoned");
182 let props = map
184 .get_mut(&object_id)
185 .ok_or(BacnetServiceError::UnknownObject)?;
186 props.insert(property_id, value);
187 Ok(())
188 }
189}
190
191pub struct BacnetServer<D: DataLink> {
197 datalink: Arc<D>,
198 handler: Arc<dyn ServiceHandler>,
199 device_id: u32,
200 vendor_id: u16,
201 #[allow(dead_code)]
203 max_apdu: u8,
204}
205
206impl<D: DataLink> BacnetServer<D> {
207 pub fn new(datalink: D, device_id: u32, handler: impl ServiceHandler) -> Self {
209 Self {
210 datalink: Arc::new(datalink),
211 handler: Arc::new(handler),
212 device_id,
213 vendor_id: 0,
214 max_apdu: 5, }
216 }
217
218 pub fn with_vendor_id(mut self, vendor_id: u16) -> Self {
220 self.vendor_id = vendor_id;
221 self
222 }
223
224 pub fn serve(self) -> impl Future<Output = ()> {
233 async move {
234 let mut buf = [0u8; 1500];
235 loop {
236 let result = self.datalink.recv(&mut buf).await;
237 match result {
238 Ok((n, source)) => {
239 if let Err(e) = self.handle_frame(&buf[..n], source).await {
240 log::debug!("server: error handling frame: {e:?}");
241 }
242 }
243 Err(e) => {
244 log::debug!("server: datalink recv error: {e:?}");
245 tokio::task::yield_now().await;
247 }
248 }
249 }
250 }
251 }
252
253 async fn handle_frame(
256 &self,
257 frame: &[u8],
258 source: DataLinkAddress,
259 ) -> Result<(), rustbac_core::DecodeError> {
260 let mut r = Reader::new(frame);
261 let _npdu = Npdu::decode(&mut r)?;
262
263 if r.is_empty() {
264 return Ok(());
265 }
266
267 let first = r.peek_u8()?;
268 let apdu_type = ApduType::from_u8(first >> 4);
269
270 match apdu_type {
271 Some(ApduType::UnconfirmedRequest) => {
272 let header = UnconfirmedRequestHeader::decode(&mut r)?;
273 if header.service_choice == 0x08 {
274 let limits = decode_who_is_limits(&mut r);
276 if matches_who_is(self.device_id, limits) {
277 self.send_i_am(source).await;
278 }
279 }
280 }
282 Some(ApduType::ConfirmedRequest) => {
283 let header = ConfirmedRequestHeader::decode(&mut r)?;
284 let invoke_id = header.invoke_id;
285 match header.service_choice {
286 SERVICE_READ_PROPERTY => {
287 self.handle_read_property(&mut r, invoke_id, source).await;
288 }
289 SERVICE_WRITE_PROPERTY => {
290 self.handle_write_property(&mut r, invoke_id, source).await;
291 }
292 SERVICE_READ_PROPERTY_MULTIPLE => {
293 self.handle_read_property_multiple(&mut r, invoke_id, source)
294 .await;
295 }
296 _ => {
297 self.send_reject(invoke_id, 0x08, source).await;
299 }
300 }
301 }
302 _ => {
303 }
305 }
306
307 Ok(())
308 }
309
310 async fn send_i_am(&self, target: DataLinkAddress) {
311 let device_id_raw =
312 rustbac_core::types::ObjectId::new(rustbac_core::types::ObjectType::Device, self.device_id);
313 let req = IAmRequest {
314 device_id: device_id_raw,
315 max_apdu: 1476,
316 segmentation: 3, vendor_id: self.vendor_id as u32,
318 };
319 let mut buf = [0u8; 128];
320 let mut w = Writer::new(&mut buf);
321 if Npdu::new(0).encode(&mut w).is_err() {
322 return;
323 }
324 if req.encode(&mut w).is_err() {
325 return;
326 }
327 let _ = self.datalink.send(target, w.as_written()).await;
328 }
329
330 async fn handle_read_property(
331 &self,
332 r: &mut Reader<'_>,
333 invoke_id: u8,
334 source: DataLinkAddress,
335 ) {
336 let object_id = match crate::decode_ctx_object_id(r) {
338 Ok(v) => v,
339 Err(_) => return,
340 };
341 let property_id_raw = match crate::decode_ctx_unsigned(r) {
342 Ok(v) => v,
343 Err(_) => return,
344 };
345 let property_id = PropertyId::from_u32(property_id_raw);
346
347 let array_index = decode_optional_array_index(r);
349
350 match self
351 .handler
352 .read_property(object_id, property_id, array_index)
353 {
354 Ok(value) => {
355 let borrowed = client_value_to_borrowed(&value);
356 let mut buf = [0u8; 1400];
357 let mut w = Writer::new(&mut buf);
358 if Npdu::new(0).encode(&mut w).is_err() {
359 return;
360 }
361 if (ComplexAckHeader {
362 segmented: false,
363 more_follows: false,
364 invoke_id,
365 sequence_number: None,
366 proposed_window_size: None,
367 service_choice: SERVICE_READ_PROPERTY,
368 })
369 .encode(&mut w)
370 .is_err()
371 {
372 return;
373 }
374 if encode_ctx_unsigned(&mut w, 0, object_id.raw()).is_err() {
375 return;
376 }
377 if encode_ctx_unsigned(&mut w, 1, property_id.to_u32()).is_err() {
378 return;
379 }
380 if (Tag::Opening { tag_num: 3 }).encode(&mut w).is_err() {
381 return;
382 }
383 if encode_application_data_value(&mut w, &borrowed).is_err() {
384 return;
385 }
386 if (Tag::Closing { tag_num: 3 }).encode(&mut w).is_err() {
387 return;
388 }
389 let _ = self.datalink.send(source, w.as_written()).await;
390 }
391 Err(err) => {
392 self.send_error(invoke_id, SERVICE_READ_PROPERTY, err, source)
393 .await;
394 }
395 }
396 }
397
398 async fn handle_write_property(
399 &self,
400 r: &mut Reader<'_>,
401 invoke_id: u8,
402 source: DataLinkAddress,
403 ) {
404 let object_id = match crate::decode_ctx_object_id(r) {
406 Ok(v) => v,
407 Err(_) => return,
408 };
409 let property_id_raw = match crate::decode_ctx_unsigned(r) {
410 Ok(v) => v,
411 Err(_) => return,
412 };
413 let property_id = PropertyId::from_u32(property_id_raw);
414
415 let next_tag = match Tag::decode(r) {
417 Ok(t) => t,
418 Err(_) => return,
419 };
420 let (array_index, value_start_tag) = match next_tag {
421 Tag::Context { tag_num: 2, len } => {
422 let idx = match decode_unsigned(r, len as usize) {
423 Ok(v) => v,
424 Err(_) => return,
425 };
426 let vt = match Tag::decode(r) {
427 Ok(t) => t,
428 Err(_) => return,
429 };
430 (Some(idx), vt)
431 }
432 other => (None, other),
433 };
434
435 if value_start_tag != (Tag::Opening { tag_num: 3 }) {
436 return;
437 }
438
439 let val = match rustbac_core::services::value_codec::decode_application_data_value(r) {
440 Ok(v) => v,
441 Err(_) => return,
442 };
443
444 match Tag::decode(r) {
445 Ok(Tag::Closing { tag_num: 3 }) => {}
446 _ => return,
447 }
448
449 let priority = if !r.is_empty() {
451 match Tag::decode(r) {
452 Ok(Tag::Context { tag_num: 4, len }) => {
453 match decode_unsigned(r, len as usize) {
454 Ok(p) => Some(p as u8),
455 Err(_) => return,
456 }
457 }
458 _ => None,
459 }
460 } else {
461 None
462 };
463
464 let client_val = crate::data_value_to_client(val);
465
466 match self
467 .handler
468 .write_property(object_id, property_id, array_index, client_val, priority)
469 {
470 Ok(()) => {
471 let mut buf = [0u8; 32];
472 let mut w = Writer::new(&mut buf);
473 if Npdu::new(0).encode(&mut w).is_err() {
474 return;
475 }
476 if (SimpleAck {
477 invoke_id,
478 service_choice: SERVICE_WRITE_PROPERTY,
479 })
480 .encode(&mut w)
481 .is_err()
482 {
483 return;
484 }
485 let _ = self.datalink.send(source, w.as_written()).await;
486 }
487 Err(err) => {
488 self.send_error(invoke_id, SERVICE_WRITE_PROPERTY, err, source)
489 .await;
490 }
491 }
492 }
493
494 async fn handle_read_property_multiple(
495 &self,
496 r: &mut Reader<'_>,
497 invoke_id: u8,
498 source: DataLinkAddress,
499 ) {
500 let mut specs: Vec<(ObjectId, Vec<(PropertyId, Option<u32>)>)> = Vec::new();
502
503 while !r.is_empty() {
504 let object_id = match crate::decode_ctx_object_id(r) {
506 Ok(v) => v,
507 Err(_) => return,
508 };
509
510 match Tag::decode(r) {
512 Ok(Tag::Opening { tag_num: 1 }) => {}
513 _ => return,
514 }
515
516 let mut props: Vec<(PropertyId, Option<u32>)> = Vec::new();
517 loop {
518 let tag = match Tag::decode(r) {
520 Ok(t) => t,
521 Err(_) => return,
522 };
523 if tag == (Tag::Closing { tag_num: 1 }) {
524 break;
525 }
526 let property_id = match tag {
527 Tag::Context { tag_num: 0, len } => {
528 match decode_unsigned(r, len as usize) {
529 Ok(v) => PropertyId::from_u32(v),
530 Err(_) => return,
531 }
532 }
533 _ => return,
534 };
535
536 let array_index = if !r.is_empty() {
538 match peek_context_tag(r, 1) {
540 Some(len) => {
541 match Tag::decode(r) {
543 Ok(_) => {}
544 Err(_) => return,
545 }
546 match decode_unsigned(r, len as usize) {
547 Ok(idx) => Some(idx),
548 Err(_) => return,
549 }
550 }
551 None => None,
552 }
553 } else {
554 None
555 };
556
557 props.push((property_id, array_index));
558 }
559
560 specs.push((object_id, props));
561 }
562
563 let mut buf = [0u8; 1400];
565 let mut w = Writer::new(&mut buf);
566 if Npdu::new(0).encode(&mut w).is_err() {
567 return;
568 }
569 if (ComplexAckHeader {
570 segmented: false,
571 more_follows: false,
572 invoke_id,
573 sequence_number: None,
574 proposed_window_size: None,
575 service_choice: SERVICE_READ_PROPERTY_MULTIPLE,
576 })
577 .encode(&mut w)
578 .is_err()
579 {
580 return;
581 }
582
583 for (object_id, props) in &specs {
584 if encode_ctx_unsigned(&mut w, 0, object_id.raw()).is_err() {
586 return;
587 }
588 if (Tag::Opening { tag_num: 1 }).encode(&mut w).is_err() {
590 return;
591 }
592
593 for (property_id, array_index) in props {
594 if encode_ctx_unsigned(&mut w, 2, property_id.to_u32()).is_err() {
596 return;
597 }
598 if let Some(idx) = array_index {
600 if encode_ctx_unsigned(&mut w, 3, *idx).is_err() {
601 return;
602 }
603 }
604
605 if (Tag::Opening { tag_num: 4 }).encode(&mut w).is_err() {
607 return;
608 }
609
610 match self.handler.read_property(*object_id, *property_id, *array_index) {
611 Ok(value) => {
612 let borrowed = client_value_to_borrowed(&value);
613 if encode_application_data_value(&mut w, &borrowed).is_err() {
614 return;
615 }
616 }
617 Err(err) => {
618 let (class, code) = err.to_error_class_code();
620 if (Tag::Opening { tag_num: 5 }).encode(&mut w).is_err() {
621 return;
622 }
623 if encode_ctx_unsigned(&mut w, 0, class as u32).is_err() {
624 return;
625 }
626 if encode_ctx_unsigned(&mut w, 1, code as u32).is_err() {
627 return;
628 }
629 if (Tag::Closing { tag_num: 5 }).encode(&mut w).is_err() {
630 return;
631 }
632 }
633 }
634
635 if (Tag::Closing { tag_num: 4 }).encode(&mut w).is_err() {
637 return;
638 }
639 }
640
641 if (Tag::Closing { tag_num: 1 }).encode(&mut w).is_err() {
643 return;
644 }
645 }
646
647 let _ = self.datalink.send(source, w.as_written()).await;
648 }
649
650 async fn send_error(
651 &self,
652 invoke_id: u8,
653 service_choice: u8,
654 err: BacnetServiceError,
655 target: DataLinkAddress,
656 ) {
657 let (class, code) = err.to_error_class_code();
658 let mut buf = [0u8; 64];
659 let mut w = Writer::new(&mut buf);
660 if Npdu::new(0).encode(&mut w).is_err() {
661 return;
662 }
663 if w.write_u8(0x50).is_err() {
665 return;
666 }
667 if w.write_u8(invoke_id).is_err() {
668 return;
669 }
670 if w.write_u8(service_choice).is_err() {
671 return;
672 }
673 if (Tag::Application {
675 tag: rustbac_core::encoding::tag::AppTag::Enumerated,
676 len: 1,
677 })
678 .encode(&mut w)
679 .is_err()
680 {
681 return;
682 }
683 if w.write_u8(class).is_err() {
684 return;
685 }
686 if (Tag::Application {
688 tag: rustbac_core::encoding::tag::AppTag::Enumerated,
689 len: 1,
690 })
691 .encode(&mut w)
692 .is_err()
693 {
694 return;
695 }
696 if w.write_u8(code).is_err() {
697 return;
698 }
699 let _ = self.datalink.send(target, w.as_written()).await;
700 }
701
702 async fn send_reject(&self, invoke_id: u8, reason: u8, target: DataLinkAddress) {
703 let mut buf = [0u8; 16];
704 let mut w = Writer::new(&mut buf);
705 if Npdu::new(0).encode(&mut w).is_err() {
706 return;
707 }
708 if w.write_u8(0x60).is_err() {
710 return;
711 }
712 if w.write_u8(invoke_id).is_err() {
713 return;
714 }
715 if w.write_u8(reason).is_err() {
716 return;
717 }
718 let _ = self.datalink.send(target, w.as_written()).await;
719 }
720}
721
722fn decode_who_is_limits(r: &mut Reader<'_>) -> Option<(u32, u32)> {
728 if r.is_empty() {
729 return None;
730 }
731 let tag0 = Tag::decode(r).ok()?;
732 let low = match tag0 {
733 Tag::Context { tag_num: 0, len } => decode_unsigned(r, len as usize).ok()?,
734 _ => return None,
735 };
736 let tag1 = Tag::decode(r).ok()?;
737 let high = match tag1 {
738 Tag::Context { tag_num: 1, len } => decode_unsigned(r, len as usize).ok()?,
739 _ => return None,
740 };
741 Some((low, high))
742}
743
744fn matches_who_is(device_id: u32, limits: Option<(u32, u32)>) -> bool {
746 match limits {
747 None => true,
748 Some((low, high)) => device_id >= low && device_id <= high,
749 }
750}
751
752fn decode_optional_array_index(r: &mut Reader<'_>) -> Option<u32> {
757 if r.is_empty() {
758 return None;
759 }
760 let first = r.peek_u8().ok()?;
762 let tag = Tag::decode(r).ok()?;
768 match tag {
769 Tag::Context { tag_num: 2, len } => decode_unsigned(r, len as usize).ok(),
770 _ => {
771 let _ = first; None
780 }
781 }
782}
783
784fn peek_context_tag(r: &mut Reader<'_>, tag_num: u8) -> Option<u32> {
788 let first = r.peek_u8().ok()?;
789 let is_context = (first & 0x08) != 0 && (first & 0x07) < 6;
795 if !is_context {
796 return None;
797 }
798 let this_tag_num = first >> 4;
799 if this_tag_num != tag_num {
800 return None;
801 }
802 let short_len = first & 0x07;
803 if short_len < 5 {
804 Some(short_len as u32)
805 } else {
806 None
808 }
809}
810
811fn client_value_to_borrowed(val: &ClientDataValue) -> rustbac_core::types::DataValue<'_> {
813 use rustbac_core::types::DataValue;
814 match val {
815 ClientDataValue::Null => DataValue::Null,
816 ClientDataValue::Boolean(v) => DataValue::Boolean(*v),
817 ClientDataValue::Unsigned(v) => DataValue::Unsigned(*v),
818 ClientDataValue::Signed(v) => DataValue::Signed(*v),
819 ClientDataValue::Real(v) => DataValue::Real(*v),
820 ClientDataValue::Double(v) => DataValue::Double(*v),
821 ClientDataValue::OctetString(v) => DataValue::OctetString(v),
822 ClientDataValue::CharacterString(v) => DataValue::CharacterString(v),
823 ClientDataValue::BitString { unused_bits, data } => {
824 DataValue::BitString(rustbac_core::types::BitString {
825 unused_bits: *unused_bits,
826 data,
827 })
828 }
829 ClientDataValue::Enumerated(v) => DataValue::Enumerated(*v),
830 ClientDataValue::Date(v) => DataValue::Date(*v),
831 ClientDataValue::Time(v) => DataValue::Time(*v),
832 ClientDataValue::ObjectId(v) => DataValue::ObjectId(*v),
833 ClientDataValue::Constructed { tag_num, values } => DataValue::Constructed {
834 tag_num: *tag_num,
835 values: values.iter().map(client_value_to_borrowed).collect(),
836 },
837 }
838}
839
840trait WriterExt {
846 fn write_u8(&mut self, b: u8) -> Result<(), rustbac_core::EncodeError>;
847}
848
849impl WriterExt for Writer<'_> {
850 fn write_u8(&mut self, b: u8) -> Result<(), rustbac_core::EncodeError> {
851 Writer::write_u8(self, b)
852 }
853}
854
855#[cfg(test)]
856mod tests {
857 use super::*;
858 use rustbac_core::apdu::{ComplexAckHeader, SimpleAck};
859 use rustbac_core::encoding::{reader::Reader, writer::Writer};
860 use rustbac_core::npdu::Npdu;
861 use rustbac_core::services::read_property::SERVICE_READ_PROPERTY;
862 use rustbac_core::services::write_property::SERVICE_WRITE_PROPERTY;
863 use rustbac_core::types::{ObjectId, ObjectType, PropertyId};
864 use rustbac_datalink::DataLinkAddress;
865 use std::sync::{Arc, Mutex};
866
867 #[derive(Clone, Default)]
868 struct MockDataLink {
869 sent: Arc<Mutex<Vec<(DataLinkAddress, Vec<u8>)>>>,
870 }
871
872 impl rustbac_datalink::DataLink for MockDataLink {
873 async fn send(
874 &self,
875 address: DataLinkAddress,
876 payload: &[u8],
877 ) -> Result<(), rustbac_datalink::DataLinkError> {
878 self.sent
879 .lock()
880 .expect("poisoned")
881 .push((address, payload.to_vec()));
882 Ok(())
883 }
884
885 async fn recv(
886 &self,
887 _buf: &mut [u8],
888 ) -> Result<(usize, DataLinkAddress), rustbac_datalink::DataLinkError> {
889 Err(rustbac_datalink::DataLinkError::InvalidFrame)
890 }
891 }
892
893 fn make_server() -> (BacnetServer<MockDataLink>, Arc<Mutex<Vec<(DataLinkAddress, Vec<u8>)>>>, Arc<ObjectStore>) {
894 let store = Arc::new(ObjectStore::new());
895 let device_id = ObjectId::new(ObjectType::Device, 42);
896 store.set(
897 device_id,
898 PropertyId::ObjectName,
899 ClientDataValue::CharacterString("TestDevice".to_string()),
900 );
901 let handler = ObjectStoreHandler::new(store.clone());
902 let dl = MockDataLink::default();
903 let sent = dl.sent.clone();
904 let server = BacnetServer::new(dl, 42, handler);
905 (server, sent, store)
906 }
907
908 fn source() -> DataLinkAddress {
909 DataLinkAddress::Ip("127.0.0.1:47808".parse().unwrap())
910 }
911
912 #[tokio::test]
913 async fn object_store_set_get_remove() {
914 let store = ObjectStore::new();
915 let oid = ObjectId::new(ObjectType::AnalogValue, 1);
916 store.set(oid, PropertyId::PresentValue, ClientDataValue::Real(3.14));
917 assert_eq!(
918 store.get(oid, PropertyId::PresentValue),
919 Some(ClientDataValue::Real(3.14))
920 );
921 store.remove_object(oid);
922 assert_eq!(store.get(oid, PropertyId::PresentValue), None);
923 }
924
925 #[tokio::test]
926 async fn object_store_object_ids() {
927 let store = ObjectStore::new();
928 let oid1 = ObjectId::new(ObjectType::AnalogValue, 1);
929 let oid2 = ObjectId::new(ObjectType::AnalogValue, 2);
930 store.set(oid1, PropertyId::PresentValue, ClientDataValue::Real(1.0));
931 store.set(oid2, PropertyId::PresentValue, ClientDataValue::Real(2.0));
932 let mut ids = store.object_ids();
933 ids.sort_by_key(|id| id.raw());
934 assert!(ids.contains(&oid1));
935 assert!(ids.contains(&oid2));
936 }
937
938 #[tokio::test]
939 async fn read_property_known_returns_complex_ack() {
940 let (server, sent, _store) = make_server();
941 let device_id = ObjectId::new(ObjectType::Device, 42);
942
943 use rustbac_core::encoding::primitives::encode_ctx_unsigned;
945 let mut req_buf = [0u8; 256];
946 let mut w = Writer::new(&mut req_buf);
947 Npdu::new(0).encode(&mut w).unwrap();
948 rustbac_core::apdu::ConfirmedRequestHeader {
949 segmented: false,
950 more_follows: false,
951 segmented_response_accepted: true,
952 max_segments: 0,
953 max_apdu: 5,
954 invoke_id: 7,
955 sequence_number: None,
956 proposed_window_size: None,
957 service_choice: SERVICE_READ_PROPERTY,
958 }
959 .encode(&mut w)
960 .unwrap();
961 encode_ctx_unsigned(&mut w, 0, device_id.raw()).unwrap();
962 encode_ctx_unsigned(&mut w, 1, PropertyId::ObjectName.to_u32()).unwrap();
963
964 server
965 .handle_frame(w.as_written(), source())
966 .await
967 .unwrap();
968
969 let sent = sent.lock().expect("poisoned");
970 assert_eq!(sent.len(), 1);
971 let mut r = Reader::new(&sent[0].1);
972 let _npdu = Npdu::decode(&mut r).unwrap();
973 let hdr = ComplexAckHeader::decode(&mut r).unwrap();
974 assert_eq!(hdr.invoke_id, 7);
975 assert_eq!(hdr.service_choice, SERVICE_READ_PROPERTY);
976 }
977
978 #[tokio::test]
979 async fn read_property_unknown_object_returns_error() {
980 let (server, sent, _store) = make_server();
981 let unknown = ObjectId::new(ObjectType::AnalogValue, 999);
982
983 use rustbac_core::encoding::primitives::encode_ctx_unsigned;
984 let mut req_buf = [0u8; 256];
985 let mut w = Writer::new(&mut req_buf);
986 Npdu::new(0).encode(&mut w).unwrap();
987 rustbac_core::apdu::ConfirmedRequestHeader {
988 segmented: false,
989 more_follows: false,
990 segmented_response_accepted: true,
991 max_segments: 0,
992 max_apdu: 5,
993 invoke_id: 3,
994 sequence_number: None,
995 proposed_window_size: None,
996 service_choice: SERVICE_READ_PROPERTY,
997 }
998 .encode(&mut w)
999 .unwrap();
1000 encode_ctx_unsigned(&mut w, 0, unknown.raw()).unwrap();
1001 encode_ctx_unsigned(&mut w, 1, PropertyId::PresentValue.to_u32()).unwrap();
1002
1003 server
1004 .handle_frame(w.as_written(), source())
1005 .await
1006 .unwrap();
1007
1008 let sent = sent.lock().expect("poisoned");
1009 assert_eq!(sent.len(), 1);
1010 let mut r = Reader::new(&sent[0].1);
1012 let _npdu = Npdu::decode(&mut r).unwrap();
1013 let type_byte = r.read_u8().unwrap();
1014 assert_eq!(type_byte >> 4, 5, "expected Error PDU type");
1015 }
1016
1017 #[tokio::test]
1018 async fn write_property_updates_store() {
1019 let (server, sent, store) = make_server();
1020 let device_id = ObjectId::new(ObjectType::Device, 42);
1021
1022 use rustbac_core::encoding::primitives::encode_ctx_unsigned;
1023 use rustbac_core::services::value_codec::encode_application_data_value;
1024 use rustbac_core::types::DataValue;
1025
1026 let mut req_buf = [0u8; 256];
1027 let mut w = Writer::new(&mut req_buf);
1028 Npdu::new(0).encode(&mut w).unwrap();
1029 rustbac_core::apdu::ConfirmedRequestHeader {
1030 segmented: false,
1031 more_follows: false,
1032 segmented_response_accepted: false,
1033 max_segments: 0,
1034 max_apdu: 5,
1035 invoke_id: 11,
1036 sequence_number: None,
1037 proposed_window_size: None,
1038 service_choice: SERVICE_WRITE_PROPERTY,
1039 }
1040 .encode(&mut w)
1041 .unwrap();
1042 encode_ctx_unsigned(&mut w, 0, device_id.raw()).unwrap();
1043 encode_ctx_unsigned(&mut w, 1, PropertyId::ObjectName.to_u32()).unwrap();
1044 Tag::Opening { tag_num: 3 }.encode(&mut w).unwrap();
1045 encode_application_data_value(&mut w, &DataValue::CharacterString("NewName")).unwrap();
1046 Tag::Closing { tag_num: 3 }.encode(&mut w).unwrap();
1047
1048 server
1049 .handle_frame(w.as_written(), source())
1050 .await
1051 .unwrap();
1052
1053 let sent_frames = sent.lock().expect("poisoned");
1055 assert_eq!(sent_frames.len(), 1);
1056 let mut r = Reader::new(&sent_frames[0].1);
1057 let _npdu = Npdu::decode(&mut r).unwrap();
1058 let ack = SimpleAck::decode(&mut r).unwrap();
1059 assert_eq!(ack.invoke_id, 11);
1060 assert_eq!(ack.service_choice, SERVICE_WRITE_PROPERTY);
1061 drop(sent_frames);
1062
1063 assert_eq!(
1065 store.get(device_id, PropertyId::ObjectName),
1066 Some(ClientDataValue::CharacterString("NewName".to_string()))
1067 );
1068 }
1069
1070 #[tokio::test]
1071 async fn who_is_sends_i_am() {
1072 let (server, sent, _store) = make_server();
1073
1074 let mut req_buf = [0u8; 32];
1075 let mut w = Writer::new(&mut req_buf);
1076 Npdu::new(0).encode(&mut w).unwrap();
1077 rustbac_core::apdu::UnconfirmedRequestHeader { service_choice: 0x08 }
1079 .encode(&mut w)
1080 .unwrap();
1081
1082 server
1083 .handle_frame(w.as_written(), source())
1084 .await
1085 .unwrap();
1086
1087 let sent = sent.lock().expect("poisoned");
1088 assert_eq!(sent.len(), 1);
1089 let mut r = Reader::new(&sent[0].1);
1090 let _npdu = Npdu::decode(&mut r).unwrap();
1091 let _unconf_hdr = rustbac_core::apdu::UnconfirmedRequestHeader::decode(&mut r).unwrap();
1092 let iam = rustbac_core::services::i_am::IAmRequest::decode_after_header(&mut r).unwrap();
1093 assert_eq!(iam.device_id.instance(), 42);
1094 }
1095
1096 #[tokio::test]
1097 async fn unknown_service_sends_reject() {
1098 let (server, sent, _store) = make_server();
1099
1100 let mut req_buf = [0u8; 32];
1101 let mut w = Writer::new(&mut req_buf);
1102 Npdu::new(0).encode(&mut w).unwrap();
1103 rustbac_core::apdu::ConfirmedRequestHeader {
1104 segmented: false,
1105 more_follows: false,
1106 segmented_response_accepted: false,
1107 max_segments: 0,
1108 max_apdu: 5,
1109 invoke_id: 99,
1110 sequence_number: None,
1111 proposed_window_size: None,
1112 service_choice: 0x55, }
1114 .encode(&mut w)
1115 .unwrap();
1116
1117 server
1118 .handle_frame(w.as_written(), source())
1119 .await
1120 .unwrap();
1121
1122 let sent = sent.lock().expect("poisoned");
1123 assert_eq!(sent.len(), 1);
1124 let mut r = Reader::new(&sent[0].1);
1125 let _npdu = Npdu::decode(&mut r).unwrap();
1126 let type_byte = r.read_u8().unwrap();
1127 assert_eq!(type_byte >> 4, 6, "expected Reject PDU type");
1128 let id = r.read_u8().unwrap();
1129 let reason = r.read_u8().unwrap();
1130 assert_eq!(id, 99);
1131 assert_eq!(reason, 0x08); }
1133}