• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use log::warn;
2 use pdl_runtime::EncodeError;
3 
4 use crate::core::uuid::Uuid;
5 use crate::gatt::ids::AttHandle;
6 use crate::gatt::server::att_database::{AttAttribute, StableAttDatabase};
7 use crate::packets::att::{self, AttErrorCode};
8 
9 use super::helpers::att_grouping::find_group_end;
10 use super::helpers::att_range_filter::filter_to_range;
11 use super::helpers::payload_accumulator::PayloadAccumulator;
12 
handle_find_by_type_value_request( request: att::AttFindByTypeValueRequest, mtu: usize, db: &impl StableAttDatabase, ) -> Result<att::Att, EncodeError>13 pub async fn handle_find_by_type_value_request(
14     request: att::AttFindByTypeValueRequest,
15     mtu: usize,
16     db: &impl StableAttDatabase,
17 ) -> Result<att::Att, EncodeError> {
18     let Some(attrs) = filter_to_range(
19         request.starting_handle.clone().into(),
20         request.ending_handle.into(),
21         db.list_attributes().into_iter(),
22     ) else {
23         return att::AttErrorResponse {
24             opcode_in_error: att::AttOpcode::FindByTypeValueRequest,
25             handle_in_error: AttHandle::from(request.starting_handle).into(),
26             error_code: AttErrorCode::InvalidHandle,
27         }
28         .try_into();
29     };
30 
31     // ATT_MTU-1 limit comes from Spec 5.3 Vol 3F Sec 3.4.3.4
32     let mut matches = PayloadAccumulator::new(mtu - 1);
33 
34     for attr @ AttAttribute { handle, type_, .. } in attrs {
35         if Uuid::from(request.attribute_type.clone()) != type_ {
36             continue;
37         }
38         if let Ok(value) = db.read_attribute(handle).await {
39             if value == request.attribute_value {
40                 // match found
41                 if !matches.push(att::AttributeHandleRange {
42                     found_attribute_handle: handle.into(),
43                     group_end_handle: find_group_end(db, attr)
44                         .map(|attr| attr.handle)
45                         .unwrap_or(handle)
46                         .into(),
47                 }) {
48                     break;
49                 }
50             }
51         } else {
52             warn!("skipping {handle:?} in FindByTypeRequest since read failed")
53         }
54     }
55 
56     if matches.is_empty() {
57         att::AttErrorResponse {
58             opcode_in_error: att::AttOpcode::FindByTypeValueRequest,
59             handle_in_error: request.starting_handle,
60             error_code: AttErrorCode::AttributeNotFound,
61         }
62         .try_into()
63     } else {
64         att::AttFindByTypeValueResponse { handles_info: matches.into_vec() }.try_into()
65     }
66 }
67 
68 #[cfg(test)]
69 mod test {
70     use crate::gatt::ffi::Uuid;
71     use crate::gatt::server::gatt_database::{
72         AttPermissions, CHARACTERISTIC_UUID, PRIMARY_SERVICE_DECLARATION_UUID,
73     };
74     use crate::gatt::server::test::test_att_db::TestAttDatabase;
75     use crate::packets::att;
76 
77     use super::*;
78 
79     const UUID: Uuid = Uuid::new(0);
80     const ANOTHER_UUID: Uuid = Uuid::new(1);
81 
82     const VALUE: [u8; 2] = [1, 2];
83     const ANOTHER_VALUE: [u8; 2] = [3, 4];
84 
85     #[test]
test_uuid_match()86     fn test_uuid_match() {
87         // arrange: db all with same value, but some with different UUID
88         let db = TestAttDatabase::new(vec![
89             (
90                 AttAttribute {
91                     handle: AttHandle(3),
92                     type_: UUID,
93                     permissions: AttPermissions::READABLE,
94                 },
95                 VALUE.into(),
96             ),
97             (
98                 AttAttribute {
99                     handle: AttHandle(4),
100                     type_: ANOTHER_UUID,
101                     permissions: AttPermissions::READABLE,
102                 },
103                 VALUE.into(),
104             ),
105             (
106                 AttAttribute {
107                     handle: AttHandle(5),
108                     type_: UUID,
109                     permissions: AttPermissions::READABLE,
110                 },
111                 VALUE.into(),
112             ),
113         ]);
114 
115         // act
116         let att_view = att::AttFindByTypeValueRequest {
117             starting_handle: AttHandle(3).into(),
118             ending_handle: AttHandle(5).into(),
119             attribute_type: UUID.try_into().unwrap(),
120             attribute_value: VALUE.to_vec(),
121         };
122         let response = tokio_test::block_on(handle_find_by_type_value_request(att_view, 128, &db));
123 
124         // assert: we only matched the ones with the correct UUID
125         assert_eq!(
126             response,
127             att::AttFindByTypeValueResponse {
128                 handles_info: vec![
129                     att::AttributeHandleRange {
130                         found_attribute_handle: AttHandle(3).into(),
131                         group_end_handle: AttHandle(3).into(),
132                     },
133                     att::AttributeHandleRange {
134                         found_attribute_handle: AttHandle(5).into(),
135                         group_end_handle: AttHandle(5).into(),
136                     },
137                 ]
138             }
139             .try_into()
140         );
141     }
142 
143     #[test]
test_value_match()144     fn test_value_match() {
145         // arrange: db all with same type, but some with different value
146         let db = TestAttDatabase::new(vec![
147             (
148                 AttAttribute {
149                     handle: AttHandle(3),
150                     type_: UUID,
151                     permissions: AttPermissions::READABLE,
152                 },
153                 VALUE.into(),
154             ),
155             (
156                 AttAttribute {
157                     handle: AttHandle(4),
158                     type_: UUID,
159                     permissions: AttPermissions::READABLE,
160                 },
161                 ANOTHER_VALUE.into(),
162             ),
163             (
164                 AttAttribute {
165                     handle: AttHandle(5),
166                     type_: UUID,
167                     permissions: AttPermissions::READABLE,
168                 },
169                 VALUE.into(),
170             ),
171         ]);
172 
173         // act
174         let att_view = att::AttFindByTypeValueRequest {
175             starting_handle: AttHandle(3).into(),
176             ending_handle: AttHandle(5).into(),
177             attribute_type: UUID.try_into().unwrap(),
178             attribute_value: VALUE.to_vec(),
179         };
180         let response = tokio_test::block_on(handle_find_by_type_value_request(att_view, 128, &db));
181 
182         // assert
183         assert_eq!(
184             response,
185             att::AttFindByTypeValueResponse {
186                 handles_info: vec![
187                     att::AttributeHandleRange {
188                         found_attribute_handle: AttHandle(3).into(),
189                         group_end_handle: AttHandle(3).into(),
190                     },
191                     att::AttributeHandleRange {
192                         found_attribute_handle: AttHandle(5).into(),
193                         group_end_handle: AttHandle(5).into(),
194                     },
195                 ]
196             }
197             .try_into()
198         );
199     }
200 
201     #[test]
test_range_check()202     fn test_range_check() {
203         // arrange: empty db
204         let db = TestAttDatabase::new(vec![]);
205 
206         // act: provide an invalid handle range
207         let att_view = att::AttFindByTypeValueRequest {
208             starting_handle: AttHandle(3).into(),
209             ending_handle: AttHandle(1).into(),
210             attribute_type: UUID.try_into().unwrap(),
211             attribute_value: VALUE.to_vec(),
212         };
213         let response = tokio_test::block_on(handle_find_by_type_value_request(att_view, 128, &db));
214 
215         // assert
216         assert_eq!(
217             response,
218             att::AttErrorResponse {
219                 opcode_in_error: att::AttOpcode::FindByTypeValueRequest,
220                 handle_in_error: AttHandle(3).into(),
221                 error_code: AttErrorCode::InvalidHandle,
222             }
223             .try_into()
224         );
225     }
226 
227     #[test]
test_empty_response()228     fn test_empty_response() {
229         // arrange
230         let db = TestAttDatabase::new(vec![(
231             AttAttribute {
232                 handle: AttHandle(3),
233                 type_: UUID,
234                 permissions: AttPermissions::READABLE,
235             },
236             VALUE.into(),
237         )]);
238 
239         // act: query using a range that does not overlap with matching attributes
240         let att_view = att::AttFindByTypeValueRequest {
241             starting_handle: AttHandle(4).into(),
242             ending_handle: AttHandle(5).into(),
243             attribute_type: UUID.try_into().unwrap(),
244             attribute_value: VALUE.to_vec(),
245         };
246         let response = tokio_test::block_on(handle_find_by_type_value_request(att_view, 128, &db));
247 
248         // assert: got ATTRIBUTE_NOT_FOUND error
249         assert_eq!(
250             response,
251             att::AttErrorResponse {
252                 opcode_in_error: att::AttOpcode::FindByTypeValueRequest,
253                 handle_in_error: AttHandle(4).into(),
254                 error_code: AttErrorCode::AttributeNotFound,
255             }
256             .try_into()
257         );
258     }
259 
260     #[test]
test_grouping_uuid()261     fn test_grouping_uuid() {
262         // arrange
263         let db = TestAttDatabase::new(vec![
264             (
265                 AttAttribute {
266                     handle: AttHandle(3),
267                     type_: CHARACTERISTIC_UUID,
268                     permissions: AttPermissions::READABLE,
269                 },
270                 VALUE.into(),
271             ),
272             (
273                 AttAttribute {
274                     handle: AttHandle(4),
275                     type_: UUID,
276                     permissions: AttPermissions::READABLE,
277                 },
278                 VALUE.into(),
279             ),
280             (
281                 AttAttribute {
282                     handle: AttHandle(5),
283                     type_: PRIMARY_SERVICE_DECLARATION_UUID,
284                     permissions: AttPermissions::READABLE,
285                 },
286                 VALUE.into(),
287             ),
288         ]);
289 
290         // act: look for a particular characteristic declaration
291         let att_view = att::AttFindByTypeValueRequest {
292             starting_handle: AttHandle(3).into(),
293             ending_handle: AttHandle(4).into(),
294             attribute_type: CHARACTERISTIC_UUID.try_into().unwrap(),
295             attribute_value: VALUE.to_vec(),
296         };
297         let response = tokio_test::block_on(handle_find_by_type_value_request(att_view, 128, &db));
298 
299         // assert
300         assert_eq!(
301             response,
302             att::AttFindByTypeValueResponse {
303                 handles_info: vec![att::AttributeHandleRange {
304                     found_attribute_handle: AttHandle(3).into(),
305                     group_end_handle: AttHandle(4).into(),
306                 },]
307             }
308             .try_into()
309         );
310     }
311 
312     #[test]
test_limit_total_size()313     fn test_limit_total_size() {
314         // arrange
315         let db = TestAttDatabase::new(vec![
316             (
317                 AttAttribute {
318                     handle: AttHandle(3),
319                     type_: UUID,
320                     permissions: AttPermissions::READABLE,
321                 },
322                 VALUE.into(),
323             ),
324             (
325                 AttAttribute {
326                     handle: AttHandle(4),
327                     type_: UUID,
328                     permissions: AttPermissions::READABLE,
329                 },
330                 VALUE.into(),
331             ),
332         ]);
333 
334         // act: use MTU = 5, so we can only fit one element in the output
335         let att_view = att::AttFindByTypeValueRequest {
336             starting_handle: AttHandle(3).into(),
337             ending_handle: AttHandle(4).into(),
338             attribute_type: UUID.try_into().unwrap(),
339             attribute_value: VALUE.to_vec(),
340         };
341         let response = tokio_test::block_on(handle_find_by_type_value_request(att_view, 5, &db));
342 
343         // assert: only one of the two matches produced
344         assert_eq!(
345             response,
346             att::AttFindByTypeValueResponse {
347                 handles_info: vec![att::AttributeHandleRange {
348                     found_attribute_handle: AttHandle(3).into(),
349                     group_end_handle: AttHandle(3).into(),
350                 },]
351             }
352             .try_into()
353         );
354     }
355 }
356