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