1use crate::{ClientDataValue, ClientError};
7use rustbac_core::apdu::{
8 ApduType, ComplexAckHeader, ConfirmedRequestHeader, SimpleAck, UnconfirmedRequestHeader,
9};
10use rustbac_core::encoding::{
11 primitives::{decode_unsigned, encode_ctx_unsigned},
12 reader::Reader,
13 tag::Tag,
14 writer::Writer,
15};
16use rustbac_core::npdu::Npdu;
17use rustbac_core::services::i_am::IAmRequest;
18use rustbac_core::services::read_property::SERVICE_READ_PROPERTY;
19use rustbac_core::services::value_codec::encode_application_data_value;
20use rustbac_core::services::write_property::SERVICE_WRITE_PROPERTY;
21use rustbac_core::types::{DataValue, ObjectId, ObjectType, PropertyId};
22use rustbac_datalink::{DataLink, DataLinkAddress};
23use std::collections::HashMap;
24use std::sync::Arc;
25use tokio::sync::RwLock;
26
27pub struct SimulatedDevice<D: DataLink> {
29 pub device_id: ObjectId,
30 objects: Arc<RwLock<HashMap<ObjectId, HashMap<PropertyId, ClientDataValue>>>>,
31 datalink: D,
32}
33
34impl<D: DataLink> SimulatedDevice<D> {
35 pub fn new(instance: u32, datalink: D) -> Self {
37 let device_id = ObjectId::new(ObjectType::Device, instance);
38 let mut device_props = HashMap::new();
39 device_props.insert(
40 PropertyId::ObjectIdentifier,
41 ClientDataValue::ObjectId(device_id),
42 );
43 device_props.insert(
44 PropertyId::ObjectName,
45 ClientDataValue::CharacterString(format!("SimDevice-{instance}")),
46 );
47 device_props.insert(
48 PropertyId::ObjectType,
49 ClientDataValue::Enumerated(ObjectType::Device.to_u16() as u32),
50 );
51
52 let mut objects = HashMap::new();
53 objects.insert(device_id, device_props);
54
55 Self {
56 device_id,
57 objects: Arc::new(RwLock::new(objects)),
58 datalink,
59 }
60 }
61
62 pub async fn add_object(&self, id: ObjectId, properties: HashMap<PropertyId, ClientDataValue>) {
64 self.objects.write().await.insert(id, properties);
65 }
66
67 pub async fn run(&self) -> Result<(), ClientError> {
69 let mut buf = [0u8; 1500];
70 loop {
71 let (n, source) = self.datalink.recv(&mut buf).await?;
72 if let Err(e) = self.handle_frame(&buf[..n], source).await {
73 log::debug!("simulator: error handling frame: {e}");
74 }
75 }
76 }
77
78 async fn handle_frame(&self, frame: &[u8], source: DataLinkAddress) -> Result<(), ClientError> {
79 let mut r = Reader::new(frame);
80 let _npdu = Npdu::decode(&mut r)?;
81
82 if r.is_empty() {
83 return Ok(());
84 }
85
86 let first = r.peek_u8()?;
87 let apdu_type = ApduType::from_u8(first >> 4);
88
89 match apdu_type {
90 Some(ApduType::UnconfirmedRequest) => {
91 let header = UnconfirmedRequestHeader::decode(&mut r)?;
92 if header.service_choice == 0x08 {
93 let who_is_limits = self.decode_who_is_limits(&mut r);
95 if self.matches_who_is(who_is_limits) {
96 self.send_i_am(source).await?;
97 }
98 }
99 }
100 Some(ApduType::ConfirmedRequest) => {
101 let header = ConfirmedRequestHeader::decode(&mut r)?;
102 match header.service_choice {
103 SERVICE_READ_PROPERTY => {
104 self.handle_read_property(&mut r, header.invoke_id, source)
105 .await?;
106 }
107 SERVICE_WRITE_PROPERTY => {
108 self.handle_write_property(&mut r, header.invoke_id, source)
109 .await?;
110 }
111 _ => {
112 }
114 }
115 }
116 _ => {}
117 }
118
119 Ok(())
120 }
121
122 fn decode_who_is_limits(&self, r: &mut Reader<'_>) -> Option<(u32, u32)> {
123 if r.is_empty() {
125 return None; }
127 let tag0 = Tag::decode(r).ok()?;
128 let low = match tag0 {
129 Tag::Context { tag_num: 0, len } => decode_unsigned(r, len as usize).ok()?,
130 _ => return None,
131 };
132 let tag1 = Tag::decode(r).ok()?;
133 let high = match tag1 {
134 Tag::Context { tag_num: 1, len } => decode_unsigned(r, len as usize).ok()?,
135 _ => return None,
136 };
137 Some((low, high))
138 }
139
140 fn matches_who_is(&self, limits: Option<(u32, u32)>) -> bool {
141 let instance = self.device_id.instance();
142 match limits {
143 None => true, Some((low, high)) => instance >= low && instance <= high,
145 }
146 }
147
148 async fn send_i_am(&self, target: DataLinkAddress) -> Result<(), ClientError> {
149 let req = IAmRequest {
150 device_id: self.device_id,
151 max_apdu: 1476,
152 segmentation: 3, vendor_id: 0,
154 };
155
156 let mut buf = [0u8; 128];
157 let mut w = Writer::new(&mut buf);
158 Npdu::new(0).encode(&mut w)?;
159 req.encode(&mut w)?;
160 let data = w.as_written();
161 self.datalink.send(target, data).await?;
162 Ok(())
163 }
164
165 async fn handle_read_property(
166 &self,
167 r: &mut Reader<'_>,
168 invoke_id: u8,
169 source: DataLinkAddress,
170 ) -> Result<(), ClientError> {
171 let object_id = crate::decode_ctx_object_id(r)?;
173 let property_id = PropertyId::from_u32(crate::decode_ctx_unsigned(r)?);
174 let objects = self.objects.read().await;
175
176 let value = objects
177 .get(&object_id)
178 .and_then(|props| props.get(&property_id));
179
180 match value {
181 Some(val) => {
182 let borrowed = client_value_to_borrowed(val);
183 let mut buf = [0u8; 1400];
184 let mut w = Writer::new(&mut buf);
185 Npdu::new(0).encode(&mut w)?;
186 ComplexAckHeader {
187 segmented: false,
188 more_follows: false,
189 invoke_id,
190 sequence_number: None,
191 proposed_window_size: None,
192 service_choice: SERVICE_READ_PROPERTY,
193 }
194 .encode(&mut w)?;
195 encode_ctx_unsigned(&mut w, 0, object_id.raw())?;
197 encode_ctx_unsigned(&mut w, 1, property_id.to_u32())?;
198 Tag::Opening { tag_num: 3 }.encode(&mut w)?;
199 encode_application_data_value(&mut w, &borrowed)?;
200 Tag::Closing { tag_num: 3 }.encode(&mut w)?;
201 let data = w.as_written();
202 self.datalink.send(source, data).await?;
203 }
204 None => {
205 let mut buf = [0u8; 64];
207 let mut w = Writer::new(&mut buf);
208 Npdu::new(0).encode(&mut w)?;
209 w.write_u8(0x50)?; w.write_u8(invoke_id)?;
212 w.write_u8(SERVICE_READ_PROPERTY)?;
213 Tag::Application {
215 tag: rustbac_core::encoding::tag::AppTag::Enumerated,
216 len: 1,
217 }
218 .encode(&mut w)?;
219 w.write_u8(2)?; Tag::Application {
221 tag: rustbac_core::encoding::tag::AppTag::Enumerated,
222 len: 1,
223 }
224 .encode(&mut w)?;
225 w.write_u8(32)?; let data = w.as_written();
227 self.datalink.send(source, data).await?;
228 }
229 }
230
231 Ok(())
232 }
233
234 async fn handle_write_property(
235 &self,
236 r: &mut Reader<'_>,
237 invoke_id: u8,
238 source: DataLinkAddress,
239 ) -> Result<(), ClientError> {
240 let object_id = crate::decode_ctx_object_id(r)?;
242 let property_id_raw = crate::decode_ctx_unsigned(r)?;
243 let property_id = PropertyId::from_u32(property_id_raw);
244
245 let next_tag = Tag::decode(r)?;
246 let value_start_tag = match next_tag {
247 Tag::Context { tag_num: 2, len } => {
248 let _array_index = decode_unsigned(r, len as usize)?;
249 Tag::decode(r)?
250 }
251 other => other,
252 };
253 if value_start_tag != (Tag::Opening { tag_num: 3 }) {
254 return Err(rustbac_core::DecodeError::InvalidTag.into());
255 }
256 let val = rustbac_core::services::value_codec::decode_application_data_value(r)?;
257 match Tag::decode(r)? {
258 Tag::Closing { tag_num: 3 } => {}
259 _ => return Err(rustbac_core::DecodeError::InvalidTag.into()),
260 }
261
262 let client_val = crate::data_value_to_client(val);
263 let mut objects = self.objects.write().await;
264 if let Some(props) = objects.get_mut(&object_id) {
265 props.insert(property_id, client_val);
266 }
267
268 let mut buf = [0u8; 32];
270 let mut w = Writer::new(&mut buf);
271 Npdu::new(0).encode(&mut w)?;
272 SimpleAck {
273 invoke_id,
274 service_choice: SERVICE_WRITE_PROPERTY,
275 }
276 .encode(&mut w)?;
277 let data = w.as_written();
278 self.datalink.send(source, data).await?;
279
280 Ok(())
281 }
282}
283
284fn client_value_to_borrowed(val: &ClientDataValue) -> DataValue<'_> {
288 match val {
289 ClientDataValue::Null => DataValue::Null,
290 ClientDataValue::Boolean(v) => DataValue::Boolean(*v),
291 ClientDataValue::Unsigned(v) => DataValue::Unsigned(*v),
292 ClientDataValue::Signed(v) => DataValue::Signed(*v),
293 ClientDataValue::Real(v) => DataValue::Real(*v),
294 ClientDataValue::Double(v) => DataValue::Double(*v),
295 ClientDataValue::OctetString(v) => DataValue::OctetString(v),
296 ClientDataValue::CharacterString(v) => DataValue::CharacterString(v),
297 ClientDataValue::BitString { unused_bits, data } => {
298 DataValue::BitString(rustbac_core::types::BitString {
299 unused_bits: *unused_bits,
300 data,
301 })
302 }
303 ClientDataValue::Enumerated(v) => DataValue::Enumerated(*v),
304 ClientDataValue::Date(v) => DataValue::Date(*v),
305 ClientDataValue::Time(v) => DataValue::Time(*v),
306 ClientDataValue::ObjectId(v) => DataValue::ObjectId(*v),
307 ClientDataValue::Constructed { tag_num, values } => DataValue::Constructed {
308 tag_num: *tag_num,
309 values: values.iter().map(client_value_to_borrowed).collect(),
310 },
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317 use rustbac_core::encoding::{primitives::encode_ctx_unsigned, reader::Reader, writer::Writer};
318 use std::sync::{Arc, Mutex};
319
320 #[derive(Clone, Default)]
321 struct MockDataLink {
322 sent: Arc<Mutex<Vec<(DataLinkAddress, Vec<u8>)>>>,
323 }
324
325 impl DataLink for MockDataLink {
326 async fn send(
327 &self,
328 address: DataLinkAddress,
329 payload: &[u8],
330 ) -> Result<(), rustbac_datalink::DataLinkError> {
331 self.sent
332 .lock()
333 .expect("poisoned lock")
334 .push((address, payload.to_vec()));
335 Ok(())
336 }
337
338 async fn recv(
339 &self,
340 _buf: &mut [u8],
341 ) -> Result<(usize, DataLinkAddress), rustbac_datalink::DataLinkError> {
342 Err(rustbac_datalink::DataLinkError::InvalidFrame)
343 }
344 }
345
346 #[tokio::test]
347 async fn handle_write_property_accepts_optional_array_index() {
348 let dl = MockDataLink::default();
349 let sent = dl.sent.clone();
350 let sim = SimulatedDevice::new(1, dl);
351
352 let mut payload = [0u8; 256];
353 let mut w = Writer::new(&mut payload);
354 encode_ctx_unsigned(&mut w, 0, sim.device_id.raw()).unwrap();
355 encode_ctx_unsigned(&mut w, 1, PropertyId::ObjectName.to_u32()).unwrap();
356 encode_ctx_unsigned(&mut w, 2, 0).unwrap();
357 Tag::Opening { tag_num: 3 }.encode(&mut w).unwrap();
358 rustbac_core::services::value_codec::encode_application_data_value(
359 &mut w,
360 &DataValue::CharacterString("updated-name"),
361 )
362 .unwrap();
363 Tag::Closing { tag_num: 3 }.encode(&mut w).unwrap();
364
365 let source = DataLinkAddress::Ip("127.0.0.1:47808".parse().unwrap());
366 let mut r = Reader::new(w.as_written());
367 sim.handle_write_property(&mut r, 9, source).await.unwrap();
368
369 let objects = sim.objects.read().await;
370 let props = objects.get(&sim.device_id).unwrap();
371 assert_eq!(
372 props.get(&PropertyId::ObjectName),
373 Some(&ClientDataValue::CharacterString(
374 "updated-name".to_string()
375 ))
376 );
377
378 let sent = sent.lock().expect("poisoned lock");
379 assert_eq!(sent.len(), 1);
380 let mut ack_reader = Reader::new(&sent[0].1);
381 let _npdu = Npdu::decode(&mut ack_reader).unwrap();
382 let ack = SimpleAck::decode(&mut ack_reader).unwrap();
383 assert_eq!(ack.invoke_id, 9);
384 assert_eq!(ack.service_choice, SERVICE_WRITE_PROPERTY);
385 }
386}