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_retries: u8,
18}
19
20impl Default for TsmConfig {
21 fn default() -> Self {
22 Self {
23 apdu_timeout_ms: 6000,
24 apdu_retries: 3,
25 }
26 }
27}
28
29#[derive(Debug)]
31pub enum TsmResponse {
32 SimpleAck,
34 ComplexAck { service_data: Bytes },
36 Error { class: u32, code: u32 },
38 Reject { reason: u8 },
40 Abort { reason: u8 },
42}
43
44struct InvokeIdAllocator {
46 next_id: u8,
47 in_use: [bool; 256],
48}
49
50impl InvokeIdAllocator {
51 fn new() -> Self {
52 Self {
53 next_id: 0,
54 in_use: [false; 256],
55 }
56 }
57
58 fn allocate(&mut self) -> Option<u8> {
59 let start = self.next_id;
60 loop {
61 let id = self.next_id;
62 self.next_id = self.next_id.wrapping_add(1);
63 if !self.in_use[id as usize] {
64 self.in_use[id as usize] = true;
65 return Some(id);
66 }
67 if self.next_id == start {
68 return None; }
70 }
71 }
72
73 fn release(&mut self, id: u8) {
74 self.in_use[id as usize] = false;
75 }
76
77 fn all_free(&self) -> bool {
78 !self.in_use.iter().any(|&used| used)
79 }
80}
81
82const MAX_TSM_DESTINATIONS: usize = 1024;
85
86pub struct Tsm {
91 config: TsmConfig,
92 allocators: HashMap<MacAddr, InvokeIdAllocator>,
94 pending: HashMap<(MacAddr, u8), oneshot::Sender<TsmResponse>>,
96}
97
98impl Tsm {
99 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 {
110 &self.config
111 }
112
113 pub fn allocate_invoke_id(&mut self, destination_mac: &[u8]) -> Option<u8> {
117 let key = MacAddr::from_slice(destination_mac);
118 if !self.allocators.contains_key(&key) && self.allocators.len() >= MAX_TSM_DESTINATIONS {
119 return None; }
121 let allocator = self
122 .allocators
123 .entry(key)
124 .or_insert_with(InvokeIdAllocator::new);
125 allocator.allocate()
126 }
127
128 pub fn release_invoke_id(&mut self, destination_mac: &[u8], invoke_id: u8) {
131 let key = MacAddr::from_slice(destination_mac);
132 if let Some(allocator) = self.allocators.get_mut(&key) {
133 allocator.release(invoke_id);
134 if allocator.all_free() {
135 self.allocators.remove(&key);
136 }
137 }
138 }
139
140 pub fn register_transaction(
143 &mut self,
144 destination_mac: MacAddr,
145 invoke_id: u8,
146 ) -> oneshot::Receiver<TsmResponse> {
147 let (tx, rx) = oneshot::channel();
148 self.pending.insert((destination_mac, invoke_id), tx);
149 rx
150 }
151
152 pub fn complete_transaction(
155 &mut self,
156 source_mac: &[u8],
157 invoke_id: u8,
158 response: TsmResponse,
159 ) -> bool {
160 let key = (MacAddr::from_slice(source_mac), invoke_id);
161 if let Some(tx) = self.pending.remove(&key) {
162 self.release_invoke_id(source_mac, invoke_id);
164 let _ = tx.send(response);
166 true
167 } else {
168 false
169 }
170 }
171
172 pub fn cancel_transaction(&mut self, destination_mac: &[u8], invoke_id: u8) -> bool {
174 let key = (MacAddr::from_slice(destination_mac), invoke_id);
175 if self.pending.remove(&key).is_some() {
176 self.release_invoke_id(destination_mac, invoke_id);
177 true
178 } else {
179 false
180 }
181 }
182
183 pub fn pending_count(&self) -> usize {
185 self.pending.len()
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn allocate_invoke_id_sequential() {
195 let mut tsm = Tsm::new(TsmConfig::default());
196 let mac = [127, 0, 0, 1, 0xBA, 0xC0];
197 let id1 = tsm.allocate_invoke_id(&mac);
198 let id2 = tsm.allocate_invoke_id(&mac);
199 assert_eq!(id1, Some(0));
200 assert_eq!(id2, Some(1));
201 }
202
203 #[test]
204 fn allocate_invoke_id_per_destination() {
205 let mut tsm = Tsm::new(TsmConfig::default());
206 let mac_a = [10, 0, 0, 1, 0xBA, 0xC0];
207 let mac_b = [10, 0, 0, 2, 0xBA, 0xC0];
208 let id_a = tsm.allocate_invoke_id(&mac_a);
209 let id_b = tsm.allocate_invoke_id(&mac_b);
210 assert_eq!(id_a, Some(0));
212 assert_eq!(id_b, Some(0));
213 }
214
215 #[test]
216 fn allocate_invoke_id_wraps() {
217 let mut tsm = Tsm::new(TsmConfig::default());
218 let mac = [127, 0, 0, 1, 0xBA, 0xC0];
219 for i in 0..256 {
221 assert_eq!(tsm.allocate_invoke_id(&mac), Some(i as u8));
222 }
223 assert_eq!(tsm.allocate_invoke_id(&mac), None);
225 }
226
227 #[test]
228 fn release_makes_id_available() {
229 let mut tsm = Tsm::new(TsmConfig::default());
230 let mac = [127, 0, 0, 1, 0xBA, 0xC0];
231 let id0 = tsm.allocate_invoke_id(&mac).unwrap();
232 let id1 = tsm.allocate_invoke_id(&mac).unwrap();
233 assert_eq!(id0, 0);
234 assert_eq!(id1, 1);
235 tsm.release_invoke_id(&mac, id0);
237 let id2 = tsm.allocate_invoke_id(&mac).unwrap();
239 assert_eq!(id2, 2); tsm.release_invoke_id(&mac, id1);
241 tsm.release_invoke_id(&mac, id2);
242 let id3 = tsm.allocate_invoke_id(&mac).unwrap();
244 assert_eq!(id3, 0);
245 }
246
247 #[tokio::test]
248 async fn register_and_complete_transaction() {
249 let mut tsm = Tsm::new(TsmConfig::default());
250 let mac = MacAddr::from_slice(&[127, 0, 0, 1, 0xBA, 0xC0]);
251 let invoke_id = tsm.allocate_invoke_id(&mac).unwrap();
252
253 let rx = tsm.register_transaction(mac.clone(), invoke_id);
254
255 let response = TsmResponse::ComplexAck {
257 service_data: Bytes::from_static(&[0xDE, 0xAD]),
258 };
259 let completed = tsm.complete_transaction(&mac, invoke_id, response);
260 assert!(completed);
261
262 let result = rx.await.unwrap();
264 match result {
265 TsmResponse::ComplexAck { service_data } => {
266 assert_eq!(service_data, vec![0xDE, 0xAD]);
267 }
268 _ => panic!("Expected ComplexAck"),
269 }
270 }
271
272 #[tokio::test]
273 async fn complete_unknown_transaction_returns_false() {
274 let mut tsm = Tsm::new(TsmConfig::default());
275 let mac = MacAddr::from_slice(&[127, 0, 0, 1, 0xBA, 0xC0]);
276 let completed = tsm.complete_transaction(&mac, 42, TsmResponse::SimpleAck);
277 assert!(!completed);
278 }
279
280 #[test]
281 fn cancel_transaction() {
282 let mut tsm = Tsm::new(TsmConfig::default());
283 let mac = MacAddr::from_slice(&[127, 0, 0, 1, 0xBA, 0xC0]);
284 let invoke_id = tsm.allocate_invoke_id(&mac).unwrap();
285 let _rx = tsm.register_transaction(mac.clone(), invoke_id);
286 assert_eq!(tsm.pending_count(), 1);
287
288 let cancelled = tsm.cancel_transaction(&mac, invoke_id);
289 assert!(cancelled);
290 assert_eq!(tsm.pending_count(), 0);
291 }
292}