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