1 use pdl_runtime::Packet;
2 use std::{
3 rc::Rc,
4 sync::{Arc, Mutex},
5 };
6
7 use bluetooth_core::{
8 core::uuid::Uuid,
9 gatt::{
10 self,
11 ffi::AttributeBackingType,
12 ids::{AdvertiserId, AttHandle, ServerId, TransportIndex},
13 mocks::{
14 mock_datastore::{MockDatastore, MockDatastoreEvents},
15 mock_transport::MockAttTransport,
16 },
17 server::{
18 gatt_database::{
19 AttPermissions, GattCharacteristicWithHandle, GattDescriptorWithHandle,
20 GattServiceWithHandle, CHARACTERISTIC_UUID, PRIMARY_SERVICE_DECLARATION_UUID,
21 },
22 isolation_manager::IsolationManager,
23 services::{
24 gap::DEVICE_NAME_UUID,
25 gatt::{
26 CLIENT_CHARACTERISTIC_CONFIGURATION_UUID, GATT_SERVICE_UUID,
27 SERVICE_CHANGE_UUID,
28 },
29 },
30 GattModule, IndicationError,
31 },
32 },
33 packets::att::{self, AttErrorCode},
34 };
35
36 use tokio::{
37 sync::mpsc::{error::TryRecvError, UnboundedReceiver},
38 task::spawn_local,
39 };
40 use utils::start_test;
41
42 mod utils;
43
44 const TCB_IDX: TransportIndex = TransportIndex(1);
45 const SERVER_ID: ServerId = ServerId(2);
46 const ADVERTISER_ID: AdvertiserId = AdvertiserId(3);
47
48 const ANOTHER_TCB_IDX: TransportIndex = TransportIndex(2);
49 const ANOTHER_SERVER_ID: ServerId = ServerId(3);
50 const ANOTHER_ADVERTISER_ID: AdvertiserId = AdvertiserId(4);
51
52 const SERVICE_HANDLE: AttHandle = AttHandle(6);
53 const CHARACTERISTIC_HANDLE: AttHandle = AttHandle(8);
54 const DESCRIPTOR_HANDLE: AttHandle = AttHandle(9);
55
56 const SERVICE_TYPE: Uuid = Uuid::new(0x0102);
57 const CHARACTERISTIC_TYPE: Uuid = Uuid::new(0x0103);
58 const DESCRIPTOR_TYPE: Uuid = Uuid::new(0x0104);
59
60 const DATA: [u8; 4] = [1, 2, 3, 4];
61 const ANOTHER_DATA: [u8; 4] = [5, 6, 7, 8];
62
start_gatt_module() -> (gatt::server::GattModule, UnboundedReceiver<(TransportIndex, att::Att)>)63 fn start_gatt_module() -> (gatt::server::GattModule, UnboundedReceiver<(TransportIndex, att::Att)>)
64 {
65 let (transport, transport_rx) = MockAttTransport::new();
66 let arbiter = IsolationManager::new();
67 let gatt = GattModule::new(Rc::new(transport), Arc::new(Mutex::new(arbiter)));
68
69 (gatt, transport_rx)
70 }
71
create_server_and_open_connection( gatt: &mut GattModule, ) -> UnboundedReceiver<MockDatastoreEvents>72 fn create_server_and_open_connection(
73 gatt: &mut GattModule,
74 ) -> UnboundedReceiver<MockDatastoreEvents> {
75 gatt.open_gatt_server(SERVER_ID).unwrap();
76 let (datastore, data_rx) = MockDatastore::new();
77 gatt.register_gatt_service(
78 SERVER_ID,
79 GattServiceWithHandle {
80 handle: SERVICE_HANDLE,
81 type_: SERVICE_TYPE,
82 characteristics: vec![GattCharacteristicWithHandle {
83 handle: CHARACTERISTIC_HANDLE,
84 type_: CHARACTERISTIC_TYPE,
85 permissions: AttPermissions::READABLE
86 | AttPermissions::WRITABLE_WITH_RESPONSE
87 | AttPermissions::INDICATE,
88 descriptors: vec![GattDescriptorWithHandle {
89 handle: DESCRIPTOR_HANDLE,
90 type_: DESCRIPTOR_TYPE,
91 permissions: AttPermissions::READABLE | AttPermissions::WRITABLE_WITH_RESPONSE,
92 }],
93 }],
94 },
95 datastore,
96 )
97 .unwrap();
98 gatt.get_isolation_manager().associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
99 gatt.on_le_connect(TCB_IDX, Some(ADVERTISER_ID)).unwrap();
100 data_rx
101 }
102
103 #[test]
test_service_read()104 fn test_service_read() {
105 start_test(async move {
106 // arrange
107 let (mut gatt, mut transport_rx) = start_gatt_module();
108
109 create_server_and_open_connection(&mut gatt);
110
111 // act
112 gatt.get_bearer(TCB_IDX).unwrap().handle_packet(
113 att::AttReadRequest { attribute_handle: SERVICE_HANDLE.into() }.try_into().unwrap(),
114 );
115 let (tcb_idx, resp) = transport_rx.recv().await.unwrap();
116
117 // assert
118 assert_eq!(tcb_idx, TCB_IDX);
119 assert_eq!(
120 Ok(resp),
121 att::AttReadResponse {
122 value: att::GattServiceDeclarationValue { uuid: SERVICE_TYPE.into() }
123 .encode_to_vec()
124 .unwrap(),
125 }
126 .try_into()
127 );
128 })
129 }
130
131 #[test]
test_server_closed_while_connected()132 fn test_server_closed_while_connected() {
133 start_test(async move {
134 // arrange: set up a connection to a closed server
135 let (mut gatt, mut transport_rx) = start_gatt_module();
136
137 // open a server and connect
138 create_server_and_open_connection(&mut gatt);
139 gatt.close_gatt_server(SERVER_ID).unwrap();
140
141 // act: read from the closed server
142 gatt.get_bearer(TCB_IDX).unwrap().handle_packet(
143 att::AttReadRequest { attribute_handle: SERVICE_HANDLE.into() }.try_into().unwrap(),
144 );
145 let (_, resp) = transport_rx.recv().await.unwrap();
146
147 // assert that the read failed, but that a response was provided
148 assert_eq!(
149 Ok(resp),
150 att::AttErrorResponse {
151 opcode_in_error: att::AttOpcode::ReadRequest,
152 handle_in_error: SERVICE_HANDLE.into(),
153 error_code: AttErrorCode::InvalidHandle
154 }
155 .try_into()
156 )
157 });
158 }
159
160 #[test]
test_characteristic_read()161 fn test_characteristic_read() {
162 start_test(async move {
163 // arrange
164 let (mut gatt, mut transport_rx) = start_gatt_module();
165 let mut data_rx = create_server_and_open_connection(&mut gatt);
166
167 // act
168 gatt.get_bearer(TCB_IDX).unwrap().handle_packet(
169 att::AttReadRequest { attribute_handle: CHARACTERISTIC_HANDLE.into() }
170 .try_into()
171 .unwrap(),
172 );
173 let tx = if let MockDatastoreEvents::Read(
174 TCB_IDX,
175 CHARACTERISTIC_HANDLE,
176 AttributeBackingType::Characteristic,
177 tx,
178 ) = data_rx.recv().await.unwrap()
179 {
180 tx
181 } else {
182 unreachable!()
183 };
184 tx.send(Ok(DATA.to_vec())).unwrap();
185 let (tcb_idx, resp) = transport_rx.recv().await.unwrap();
186
187 // assert
188 assert_eq!(tcb_idx, TCB_IDX);
189 assert_eq!(Ok(resp), att::AttReadResponse { value: DATA.into() }.try_into());
190 })
191 }
192
193 #[test]
test_characteristic_write()194 fn test_characteristic_write() {
195 start_test(async move {
196 // arrange
197 let (mut gatt, mut transport_rx) = start_gatt_module();
198 let mut data_rx = create_server_and_open_connection(&mut gatt);
199
200 // act
201 gatt.get_bearer(TCB_IDX).unwrap().handle_packet(
202 att::AttWriteRequest { handle: CHARACTERISTIC_HANDLE.into(), value: DATA.into() }
203 .try_into()
204 .unwrap(),
205 );
206 let (tx, written_data) = if let MockDatastoreEvents::Write(
207 TCB_IDX,
208 CHARACTERISTIC_HANDLE,
209 AttributeBackingType::Characteristic,
210 written_data,
211 tx,
212 ) = data_rx.recv().await.unwrap()
213 {
214 (tx, written_data)
215 } else {
216 unreachable!()
217 };
218 tx.send(Ok(())).unwrap();
219 let (tcb_idx, resp) = transport_rx.recv().await.unwrap();
220
221 // assert
222 assert_eq!(tcb_idx, TCB_IDX);
223 assert_eq!(Ok(resp), att::AttWriteResponse {}.try_into());
224 assert_eq!(&DATA, written_data.as_slice());
225 })
226 }
227
228 #[test]
test_send_indication()229 fn test_send_indication() {
230 start_test(async move {
231 // arrange
232 let (mut gatt, mut transport_rx) = start_gatt_module();
233 create_server_and_open_connection(&mut gatt);
234
235 // act
236 let pending_indication = spawn_local(
237 gatt.get_bearer(TCB_IDX).unwrap().send_indication(CHARACTERISTIC_HANDLE, DATA.into()),
238 );
239
240 let (tcb_idx, resp) = transport_rx.recv().await.unwrap();
241
242 gatt.get_bearer(TCB_IDX)
243 .unwrap()
244 .handle_packet(att::AttHandleValueConfirmation {}.try_into().unwrap());
245
246 // assert
247 assert!(matches!(pending_indication.await.unwrap(), Ok(())));
248 assert_eq!(tcb_idx, TCB_IDX);
249 assert_eq!(
250 Ok(resp),
251 att::AttHandleValueIndication {
252 handle: CHARACTERISTIC_HANDLE.into(),
253 value: DATA.into(),
254 }
255 .try_into()
256 );
257 })
258 }
259
260 #[test]
test_send_indication_and_disconnect()261 fn test_send_indication_and_disconnect() {
262 start_test(async move {
263 // arrange
264 let (mut gatt, mut transport_rx) = start_gatt_module();
265
266 create_server_and_open_connection(&mut gatt);
267
268 // act: send an indication, then disconnect
269 let pending_indication = spawn_local(
270 gatt.get_bearer(TCB_IDX)
271 .unwrap()
272 .send_indication(CHARACTERISTIC_HANDLE, vec![1, 2, 3, 4]),
273 );
274 transport_rx.recv().await.unwrap();
275 gatt.on_le_disconnect(TCB_IDX).unwrap();
276
277 // assert: the pending indication resolves appropriately
278 assert!(matches!(
279 pending_indication.await.unwrap(),
280 Err(IndicationError::ConnectionDroppedWhileWaitingForConfirmation)
281 ));
282 })
283 }
284
285 #[test]
test_write_to_descriptor()286 fn test_write_to_descriptor() {
287 start_test(async move {
288 // arrange
289 let (mut gatt, mut transport_rx) = start_gatt_module();
290 let mut data_rx = create_server_and_open_connection(&mut gatt);
291
292 // act
293 gatt.get_bearer(TCB_IDX).unwrap().handle_packet(
294 att::AttWriteRequest { handle: DESCRIPTOR_HANDLE.into(), value: DATA.into() }
295 .try_into()
296 .unwrap(),
297 );
298 let (tx, written_data) = if let MockDatastoreEvents::Write(
299 TCB_IDX,
300 DESCRIPTOR_HANDLE,
301 AttributeBackingType::Descriptor,
302 written_data,
303 tx,
304 ) = data_rx.recv().await.unwrap()
305 {
306 (tx, written_data)
307 } else {
308 unreachable!()
309 };
310 tx.send(Ok(())).unwrap();
311 let (tcb_idx, resp) = transport_rx.recv().await.unwrap();
312
313 // assert
314 assert_eq!(tcb_idx, TCB_IDX);
315 assert_eq!(Ok(resp), att::AttWriteResponse {}.try_into());
316 assert_eq!(&DATA, written_data.as_slice());
317 })
318 }
319
320 #[test]
test_multiple_servers()321 fn test_multiple_servers() {
322 start_test(async move {
323 // arrange
324 let (mut gatt, mut transport_rx) = start_gatt_module();
325 // open the default server (SERVER_ID on CONN_ID)
326 let mut data_rx_1 = create_server_and_open_connection(&mut gatt);
327 // open a second server and connect to it (ANOTHER_SERVER_ID on ANOTHER_CONN_ID)
328 let (datastore, mut data_rx_2) = MockDatastore::new();
329 gatt.open_gatt_server(ANOTHER_SERVER_ID).unwrap();
330 gatt.register_gatt_service(
331 ANOTHER_SERVER_ID,
332 GattServiceWithHandle {
333 handle: SERVICE_HANDLE,
334 type_: SERVICE_TYPE,
335 characteristics: vec![GattCharacteristicWithHandle {
336 handle: CHARACTERISTIC_HANDLE,
337 type_: CHARACTERISTIC_TYPE,
338 permissions: AttPermissions::READABLE,
339 descriptors: vec![],
340 }],
341 },
342 datastore,
343 )
344 .unwrap();
345 gatt.get_isolation_manager()
346 .associate_server_with_advertiser(ANOTHER_SERVER_ID, ANOTHER_ADVERTISER_ID);
347 gatt.on_le_connect(ANOTHER_TCB_IDX, Some(ANOTHER_ADVERTISER_ID)).unwrap();
348
349 // act: read from both connections
350 gatt.get_bearer(TCB_IDX).unwrap().handle_packet(
351 att::AttReadRequest { attribute_handle: CHARACTERISTIC_HANDLE.into() }
352 .try_into()
353 .unwrap(),
354 );
355 gatt.get_bearer(ANOTHER_TCB_IDX).unwrap().handle_packet(
356 att::AttReadRequest { attribute_handle: CHARACTERISTIC_HANDLE.into() }
357 .try_into()
358 .unwrap(),
359 );
360 // service the first read with `data`
361 let MockDatastoreEvents::Read(TCB_IDX, _, _, tx) = data_rx_1.recv().await.unwrap() else {
362 unreachable!()
363 };
364 tx.send(Ok(DATA.to_vec())).unwrap();
365 // and then the second read with `another_data`
366 let MockDatastoreEvents::Read(ANOTHER_TCB_IDX, _, _, tx) = data_rx_2.recv().await.unwrap()
367 else {
368 unreachable!()
369 };
370 tx.send(Ok(ANOTHER_DATA.to_vec())).unwrap();
371
372 // receive both response packets
373 let (tcb_idx_1, resp_1) = transport_rx.recv().await.unwrap();
374 let (tcb_idx_2, resp_2) = transport_rx.recv().await.unwrap();
375
376 // assert: the responses were routed to the correct connections
377 assert_eq!(tcb_idx_1, TCB_IDX);
378 assert_eq!(Ok(resp_1), att::AttReadResponse { value: DATA.to_vec() }.try_into());
379 assert_eq!(tcb_idx_2, ANOTHER_TCB_IDX);
380 assert_eq!(Ok(resp_2), att::AttReadResponse { value: ANOTHER_DATA.to_vec() }.try_into());
381 })
382 }
383
384 #[test]
test_read_device_name()385 fn test_read_device_name() {
386 start_test(async move {
387 // arrange
388 let (mut gatt, mut transport_rx) = start_gatt_module();
389 create_server_and_open_connection(&mut gatt);
390
391 // act: try to read the device name
392 gatt.get_bearer(TCB_IDX).unwrap().handle_packet(
393 att::AttReadByTypeRequest {
394 starting_handle: AttHandle(1).into(),
395 ending_handle: AttHandle(0xFFFF).into(),
396 attribute_type: DEVICE_NAME_UUID.into(),
397 }
398 .try_into()
399 .unwrap(),
400 );
401 let (tcb_idx, resp) = transport_rx.recv().await.unwrap();
402
403 // assert: the name should not be readable
404 assert_eq!(tcb_idx, TCB_IDX);
405 assert_eq!(
406 Ok(resp),
407 att::AttErrorResponse {
408 opcode_in_error: att::AttOpcode::ReadByTypeRequest,
409 handle_in_error: AttHandle(1).into(),
410 error_code: AttErrorCode::InsufficientAuthentication,
411 }
412 .try_into()
413 );
414 });
415 }
416
417 #[test]
test_ignored_service_change_indication()418 fn test_ignored_service_change_indication() {
419 start_test(async move {
420 // arrange
421 let (mut gatt, mut transport_rx) = start_gatt_module();
422 create_server_and_open_connection(&mut gatt);
423
424 // act: add a new service
425 let (datastore, _) = MockDatastore::new();
426 gatt.register_gatt_service(
427 SERVER_ID,
428 GattServiceWithHandle {
429 handle: AttHandle(30),
430 type_: SERVICE_TYPE,
431 characteristics: vec![],
432 },
433 datastore,
434 )
435 .unwrap();
436
437 // assert: no packets should be sent
438 assert_eq!(transport_rx.try_recv().unwrap_err(), TryRecvError::Empty);
439 });
440 }
441
442 #[test]
test_service_change_indication()443 fn test_service_change_indication() {
444 start_test(async move {
445 // arrange
446 let (mut gatt, mut transport_rx) = start_gatt_module();
447 create_server_and_open_connection(&mut gatt);
448
449 // act: discover the GATT server
450 gatt.get_bearer(TCB_IDX).unwrap().handle_packet(
451 att::AttFindByTypeValueRequest {
452 starting_handle: AttHandle::MIN.into(),
453 ending_handle: AttHandle::MAX.into(),
454 attribute_type: PRIMARY_SERVICE_DECLARATION_UUID.try_into().unwrap(),
455 attribute_value: att::UuidAsAttData { uuid: GATT_SERVICE_UUID.into() }
456 .encode_to_vec()
457 .unwrap(),
458 }
459 .try_into()
460 .unwrap(),
461 );
462 let Ok(resp): Result<att::AttFindByTypeValueResponse, _> =
463 transport_rx.recv().await.unwrap().1.try_into()
464 else {
465 unreachable!()
466 };
467 let (starting_handle, ending_handle) = (
468 resp.handles_info[0].clone().found_attribute_handle,
469 resp.handles_info[0].clone().group_end_handle,
470 );
471 // act: discover the service changed characteristic
472 gatt.get_bearer(TCB_IDX).unwrap().handle_packet(
473 att::AttReadByTypeRequest {
474 starting_handle,
475 ending_handle,
476 attribute_type: CHARACTERISTIC_UUID.into(),
477 }
478 .try_into()
479 .unwrap(),
480 );
481
482 let Ok(resp): Result<att::AttReadByTypeResponse, _> =
483 transport_rx.recv().await.unwrap().1.try_into()
484 else {
485 unreachable!()
486 };
487 let service_change_char_handle: AttHandle = resp
488 .data
489 .into_iter()
490 .find_map(|characteristic| {
491 let value = characteristic.value.to_vec();
492 let decl =
493 att::GattCharacteristicDeclarationValue::decode_full(value.as_slice()).unwrap();
494
495 if SERVICE_CHANGE_UUID == decl.uuid.try_into().unwrap() {
496 Some(decl.handle.into())
497 } else {
498 None
499 }
500 })
501 .unwrap();
502 // act: find the CCC descriptor for the service changed characteristic
503 gatt.get_bearer(TCB_IDX).unwrap().handle_packet(
504 att::AttFindInformationRequest {
505 starting_handle: service_change_char_handle.into(),
506 ending_handle: AttHandle::MAX.into(),
507 }
508 .try_into()
509 .unwrap(),
510 );
511 let Ok(resp): Result<att::AttFindInformationResponse, _> =
512 transport_rx.recv().await.unwrap().1.try_into()
513 else {
514 unreachable!()
515 };
516 let Ok(resp): Result<att::AttFindInformationShortResponse, _> = resp.try_into() else {
517 unreachable!()
518 };
519 let service_change_descriptor_handle = resp
520 .data
521 .into_iter()
522 .find_map(|attr| {
523 if attr.uuid == CLIENT_CHARACTERISTIC_CONFIGURATION_UUID.try_into().unwrap() {
524 Some(attr.handle)
525 } else {
526 None
527 }
528 })
529 .unwrap();
530 // act: register for indications on this handle
531 gatt.get_bearer(TCB_IDX).unwrap().handle_packet(
532 att::AttWriteRequest {
533 handle: service_change_descriptor_handle,
534 value: att::GattClientCharacteristicConfiguration {
535 notification: 0,
536 indication: 1,
537 }
538 .encode_to_vec()
539 .unwrap(),
540 }
541 .try_into()
542 .unwrap(),
543 );
544 let Ok(_): Result<att::AttWriteResponse, _> =
545 transport_rx.recv().await.unwrap().1.try_into()
546 else {
547 unreachable!()
548 };
549 // act: add a new service
550 let (datastore, _) = MockDatastore::new();
551 gatt.register_gatt_service(
552 SERVER_ID,
553 GattServiceWithHandle {
554 handle: AttHandle(30),
555 type_: SERVICE_TYPE,
556 characteristics: vec![],
557 },
558 datastore,
559 )
560 .unwrap();
561
562 // assert: we got an indication
563 let Ok(indication): Result<att::AttHandleValueIndication, _> =
564 transport_rx.recv().await.unwrap().1.try_into()
565 else {
566 unreachable!()
567 };
568 assert_eq!(indication.handle, service_change_char_handle.into());
569 assert_eq!(
570 Ok(indication.value.into()),
571 att::GattServiceChanged {
572 start_handle: AttHandle(30).into(),
573 end_handle: AttHandle(30).into(),
574 }
575 .encode_to_vec()
576 );
577 });
578 }
579
580 #[test]
test_closing_gatt_server_unisolates_advertiser()581 fn test_closing_gatt_server_unisolates_advertiser() {
582 start_test(async move {
583 // arrange
584 let (mut gatt, _) = start_gatt_module();
585 gatt.open_gatt_server(SERVER_ID).unwrap();
586 gatt.get_isolation_manager().associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
587
588 // act
589 gatt.close_gatt_server(SERVER_ID).unwrap();
590
591 // assert
592 let is_advertiser_isolated =
593 gatt.get_isolation_manager().is_advertiser_isolated(ADVERTISER_ID);
594 assert!(!is_advertiser_isolated);
595 });
596 }
597
598 #[test]
test_disconnection_unisolates_connection()599 fn test_disconnection_unisolates_connection() {
600 start_test(async move {
601 // arrange
602 let (mut gatt, _) = start_gatt_module();
603 create_server_and_open_connection(&mut gatt);
604
605 // act
606 gatt.on_le_disconnect(TCB_IDX).unwrap();
607
608 // assert
609 let is_connection_isolated = gatt.get_isolation_manager().is_connection_isolated(TCB_IDX);
610 assert!(!is_connection_isolated);
611 });
612 }
613