1 //! The GATT service as defined in Core Spec 5.3 Vol 3G Section 7
2
3 use pdl_runtime::Packet;
4 use std::cell::RefCell;
5 use std::collections::HashMap;
6 use std::ops::RangeInclusive;
7 use std::rc::Rc;
8
9 use anyhow::Result;
10 use async_trait::async_trait;
11 use log::{error, warn};
12 use tokio::task::spawn_local;
13
14 use crate::core::shared_box::{WeakBox, WeakBoxRef};
15 use crate::core::uuid::Uuid;
16 use crate::gatt::callbacks::GattDatastore;
17 use crate::gatt::ffi::AttributeBackingType;
18 use crate::gatt::ids::{AttHandle, TransportIndex};
19 use crate::gatt::server::att_server_bearer::AttServerBearer;
20 use crate::gatt::server::gatt_database::{
21 AttDatabaseImpl, AttPermissions, GattCharacteristicWithHandle, GattDatabase,
22 GattDatabaseCallbacks, GattDescriptorWithHandle, GattServiceWithHandle,
23 };
24 use crate::packets::att::{self, AttErrorCode};
25
26 #[derive(Default)]
27 struct GattService {
28 clients: RefCell<HashMap<TransportIndex, ClientState>>,
29 }
30
31 #[derive(Clone)]
32 struct ClientState {
33 bearer: WeakBox<AttServerBearer<AttDatabaseImpl>>,
34 registered_for_service_change: bool,
35 }
36
37 // Must lie in the range specified by GATT_GATT_START_HANDLE from legacy stack
38 const GATT_SERVICE_HANDLE: AttHandle = AttHandle(1);
39 const SERVICE_CHANGE_HANDLE: AttHandle = AttHandle(3);
40 const SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE: AttHandle = AttHandle(4);
41
42 /// The UUID used for the GATT service (Assigned Numbers 3.4.1 Services by Name)
43 pub const GATT_SERVICE_UUID: Uuid = Uuid::new(0x1801);
44 /// The UUID used for the Service Changed characteristic (Assigned Numbers 3.8.1 Characteristics by Name)
45 pub const SERVICE_CHANGE_UUID: Uuid = Uuid::new(0x2A05);
46 /// The UUID used for the Client Characteristic Configuration descriptor (Assigned Numbers 3.7 Descriptors)
47 pub const CLIENT_CHARACTERISTIC_CONFIGURATION_UUID: Uuid = Uuid::new(0x2902);
48
49 #[async_trait(?Send)]
50 impl GattDatastore for GattService {
read( &self, tcb_idx: TransportIndex, handle: AttHandle, _: AttributeBackingType, ) -> Result<Vec<u8>, AttErrorCode>51 async fn read(
52 &self,
53 tcb_idx: TransportIndex,
54 handle: AttHandle,
55 _: AttributeBackingType,
56 ) -> Result<Vec<u8>, AttErrorCode> {
57 if handle == SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE {
58 att::GattClientCharacteristicConfiguration {
59 notification: 0,
60 indication: self
61 .clients
62 .borrow()
63 .get(&tcb_idx)
64 .map(|state| state.registered_for_service_change)
65 .unwrap_or(false)
66 .into(),
67 }
68 .encode_to_vec()
69 .map_err(|_| AttErrorCode::UnlikelyError)
70 } else {
71 unreachable!()
72 }
73 }
74
write( &self, tcb_idx: TransportIndex, handle: AttHandle, _: AttributeBackingType, data: &[u8], ) -> Result<(), AttErrorCode>75 async fn write(
76 &self,
77 tcb_idx: TransportIndex,
78 handle: AttHandle,
79 _: AttributeBackingType,
80 data: &[u8],
81 ) -> Result<(), AttErrorCode> {
82 if handle == SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE {
83 let ccc =
84 att::GattClientCharacteristicConfiguration::decode_full(data).map_err(|err| {
85 warn!("failed to parse CCC descriptor, got: {err:?}");
86 AttErrorCode::ApplicationError
87 })?;
88 let mut clients = self.clients.borrow_mut();
89 let state = clients.get_mut(&tcb_idx);
90 let Some(state) = state else {
91 error!("Received write request from disconnected client...");
92 return Err(AttErrorCode::UnlikelyError);
93 };
94 state.registered_for_service_change = ccc.indication != 0;
95 Ok(())
96 } else {
97 unreachable!()
98 }
99 }
100 }
101
102 impl GattDatabaseCallbacks for GattService {
on_le_connect( &self, tcb_idx: TransportIndex, bearer: WeakBoxRef<AttServerBearer<AttDatabaseImpl>>, )103 fn on_le_connect(
104 &self,
105 tcb_idx: TransportIndex,
106 bearer: WeakBoxRef<AttServerBearer<AttDatabaseImpl>>,
107 ) {
108 // TODO(aryarahul): registered_for_service_change may not be false for bonded devices
109 self.clients.borrow_mut().insert(
110 tcb_idx,
111 ClientState { bearer: bearer.downgrade(), registered_for_service_change: false },
112 );
113 }
114
on_le_disconnect(&self, tcb_idx: TransportIndex)115 fn on_le_disconnect(&self, tcb_idx: TransportIndex) {
116 self.clients.borrow_mut().remove(&tcb_idx);
117 }
118
on_service_change(&self, range: RangeInclusive<AttHandle>)119 fn on_service_change(&self, range: RangeInclusive<AttHandle>) {
120 for (conn_id, client) in self.clients.borrow().clone() {
121 if client.registered_for_service_change {
122 client.bearer.with(|bearer| match bearer {
123 Some(bearer) => {
124 spawn_local(
125 bearer.send_indication(
126 SERVICE_CHANGE_HANDLE,
127 att::GattServiceChanged {
128 start_handle: (*range.start()).into(),
129 end_handle: (*range.end()).into(),
130 }
131 .encode_to_vec()
132 .unwrap(),
133 ),
134 );
135 }
136 None => {
137 error!("Registered client's bearer has been destructed ({conn_id:?})")
138 }
139 });
140 }
141 }
142 }
143 }
144
145 /// Register the GATT service in the provided GATT database.
register_gatt_service(database: &mut GattDatabase) -> Result<()>146 pub fn register_gatt_service(database: &mut GattDatabase) -> Result<()> {
147 let this = Rc::new(GattService::default());
148 database.add_service_with_handles(
149 // GATT Service
150 GattServiceWithHandle {
151 handle: GATT_SERVICE_HANDLE,
152 type_: GATT_SERVICE_UUID,
153 // Service Changed Characteristic
154 characteristics: vec![GattCharacteristicWithHandle {
155 handle: SERVICE_CHANGE_HANDLE,
156 type_: SERVICE_CHANGE_UUID,
157 permissions: AttPermissions::INDICATE,
158 descriptors: vec![GattDescriptorWithHandle {
159 handle: SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE,
160 type_: CLIENT_CHARACTERISTIC_CONFIGURATION_UUID,
161 permissions: AttPermissions::READABLE | AttPermissions::WRITABLE_WITH_RESPONSE,
162 }],
163 }],
164 },
165 this.clone(),
166 )?;
167 database.register_listener(this);
168 Ok(())
169 }
170 #[cfg(test)]
171 mod test {
172 use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
173
174 use super::*;
175
176 use crate::core::shared_box::SharedBox;
177 use crate::gatt::mocks::mock_datastore::MockDatastore;
178 use crate::gatt::server::att_database::AttDatabase;
179 use crate::gatt::server::gatt_database::{
180 GattDatabase, CHARACTERISTIC_UUID, PRIMARY_SERVICE_DECLARATION_UUID,
181 };
182 use crate::packets::att;
183 use crate::utils::task::{block_on_locally, try_await};
184
185 const TCB_IDX: TransportIndex = TransportIndex(1);
186 const ANOTHER_TCB_IDX: TransportIndex = TransportIndex(2);
187 const SERVICE_TYPE: Uuid = Uuid::new(0x1234);
188 const CHARACTERISTIC_TYPE: Uuid = Uuid::new(0x5678);
189
init_gatt_db() -> SharedBox<GattDatabase>190 fn init_gatt_db() -> SharedBox<GattDatabase> {
191 let mut gatt_database = GattDatabase::new();
192 register_gatt_service(&mut gatt_database).unwrap();
193 SharedBox::new(gatt_database)
194 }
195
add_connection( gatt_database: &SharedBox<GattDatabase>, tcb_idx: TransportIndex, ) -> (AttDatabaseImpl, SharedBox<AttServerBearer<AttDatabaseImpl>>, UnboundedReceiver<att::Att>)196 fn add_connection(
197 gatt_database: &SharedBox<GattDatabase>,
198 tcb_idx: TransportIndex,
199 ) -> (AttDatabaseImpl, SharedBox<AttServerBearer<AttDatabaseImpl>>, UnboundedReceiver<att::Att>)
200 {
201 let att_database = gatt_database.get_att_database(tcb_idx);
202 let (tx, rx) = unbounded_channel();
203 let bearer = SharedBox::new(AttServerBearer::new(att_database.clone(), move |packet| {
204 tx.send(packet).unwrap();
205 Ok(())
206 }));
207 gatt_database.on_bearer_ready(tcb_idx, bearer.as_ref());
208 (att_database, bearer, rx)
209 }
210
211 #[test]
test_gatt_service_discovery()212 fn test_gatt_service_discovery() {
213 // arrange
214 let gatt_db = init_gatt_db();
215 let (att_db, _, _) = add_connection(&gatt_db, TCB_IDX);
216
217 // act: discover all services
218 let attrs = att_db.list_attributes();
219
220 // assert: 1 service + 1 char decl + 1 char value + 1 char descriptor = 4 attrs
221 assert_eq!(attrs.len(), 4);
222 // assert: value handles are correct
223 assert_eq!(attrs[0].handle, GATT_SERVICE_HANDLE);
224 assert_eq!(attrs[2].handle, SERVICE_CHANGE_HANDLE);
225 // assert: types are correct
226 assert_eq!(attrs[0].type_, PRIMARY_SERVICE_DECLARATION_UUID);
227 assert_eq!(attrs[1].type_, CHARACTERISTIC_UUID);
228 assert_eq!(attrs[2].type_, SERVICE_CHANGE_UUID);
229 assert_eq!(attrs[3].type_, CLIENT_CHARACTERISTIC_CONFIGURATION_UUID);
230 // assert: permissions of value attrs are correct
231 assert_eq!(attrs[2].permissions, AttPermissions::INDICATE);
232 assert_eq!(
233 attrs[3].permissions,
234 AttPermissions::READABLE | AttPermissions::WRITABLE_WITH_RESPONSE
235 );
236 }
237
238 #[test]
test_default_indication_subscription()239 fn test_default_indication_subscription() {
240 // arrange
241 let gatt_db = init_gatt_db();
242 let (att_db, _, _) = add_connection(&gatt_db, TCB_IDX);
243
244 // act: try to read the CCC descriptor
245 let resp =
246 block_on_locally(att_db.read_attribute(SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)).unwrap();
247
248 assert_eq!(
249 Ok(resp),
250 att::GattClientCharacteristicConfiguration { notification: 0, indication: 0 }
251 .encode_to_vec()
252 );
253 }
254
register_for_indication( att_db: &impl AttDatabase, handle: AttHandle, ) -> Result<(), AttErrorCode>255 async fn register_for_indication(
256 att_db: &impl AttDatabase,
257 handle: AttHandle,
258 ) -> Result<(), AttErrorCode> {
259 att_db
260 .write_attribute(
261 handle,
262 &att::GattClientCharacteristicConfiguration { notification: 0, indication: 1 }
263 .encode_to_vec()
264 .unwrap(),
265 )
266 .await
267 }
268
269 #[test]
test_subscribe_to_indication()270 fn test_subscribe_to_indication() {
271 // arrange
272 let gatt_db = init_gatt_db();
273 let (att_db, _, _) = add_connection(&gatt_db, TCB_IDX);
274
275 // act: register for service change indication
276 block_on_locally(register_for_indication(&att_db, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE))
277 .unwrap();
278 // read our registration status
279 let resp =
280 block_on_locally(att_db.read_attribute(SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)).unwrap();
281
282 // assert: we are registered for indications
283 assert_eq!(
284 Ok(resp),
285 att::GattClientCharacteristicConfiguration { notification: 0, indication: 1 }
286 .encode_to_vec()
287 );
288 }
289
290 #[test]
test_unsubscribe_to_indication()291 fn test_unsubscribe_to_indication() {
292 // arrange
293 let gatt_db = init_gatt_db();
294 let (att_db, _, _) = add_connection(&gatt_db, TCB_IDX);
295
296 // act: register for service change indication
297 block_on_locally(
298 att_db.write_attribute(
299 SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE,
300 &att::GattClientCharacteristicConfiguration { notification: 0, indication: 1 }
301 .encode_to_vec()
302 .unwrap(),
303 ),
304 )
305 .unwrap();
306 // act: next, unregister from this indication
307 block_on_locally(
308 att_db.write_attribute(
309 SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE,
310 &att::GattClientCharacteristicConfiguration { notification: 0, indication: 0 }
311 .encode_to_vec()
312 .unwrap(),
313 ),
314 )
315 .unwrap();
316 // read our registration status
317 let resp =
318 block_on_locally(att_db.read_attribute(SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)).unwrap();
319
320 // assert: we are not registered for indications
321 assert_eq!(
322 Ok(resp),
323 att::GattClientCharacteristicConfiguration { notification: 0, indication: 0 }
324 .encode_to_vec()
325 );
326 }
327
328 #[test]
test_single_registered_service_change_indication()329 fn test_single_registered_service_change_indication() {
330 block_on_locally(async {
331 // arrange
332 let gatt_db = init_gatt_db();
333 let (att_db, _bearer, mut rx) = add_connection(&gatt_db, TCB_IDX);
334 let (gatt_datastore, _) = MockDatastore::new();
335 let gatt_datastore = Rc::new(gatt_datastore);
336 register_for_indication(&att_db, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE).await.unwrap();
337
338 // act: register some new service
339 gatt_db
340 .add_service_with_handles(
341 GattServiceWithHandle {
342 handle: AttHandle(15),
343 type_: SERVICE_TYPE,
344 characteristics: vec![GattCharacteristicWithHandle {
345 handle: AttHandle(17),
346 type_: CHARACTERISTIC_TYPE,
347 permissions: AttPermissions::empty(),
348 descriptors: vec![],
349 }],
350 },
351 gatt_datastore,
352 )
353 .unwrap();
354
355 // assert: we received the service change indication
356 let resp = rx.recv().await.unwrap();
357 let Ok(resp): Result<att::AttHandleValueIndication, _> = resp.try_into() else {
358 unreachable!();
359 };
360 let Ok(resp) = att::GattServiceChanged::decode_full(resp.value.as_slice()) else {
361 unreachable!();
362 };
363 assert_eq!(resp.start_handle.handle, 15);
364 assert_eq!(resp.end_handle.handle, 17);
365 });
366 }
367
368 #[test]
test_multiple_registered_service_change_indication()369 fn test_multiple_registered_service_change_indication() {
370 block_on_locally(async {
371 // arrange: two connections, both registered
372 let gatt_db = init_gatt_db();
373 let (att_db_1, _bearer, mut rx1) = add_connection(&gatt_db, TCB_IDX);
374 let (att_db_2, _bearer, mut rx2) = add_connection(&gatt_db, ANOTHER_TCB_IDX);
375
376 register_for_indication(&att_db_1, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE).await.unwrap();
377 register_for_indication(&att_db_2, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE).await.unwrap();
378
379 let (gatt_datastore, _) = MockDatastore::new();
380 let gatt_datastore = Rc::new(gatt_datastore);
381
382 // act: register some new service
383 gatt_db
384 .add_service_with_handles(
385 GattServiceWithHandle {
386 handle: AttHandle(15),
387 type_: SERVICE_TYPE,
388 characteristics: vec![GattCharacteristicWithHandle {
389 handle: AttHandle(17),
390 type_: CHARACTERISTIC_TYPE,
391 permissions: AttPermissions::empty(),
392 descriptors: vec![],
393 }],
394 },
395 gatt_datastore,
396 )
397 .unwrap();
398
399 // assert: both connections received the service change indication
400 let resp1 = rx1.recv().await.unwrap();
401 let resp2 = rx2.recv().await.unwrap();
402 assert!(matches!(resp1.try_into(), Ok(att::AttHandleValueIndication { .. })));
403 assert!(matches!(resp2.try_into(), Ok(att::AttHandleValueIndication { .. })));
404 });
405 }
406
407 #[test]
test_one_unregistered_service_change_indication()408 fn test_one_unregistered_service_change_indication() {
409 block_on_locally(async {
410 // arrange: two connections, only the first is registered
411 let gatt_db = init_gatt_db();
412 let (att_db_1, _bearer, mut rx1) = add_connection(&gatt_db, TCB_IDX);
413 let (_, _bearer, mut rx2) = add_connection(&gatt_db, ANOTHER_TCB_IDX);
414
415 register_for_indication(&att_db_1, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE).await.unwrap();
416
417 let (gatt_datastore, _) = MockDatastore::new();
418 let gatt_datastore = Rc::new(gatt_datastore);
419
420 // act: register some new service
421 gatt_db
422 .add_service_with_handles(
423 GattServiceWithHandle {
424 handle: AttHandle(15),
425 type_: SERVICE_TYPE,
426 characteristics: vec![GattCharacteristicWithHandle {
427 handle: AttHandle(17),
428 type_: CHARACTERISTIC_TYPE,
429 permissions: AttPermissions::empty(),
430 descriptors: vec![],
431 }],
432 },
433 gatt_datastore,
434 )
435 .unwrap();
436
437 // assert: the first connection received the service change indication
438 let resp1 = rx1.recv().await.unwrap();
439 assert!(matches!(resp1.try_into(), Ok(att::AttHandleValueIndication { .. })));
440 // assert: the second connection received nothing
441 assert!(try_await(async move { rx2.recv().await }).await.is_err());
442 });
443 }
444
445 #[test]
test_one_disconnected_service_change_indication()446 fn test_one_disconnected_service_change_indication() {
447 block_on_locally(async {
448 // arrange: two connections, both register, but the second one disconnects
449 let gatt_db = init_gatt_db();
450 let (att_db_1, _bearer, mut rx1) = add_connection(&gatt_db, TCB_IDX);
451 let (att_db_2, bearer_2, mut rx2) = add_connection(&gatt_db, ANOTHER_TCB_IDX);
452
453 register_for_indication(&att_db_1, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE).await.unwrap();
454 register_for_indication(&att_db_2, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE).await.unwrap();
455
456 drop(bearer_2);
457 gatt_db.on_bearer_dropped(ANOTHER_TCB_IDX);
458
459 let (gatt_datastore, _) = MockDatastore::new();
460 let gatt_datastore = Rc::new(gatt_datastore);
461
462 // act: register some new service
463 gatt_db
464 .add_service_with_handles(
465 GattServiceWithHandle {
466 handle: AttHandle(15),
467 type_: SERVICE_TYPE,
468 characteristics: vec![GattCharacteristicWithHandle {
469 handle: AttHandle(17),
470 type_: CHARACTERISTIC_TYPE,
471 permissions: AttPermissions::empty(),
472 descriptors: vec![],
473 }],
474 },
475 gatt_datastore,
476 )
477 .unwrap();
478
479 // assert: the first connection received the service change indication
480 let resp1 = rx1.recv().await.unwrap();
481 assert!(matches!(resp1.try_into(), Ok(att::AttHandleValueIndication { .. })));
482 // assert: the second connection is closed
483 assert!(rx2.recv().await.is_none());
484 });
485 }
486 }
487