1use bacnet_types::MacAddr;
7use bytes::Bytes;
8use std::collections::HashMap;
9use tokio::sync::oneshot;
10
11#[derive(Debug, Clone)]
13pub struct TsmConfig {
14 pub apdu_timeout_ms: u64,
16 pub apdu_segment_timeout_ms: u64,
18 pub apdu_retries: u8,
20}
21
22impl Default for TsmConfig {
23 fn default() -> Self {
24 Self {
25 apdu_timeout_ms: 6000,
26 apdu_segment_timeout_ms: 6000,
27 apdu_retries: 3,
28 }
29 }
30}
31
32#[derive(Debug)]
34pub enum TsmResponse {
35 SimpleAck,
37 ComplexAck { service_data: Bytes },
39 Error { class: u32, code: u32 },
41 Reject { reason: u8 },
43 Abort { reason: u8 },
45}
46
47struct InvokeIdAllocator {
49 next_id: u8,
50 in_use: [bool; 256],
51}
52
53impl InvokeIdAllocator {
54 fn new() -> Self {
55 Self {
56 next_id: 0,
57 in_use: [false; 256],
58 }
59 }
60
61 fn allocate(&mut self) -> Option<u8> {
62 let start = self.next_id;
63 loop {
64 let id = self.next_id;
65 self.next_id = self.next_id.wrapping_add(1);
66 if !self.in_use[id as usize] {
67 self.in_use[id as usize] = true;
68 return Some(id);
69 }
70 if self.next_id == start {
71 return None;
72 }
73 }
74 }
75
76 fn release(&mut self, id: u8) {
77 self.in_use[id as usize] = false;
78 }
79
80 fn all_free(&self) -> bool {
81 !self.in_use.iter().any(|&used| used)
82 }
83}
84
85const MAX_TSM_DESTINATIONS: usize = 1024;
88
89pub struct Tsm {
94 config: TsmConfig,
95 allocators: HashMap<MacAddr, InvokeIdAllocator>,
96 pending: HashMap<(MacAddr, u8), oneshot::Sender<TsmResponse>>,
97}
98
99impl Tsm {
100 pub fn new(config: TsmConfig) -> Self {
101 Self {
102 config,
103 allocators: HashMap::new(),
104 pending: HashMap::new(),
105 }
106 }
107
108 pub fn config(&self) -> &TsmConfig {
109 &self.config
110 }
111
112 pub fn allocate_invoke_id(&mut self, destination_mac: &[u8]) -> Option<u8> {
116 let key = MacAddr::from_slice(destination_mac);
117 if !self.allocators.contains_key(&key) && self.allocators.len() >= MAX_TSM_DESTINATIONS {
118 return None;
119 }
120 let allocator = self
121 .allocators
122 .entry(key)
123 .or_insert_with(InvokeIdAllocator::new);
124 allocator.allocate()
125 }
126
127 pub fn release_invoke_id(&mut self, destination_mac: &[u8], invoke_id: u8) {
130 let key = MacAddr::from_slice(destination_mac);
131 if let Some(allocator) = self.allocators.get_mut(&key) {
132 allocator.release(invoke_id);
133 if allocator.all_free() {
134 self.allocators.remove(&key);
135 }
136 }
137 }
138
139 pub fn register_transaction(
142 &mut self,
143 destination_mac: MacAddr,
144 invoke_id: u8,
145 ) -> oneshot::Receiver<TsmResponse> {
146 let (tx, rx) = oneshot::channel();
147 debug_assert!(
148 !self
149 .pending
150 .contains_key(&(destination_mac.clone(), invoke_id)),
151 "duplicate TSM registration for invoke_id {}",
152 invoke_id
153 );
154 self.pending.insert((destination_mac, invoke_id), tx);
155 rx
156 }
157
158 pub fn complete_transaction(
160 &mut self,
161 source_mac: &[u8],
162 invoke_id: u8,
163 response: TsmResponse,
164 ) -> bool {
165 let key = (MacAddr::from_slice(source_mac), invoke_id);
166 if let Some(tx) = self.pending.remove(&key) {
167 self.release_invoke_id(source_mac, invoke_id);
168 let _ = tx.send(response);
169 true
170 } else {
171 false
172 }
173 }
174
175 pub fn cancel_transaction(&mut self, destination_mac: &[u8], invoke_id: u8) -> bool {
177 let key = (MacAddr::from_slice(destination_mac), invoke_id);
178 if self.pending.remove(&key).is_some() {
179 self.release_invoke_id(destination_mac, invoke_id);
180 true
181 } else {
182 false
183 }
184 }
185
186 pub fn pending_count(&self) -> usize {
187 self.pending.len()
188 }
189}
190
191pub(crate) struct TsmGuard {
196 tsm: std::sync::Arc<tokio::sync::Mutex<Tsm>>,
197 mac: MacAddr,
198 invoke_id: u8,
199 completed: bool,
200}
201
202impl TsmGuard {
203 pub(crate) fn new(
204 tsm: std::sync::Arc<tokio::sync::Mutex<Tsm>>,
205 mac: MacAddr,
206 invoke_id: u8,
207 ) -> Self {
208 Self {
209 tsm,
210 mac,
211 invoke_id,
212 completed: false,
213 }
214 }
215
216 pub(crate) fn mark_completed(&mut self) {
218 self.completed = true;
219 }
220}
221
222impl Drop for TsmGuard {
223 fn drop(&mut self) {
224 if !self.completed {
225 if let Ok(mut tsm) = self.tsm.try_lock() {
226 tsm.cancel_transaction(&self.mac, self.invoke_id);
227 }
228 }
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn allocate_invoke_id_sequential() {
238 let mut tsm = Tsm::new(TsmConfig::default());
239 let mac = [127, 0, 0, 1, 0xBA, 0xC0];
240 let id1 = tsm.allocate_invoke_id(&mac);
241 let id2 = tsm.allocate_invoke_id(&mac);
242 assert_eq!(id1, Some(0));
243 assert_eq!(id2, Some(1));
244 }
245
246 #[test]
247 fn allocate_invoke_id_per_destination() {
248 let mut tsm = Tsm::new(TsmConfig::default());
249 let mac_a = [10, 0, 0, 1, 0xBA, 0xC0];
250 let mac_b = [10, 0, 0, 2, 0xBA, 0xC0];
251 let id_a = tsm.allocate_invoke_id(&mac_a);
252 let id_b = tsm.allocate_invoke_id(&mac_b);
253 assert_eq!(id_a, Some(0));
254 assert_eq!(id_b, Some(0));
255 }
256
257 #[test]
258 fn allocate_invoke_id_wraps() {
259 let mut tsm = Tsm::new(TsmConfig::default());
260 let mac = [127, 0, 0, 1, 0xBA, 0xC0];
261 for i in 0..256 {
262 assert_eq!(tsm.allocate_invoke_id(&mac), Some(i as u8));
263 }
264 assert_eq!(tsm.allocate_invoke_id(&mac), None);
265 }
266
267 #[test]
268 fn release_makes_id_available() {
269 let mut tsm = Tsm::new(TsmConfig::default());
270 let mac = [127, 0, 0, 1, 0xBA, 0xC0];
271 let id0 = tsm.allocate_invoke_id(&mac).unwrap();
272 let id1 = tsm.allocate_invoke_id(&mac).unwrap();
273 assert_eq!(id0, 0);
274 assert_eq!(id1, 1);
275 tsm.release_invoke_id(&mac, id0);
276 let id2 = tsm.allocate_invoke_id(&mac).unwrap();
277 assert_eq!(id2, 2);
278 tsm.release_invoke_id(&mac, id1);
279 tsm.release_invoke_id(&mac, id2);
280 let id3 = tsm.allocate_invoke_id(&mac).unwrap();
281 assert_eq!(id3, 0);
282 }
283
284 #[tokio::test]
285 async fn register_and_complete_transaction() {
286 let mut tsm = Tsm::new(TsmConfig::default());
287 let mac = MacAddr::from_slice(&[127, 0, 0, 1, 0xBA, 0xC0]);
288 let invoke_id = tsm.allocate_invoke_id(&mac).unwrap();
289
290 let rx = tsm.register_transaction(mac.clone(), invoke_id);
291
292 let response = TsmResponse::ComplexAck {
293 service_data: Bytes::from_static(&[0xDE, 0xAD]),
294 };
295 let completed = tsm.complete_transaction(&mac, invoke_id, response);
296 assert!(completed);
297
298 let result = rx.await.unwrap();
299 match result {
300 TsmResponse::ComplexAck { service_data } => {
301 assert_eq!(service_data, vec![0xDE, 0xAD]);
302 }
303 _ => panic!("Expected ComplexAck"),
304 }
305 }
306
307 #[tokio::test]
308 async fn complete_unknown_transaction_returns_false() {
309 let mut tsm = Tsm::new(TsmConfig::default());
310 let mac = MacAddr::from_slice(&[127, 0, 0, 1, 0xBA, 0xC0]);
311 let completed = tsm.complete_transaction(&mac, 42, TsmResponse::SimpleAck);
312 assert!(!completed);
313 }
314
315 #[test]
316 fn cancel_transaction() {
317 let mut tsm = Tsm::new(TsmConfig::default());
318 let mac = MacAddr::from_slice(&[127, 0, 0, 1, 0xBA, 0xC0]);
319 let invoke_id = tsm.allocate_invoke_id(&mac).unwrap();
320 let _rx = tsm.register_transaction(mac.clone(), invoke_id);
321 assert_eq!(tsm.pending_count(), 1);
322
323 let cancelled = tsm.cancel_transaction(&mac, invoke_id);
324 assert!(cancelled);
325 assert_eq!(tsm.pending_count(), 0);
326 }
327}