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