1 //! The GAP service as defined in Core Spec 5.3 Vol 3C Section 12
2 
3 use std::rc::Rc;
4 
5 use anyhow::Result;
6 use async_trait::async_trait;
7 
8 use crate::{
9     core::uuid::Uuid,
10     gatt::{
11         callbacks::GattDatastore,
12         ffi::AttributeBackingType,
13         ids::{AttHandle, TransportIndex},
14         server::gatt_database::{
15             AttPermissions, GattCharacteristicWithHandle, GattDatabase, GattServiceWithHandle,
16         },
17     },
18     packets::AttErrorCode,
19 };
20 
21 struct GapService;
22 
23 // Must lie in the range specified by GATT_GAP_START_HANDLE from legacy stack
24 const GAP_SERVICE_HANDLE: AttHandle = AttHandle(20);
25 const DEVICE_NAME_HANDLE: AttHandle = AttHandle(22);
26 const DEVICE_APPEARANCE_HANDLE: AttHandle = AttHandle(24);
27 
28 /// The UUID used for the GAP service (Assigned Numbers 3.4.1 Services by Name)
29 pub const GAP_SERVICE_UUID: Uuid = Uuid::new(0x1800);
30 /// The UUID used for the Device Name characteristic (Assigned Numbers 3.8.1 Characteristics by Name)
31 pub const DEVICE_NAME_UUID: Uuid = Uuid::new(0x2A00);
32 /// The UUID used for the Device Appearance characteristic (Assigned Numbers 3.8.1 Characteristics by Name)
33 pub const DEVICE_APPEARANCE_UUID: Uuid = Uuid::new(0x2A01);
34 
35 #[async_trait(?Send)]
36 impl GattDatastore for GapService {
read( &self, _: TransportIndex, handle: AttHandle, _: AttributeBackingType, ) -> Result<Vec<u8>, AttErrorCode>37     async fn read(
38         &self,
39         _: TransportIndex,
40         handle: AttHandle,
41         _: AttributeBackingType,
42     ) -> Result<Vec<u8>, AttErrorCode> {
43         match handle {
44             DEVICE_NAME_HANDLE => {
45                 // for non-bonded peers, don't let them read the device name
46                 // TODO(aryarahul): support discoverability, when we make this the main GATT server
47                 Err(AttErrorCode::INSUFFICIENT_AUTHENTICATION)
48             }
49             // 0x0000 from AssignedNumbers => "Unknown"
50             DEVICE_APPEARANCE_HANDLE => Ok(vec![0x00, 0x00]),
51             _ => unreachable!("unexpected handle read"),
52         }
53     }
54 
write( &self, _: TransportIndex, _: AttHandle, _: AttributeBackingType, _: &[u8], ) -> Result<(), AttErrorCode>55     async fn write(
56         &self,
57         _: TransportIndex,
58         _: AttHandle,
59         _: AttributeBackingType,
60         _: &[u8],
61     ) -> Result<(), AttErrorCode> {
62         unreachable!("no GAP data should be writable")
63     }
64 }
65 
66 /// Register the GAP service in the provided GATT database.
register_gap_service(database: &mut GattDatabase) -> Result<()>67 pub fn register_gap_service(database: &mut GattDatabase) -> Result<()> {
68     database.add_service_with_handles(
69         // GAP Service
70         GattServiceWithHandle {
71             handle: GAP_SERVICE_HANDLE,
72             type_: GAP_SERVICE_UUID,
73             // Device Name
74             characteristics: vec![
75                 GattCharacteristicWithHandle {
76                     handle: DEVICE_NAME_HANDLE,
77                     type_: DEVICE_NAME_UUID,
78                     permissions: AttPermissions::READABLE,
79                     descriptors: vec![],
80                 },
81                 // Appearance
82                 GattCharacteristicWithHandle {
83                     handle: DEVICE_APPEARANCE_HANDLE,
84                     type_: DEVICE_APPEARANCE_UUID,
85                     permissions: AttPermissions::READABLE,
86                     descriptors: vec![],
87                 },
88             ],
89         },
90         Rc::new(GapService),
91     )
92 }
93 
94 #[cfg(test)]
95 mod test {
96     use super::*;
97 
98     use crate::{
99         core::shared_box::SharedBox,
100         gatt::server::{
101             att_database::AttDatabase,
102             gatt_database::{GattDatabase, CHARACTERISTIC_UUID, PRIMARY_SERVICE_DECLARATION_UUID},
103         },
104         utils::task::block_on_locally,
105     };
106 
107     const TCB_IDX: TransportIndex = TransportIndex(1);
108 
init_dbs() -> (SharedBox<GattDatabase>, impl AttDatabase)109     fn init_dbs() -> (SharedBox<GattDatabase>, impl AttDatabase) {
110         let mut gatt_database = GattDatabase::new();
111         register_gap_service(&mut gatt_database).unwrap();
112         let gatt_database = SharedBox::new(gatt_database);
113         let att_database = gatt_database.get_att_database(TCB_IDX);
114         (gatt_database, att_database)
115     }
116 
117     #[test]
test_gap_service_discovery()118     fn test_gap_service_discovery() {
119         // arrange
120         let (_gatt_db, att_db) = init_dbs();
121 
122         // act: discover all services
123         let attrs = att_db.list_attributes();
124 
125         // assert: 1 service + (2 characteristics) * (declaration + value attrs) = 5 attrs
126         assert_eq!(attrs.len(), 5);
127         // assert: value handles are correct
128         assert_eq!(attrs[0].handle, GAP_SERVICE_HANDLE);
129         assert_eq!(attrs[2].handle, DEVICE_NAME_HANDLE);
130         assert_eq!(attrs[4].handle, DEVICE_APPEARANCE_HANDLE);
131         // assert: types are correct
132         assert_eq!(attrs[0].type_, PRIMARY_SERVICE_DECLARATION_UUID);
133         assert_eq!(attrs[1].type_, CHARACTERISTIC_UUID);
134         assert_eq!(attrs[2].type_, DEVICE_NAME_UUID);
135         assert_eq!(attrs[3].type_, CHARACTERISTIC_UUID);
136         assert_eq!(attrs[4].type_, DEVICE_APPEARANCE_UUID);
137         // assert: permissions of value attrs are correct
138         assert_eq!(attrs[2].permissions, AttPermissions::READABLE);
139         assert_eq!(attrs[4].permissions, AttPermissions::READABLE);
140     }
141 
142     #[test]
test_read_device_name_not_discoverable()143     fn test_read_device_name_not_discoverable() {
144         // arrange
145         let (_gatt_db, att_db) = init_dbs();
146 
147         // act: try to read the device name
148         let name = block_on_locally(att_db.read_attribute(DEVICE_NAME_HANDLE));
149 
150         // assert: the name is not readable
151         assert_eq!(name, Err(AttErrorCode::INSUFFICIENT_AUTHENTICATION));
152     }
153 
154     #[test]
test_read_device_appearance()155     fn test_read_device_appearance() {
156         // arrange
157         let (_gatt_db, att_db) = init_dbs();
158 
159         // act: try to read the device name
160         let name = block_on_locally(att_db.read_attribute(DEVICE_APPEARANCE_HANDLE));
161 
162         // assert: the name is not readable
163         assert_eq!(name, Ok(vec![0x00, 0x00]));
164     }
165 }
166