1 mod utils;
2
3 use std::{rc::Rc, time::Duration};
4
5 use bluetooth_core::{
6 gatt::{
7 callbacks::{
8 CallbackResponseError, CallbackTransactionManager, GattWriteRequestType, GattWriteType,
9 RawGattDatastore, TransactionDecision,
10 },
11 ffi::AttributeBackingType,
12 ids::{AttHandle, ConnectionId, ServerId, TransactionId, TransportIndex},
13 mocks::mock_callbacks::{MockCallbackEvents, MockCallbacks},
14 },
15 packets::AttErrorCode,
16 };
17 use tokio::{sync::mpsc::UnboundedReceiver, task::spawn_local, time::Instant};
18 use utils::start_test;
19
20 const TCB_IDX: TransportIndex = TransportIndex(1);
21 const SERVER_ID: ServerId = ServerId(2);
22
23 const CONN_ID: ConnectionId = ConnectionId::new(TCB_IDX, SERVER_ID);
24
25 const HANDLE_1: AttHandle = AttHandle(3);
26 const BACKING_TYPE: AttributeBackingType = AttributeBackingType::Descriptor;
27
28 const OFFSET: u32 = 12;
29 const WRITE_REQUEST_TYPE: GattWriteRequestType = GattWriteRequestType::Prepare { offset: 7 };
30
initialize_manager_with_connection( ) -> (Rc<CallbackTransactionManager>, UnboundedReceiver<MockCallbackEvents>)31 fn initialize_manager_with_connection(
32 ) -> (Rc<CallbackTransactionManager>, UnboundedReceiver<MockCallbackEvents>) {
33 let (callbacks, callbacks_rx) = MockCallbacks::new();
34 let callback_manager = Rc::new(CallbackTransactionManager::new(Rc::new(callbacks)));
35 (callback_manager, callbacks_rx)
36 }
37
pull_trans_id(events_rx: &mut UnboundedReceiver<MockCallbackEvents>) -> TransactionId38 async fn pull_trans_id(events_rx: &mut UnboundedReceiver<MockCallbackEvents>) -> TransactionId {
39 match events_rx.recv().await.unwrap() {
40 MockCallbackEvents::OnServerRead(_, trans_id, _, _, _) => trans_id,
41 MockCallbackEvents::OnServerWrite(_, trans_id, _, _, _, _) => trans_id,
42 MockCallbackEvents::OnExecute(_, trans_id, _) => trans_id,
43 _ => unimplemented!(),
44 }
45 }
46
47 #[test]
test_read_characteristic_callback()48 fn test_read_characteristic_callback() {
49 start_test(async {
50 // arrange
51 let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
52
53 // act: start read operation
54 spawn_local(async move {
55 callback_manager
56 .get_datastore(SERVER_ID)
57 .read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE)
58 .await
59 });
60
61 // assert: verify the read callback is received
62 let MockCallbackEvents::OnServerRead(CONN_ID, _, HANDLE_1, BACKING_TYPE, OFFSET) =
63 callbacks_rx.recv().await.unwrap()
64 else {
65 unreachable!()
66 };
67 });
68 }
69
70 #[test]
test_read_characteristic_response()71 fn test_read_characteristic_response() {
72 start_test(async {
73 // arrange
74 let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
75 let data = [1, 2];
76
77 // act: start read operation
78 let datastore = callback_manager.get_datastore(SERVER_ID);
79 let pending_read =
80 spawn_local(
81 async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await },
82 );
83 // provide a response
84 let trans_id = pull_trans_id(&mut callbacks_rx).await;
85 callback_manager.send_response(CONN_ID, trans_id, Ok(data.to_vec())).unwrap();
86
87 // assert: that the supplied data was correctly read
88 assert_eq!(pending_read.await.unwrap(), Ok(data.to_vec()));
89 });
90 }
91
92 #[test]
test_sequential_reads()93 fn test_sequential_reads() {
94 start_test(async {
95 // arrange
96 let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
97 let data1 = [1, 2];
98 let data2 = [3, 4];
99
100 // act: start read operation
101 let datastore = callback_manager.get_datastore(SERVER_ID);
102 let pending_read_1 =
103 spawn_local(
104 async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await },
105 );
106 // respond to first
107 let trans_id = pull_trans_id(&mut callbacks_rx).await;
108 callback_manager.send_response(CONN_ID, trans_id, Ok(data1.to_vec())).unwrap();
109
110 // do a second read operation
111 let datastore = callback_manager.get_datastore(SERVER_ID);
112 let pending_read_2 =
113 spawn_local(
114 async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await },
115 );
116 // respond to second
117 let trans_id = pull_trans_id(&mut callbacks_rx).await;
118 callback_manager.send_response(CONN_ID, trans_id, Ok(data2.to_vec())).unwrap();
119
120 // assert: that both operations got the correct response
121 assert_eq!(pending_read_1.await.unwrap(), Ok(data1.to_vec()));
122 assert_eq!(pending_read_2.await.unwrap(), Ok(data2.to_vec()));
123 });
124 }
125
126 #[test]
test_concurrent_reads()127 fn test_concurrent_reads() {
128 start_test(async {
129 // arrange
130 let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
131 let data1 = [1, 2];
132 let data2 = [3, 4];
133
134 // act: start read operation
135 let datastore = callback_manager.get_datastore(SERVER_ID);
136 let pending_read_1 =
137 spawn_local(
138 async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await },
139 );
140
141 // do a second read operation
142 let datastore = callback_manager.get_datastore(SERVER_ID);
143 let pending_read_2 =
144 spawn_local(
145 async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await },
146 );
147
148 // respond to first
149 let trans_id = pull_trans_id(&mut callbacks_rx).await;
150 callback_manager.send_response(CONN_ID, trans_id, Ok(data1.to_vec())).unwrap();
151
152 // respond to second
153 let trans_id = pull_trans_id(&mut callbacks_rx).await;
154 callback_manager.send_response(CONN_ID, trans_id, Ok(data2.to_vec())).unwrap();
155
156 // assert: that both operations got the correct response
157 assert_eq!(pending_read_1.await.unwrap(), Ok(data1.to_vec()));
158 assert_eq!(pending_read_2.await.unwrap(), Ok(data2.to_vec()));
159 });
160 }
161
162 #[test]
test_distinct_transaction_ids()163 fn test_distinct_transaction_ids() {
164 start_test(async {
165 // arrange
166 let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
167
168 // act: start two read operations concurrently
169 let datastore = callback_manager.get_datastore(SERVER_ID);
170 spawn_local(async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await });
171 let datastore = callback_manager.get_datastore(SERVER_ID);
172 spawn_local(async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await });
173
174 // pull both trans_ids
175 let trans_id_1 = pull_trans_id(&mut callbacks_rx).await;
176 let trans_id_2 = pull_trans_id(&mut callbacks_rx).await;
177
178 // assert: that the trans_ids are distinct
179 assert_ne!(trans_id_1, trans_id_2);
180 });
181 }
182
183 #[test]
test_invalid_trans_id()184 fn test_invalid_trans_id() {
185 start_test(async {
186 // arrange
187 let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
188 let data = [1, 2];
189
190 // act: start a read operation
191 let datastore = callback_manager.get_datastore(SERVER_ID);
192 spawn_local(async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await });
193 // respond with the correct conn_id but an invalid trans_id
194 let trans_id = pull_trans_id(&mut callbacks_rx).await;
195 let invalid_trans_id = TransactionId(trans_id.0 + 1);
196 let err = callback_manager
197 .send_response(CONN_ID, invalid_trans_id, Ok(data.to_vec()))
198 .unwrap_err();
199
200 // assert
201 assert_eq!(err, CallbackResponseError::NonExistentTransaction(invalid_trans_id));
202 });
203 }
204
205 #[test]
test_invalid_conn_id()206 fn test_invalid_conn_id() {
207 start_test(async {
208 // arrange
209 let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
210 let data = [1, 2];
211
212 // act: start a read operation
213 let datastore = callback_manager.get_datastore(SERVER_ID);
214 spawn_local(async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await });
215 // respond with the correct trans_id but an invalid conn_id
216 let trans_id = pull_trans_id(&mut callbacks_rx).await;
217 let invalid_conn_id = ConnectionId(CONN_ID.0 + 1);
218 let err = callback_manager
219 .send_response(invalid_conn_id, trans_id, Ok(data.to_vec()))
220 .unwrap_err();
221
222 // assert
223 assert_eq!(err, CallbackResponseError::NonExistentTransaction(trans_id));
224 });
225 }
226
227 #[test]
test_write_characteristic_callback()228 fn test_write_characteristic_callback() {
229 start_test(async {
230 // arrange
231 let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
232
233 // act: start write operation
234 let data = [1, 2];
235 spawn_local(async move {
236 callback_manager
237 .get_datastore(SERVER_ID)
238 .write(TCB_IDX, HANDLE_1, BACKING_TYPE, WRITE_REQUEST_TYPE, &data)
239 .await
240 });
241
242 // assert: verify the write callback is received
243 let MockCallbackEvents::OnServerWrite(
244 CONN_ID,
245 _,
246 HANDLE_1,
247 BACKING_TYPE,
248 GattWriteType::Request(WRITE_REQUEST_TYPE),
249 recv_data,
250 ) = callbacks_rx.recv().await.unwrap()
251 else {
252 unreachable!()
253 };
254 assert_eq!(recv_data, data);
255 });
256 }
257
258 #[test]
test_write_characteristic_response()259 fn test_write_characteristic_response() {
260 start_test(async {
261 // arrange
262 let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
263
264 // act: start write operation
265 let data = [1, 2];
266 let datastore = callback_manager.get_datastore(SERVER_ID);
267 let pending_write = spawn_local(async move {
268 datastore
269 .write(TCB_IDX, HANDLE_1, BACKING_TYPE, GattWriteRequestType::Request, &data)
270 .await
271 });
272 // provide a response with some error code
273 let trans_id = pull_trans_id(&mut callbacks_rx).await;
274 callback_manager
275 .send_response(CONN_ID, trans_id, Err(AttErrorCode::WRITE_NOT_PERMITTED))
276 .unwrap();
277
278 // assert: that the error code was received
279 assert_eq!(pending_write.await.unwrap(), Err(AttErrorCode::WRITE_NOT_PERMITTED));
280 });
281 }
282
283 #[test]
test_response_timeout()284 fn test_response_timeout() {
285 start_test(async {
286 // arrange
287 let (callback_manager, _callbacks_rx) = initialize_manager_with_connection();
288
289 // act: start operation
290 let time_sent = Instant::now();
291 let datastore = callback_manager.get_datastore(SERVER_ID);
292 let pending_write =
293 spawn_local(
294 async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await },
295 );
296
297 // assert: that we time-out after 15s
298 assert_eq!(pending_write.await.unwrap(), Err(AttErrorCode::UNLIKELY_ERROR));
299 let time_slept = Instant::now().duration_since(time_sent);
300 assert!(time_slept > Duration::from_secs(14));
301 assert!(time_slept < Duration::from_secs(16));
302 });
303 }
304
305 #[test]
test_transaction_cleanup_after_timeout()306 fn test_transaction_cleanup_after_timeout() {
307 start_test(async {
308 // arrange
309 let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
310
311 // act: start an operation
312 let datastore = callback_manager.get_datastore(SERVER_ID);
313 let pending =
314 spawn_local(
315 async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await },
316 );
317 let trans_id = pull_trans_id(&mut callbacks_rx).await;
318 // let it time out
319 assert_eq!(pending.await.unwrap(), Err(AttErrorCode::UNLIKELY_ERROR));
320 // try responding to it now
321 let resp =
322 callback_manager.send_response(CONN_ID, trans_id, Err(AttErrorCode::INVALID_HANDLE));
323
324 // assert: the response failed
325 assert_eq!(resp, Err(CallbackResponseError::NonExistentTransaction(trans_id)));
326 });
327 }
328
329 #[test]
test_listener_hang_up()330 fn test_listener_hang_up() {
331 start_test(async {
332 // arrange
333 let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
334
335 // act: start an operation
336 let datastore = callback_manager.get_datastore(SERVER_ID);
337 let pending =
338 spawn_local(
339 async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await },
340 );
341 let trans_id = pull_trans_id(&mut callbacks_rx).await;
342 // cancel the listener, wait for it to stop
343 pending.abort();
344 pending.await.unwrap_err();
345 // try responding to it now
346 let resp =
347 callback_manager.send_response(CONN_ID, trans_id, Err(AttErrorCode::INVALID_HANDLE));
348
349 // assert: we get the expected error
350 assert_eq!(resp, Err(CallbackResponseError::ListenerHungUp(trans_id)));
351 });
352 }
353
354 #[test]
test_write_no_response_callback()355 fn test_write_no_response_callback() {
356 start_test(async {
357 // arrange
358 let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
359
360 // act: start write_no_response operation
361 let data = [1, 2];
362 callback_manager.get_datastore(SERVER_ID).write_no_response(
363 TCB_IDX,
364 HANDLE_1,
365 BACKING_TYPE,
366 &data,
367 );
368
369 // assert: verify the write callback is received
370 let MockCallbackEvents::OnServerWrite(
371 CONN_ID,
372 _,
373 HANDLE_1,
374 BACKING_TYPE,
375 GattWriteType::Command,
376 recv_data,
377 ) = callbacks_rx.recv().await.unwrap()
378 else {
379 unreachable!()
380 };
381 assert_eq!(recv_data, data);
382 });
383 }
384
385 #[test]
test_execute_characteristic_callback()386 fn test_execute_characteristic_callback() {
387 start_test(async {
388 // arrange
389 let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
390
391 // act: start execute operation
392 spawn_local(async move {
393 callback_manager
394 .get_datastore(SERVER_ID)
395 .execute(TCB_IDX, TransactionDecision::Cancel)
396 .await
397 });
398
399 // assert: verify the execute callback is received
400 let MockCallbackEvents::OnExecute(CONN_ID, _, TransactionDecision::Cancel) =
401 callbacks_rx.recv().await.unwrap()
402 else {
403 unreachable!()
404 };
405 });
406 }
407
408 #[test]
test_execute_characteristic_response()409 fn test_execute_characteristic_response() {
410 start_test(async {
411 // arrange
412 let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
413
414 // act: start execute operation
415 let cloned_manager = callback_manager.clone();
416 let pending_execute = spawn_local(async move {
417 cloned_manager
418 .get_datastore(SERVER_ID)
419 .execute(TCB_IDX, TransactionDecision::Cancel)
420 .await
421 });
422 // provide a response with some error code
423 let trans_id = pull_trans_id(&mut callbacks_rx).await;
424 callback_manager
425 .send_response(CONN_ID, trans_id, Err(AttErrorCode::WRITE_NOT_PERMITTED))
426 .unwrap();
427
428 // assert: that the error code was received
429 assert_eq!(pending_execute.await.unwrap(), Err(AttErrorCode::WRITE_NOT_PERMITTED));
430 });
431 }
432