• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use crate::{
2     gatt::{
3         ids::AttHandle,
4         server::att_database::{AttAttribute, AttDatabase},
5     },
6     packets::{
7         AttChild, AttErrorCode, AttErrorResponseBuilder, AttFindInformationLongResponseBuilder,
8         AttFindInformationRequestView, AttFindInformationResponseBuilder,
9         AttFindInformationResponseFormat, AttFindInformationResponseLongEntryBuilder,
10         AttFindInformationResponseShortEntryBuilder, AttFindInformationShortResponseBuilder,
11         AttOpcode,
12     },
13 };
14 
15 use super::helpers::{att_range_filter::filter_to_range, payload_accumulator::PayloadAccumulator};
16 
handle_find_information_request<T: AttDatabase>( request: AttFindInformationRequestView<'_>, mtu: usize, db: &T, ) -> AttChild17 pub fn handle_find_information_request<T: AttDatabase>(
18     request: AttFindInformationRequestView<'_>,
19     mtu: usize,
20     db: &T,
21 ) -> AttChild {
22     let Some(attrs) = filter_to_range(
23         request.get_starting_handle().into(),
24         request.get_ending_handle().into(),
25         db.list_attributes().into_iter(),
26     ) else {
27         return AttErrorResponseBuilder {
28             opcode_in_error: AttOpcode::FIND_INFORMATION_REQUEST,
29             handle_in_error: AttHandle::from(request.get_starting_handle()).into(),
30             error_code: AttErrorCode::INVALID_HANDLE,
31         }
32         .into()
33     };
34 
35     if let Some(resp) = handle_find_information_request_short(attrs.clone(), mtu) {
36         AttFindInformationResponseBuilder {
37             format: AttFindInformationResponseFormat::SHORT,
38             _child_: resp.into(),
39         }
40         .into()
41     } else if let Some(resp) = handle_find_information_request_long(attrs, mtu) {
42         AttFindInformationResponseBuilder {
43             format: AttFindInformationResponseFormat::LONG,
44             _child_: resp.into(),
45         }
46         .into()
47     } else {
48         AttErrorResponseBuilder {
49             opcode_in_error: AttOpcode::FIND_INFORMATION_REQUEST,
50             handle_in_error: AttHandle::from(request.get_starting_handle()).into(),
51             error_code: AttErrorCode::ATTRIBUTE_NOT_FOUND,
52         }
53         .into()
54     }
55 }
56 
57 /// Returns a builder IF we can return at least one attribute, otherwise returns
58 /// None
handle_find_information_request_short( attributes: impl Iterator<Item = AttAttribute>, mtu: usize, ) -> Option<AttFindInformationShortResponseBuilder>59 fn handle_find_information_request_short(
60     attributes: impl Iterator<Item = AttAttribute>,
61     mtu: usize,
62 ) -> Option<AttFindInformationShortResponseBuilder> {
63     // Core Spec 5.3 Vol 3F 3.4.3.2 gives the ATT_MTU - 2 limit
64     let mut out = PayloadAccumulator::new(mtu - 2);
65     for AttAttribute { handle, type_: uuid, .. } in attributes {
66         if let Ok(uuid) = uuid.try_into() {
67             if out.push(AttFindInformationResponseShortEntryBuilder { handle: handle.into(), uuid })
68             {
69                 // If we successfully pushed a 16-bit UUID, continue. In all other cases, we
70                 // should break.
71                 continue;
72             }
73         }
74         break;
75     }
76 
77     if out.is_empty() {
78         None
79     } else {
80         Some(AttFindInformationShortResponseBuilder { data: out.into_boxed_slice() })
81     }
82 }
83 
handle_find_information_request_long( attributes: impl Iterator<Item = AttAttribute>, mtu: usize, ) -> Option<AttFindInformationLongResponseBuilder>84 fn handle_find_information_request_long(
85     attributes: impl Iterator<Item = AttAttribute>,
86     mtu: usize,
87 ) -> Option<AttFindInformationLongResponseBuilder> {
88     // Core Spec 5.3 Vol 3F 3.4.3.2 gives the ATT_MTU - 2 limit
89     let mut out = PayloadAccumulator::new(mtu - 2);
90 
91     for AttAttribute { handle, type_: uuid, .. } in attributes {
92         if !out.push(AttFindInformationResponseLongEntryBuilder {
93             handle: handle.into(),
94             uuid: uuid.into(),
95         }) {
96             break;
97         }
98     }
99 
100     if out.is_empty() {
101         None
102     } else {
103         Some(AttFindInformationLongResponseBuilder { data: out.into_boxed_slice() })
104     }
105 }
106 
107 #[cfg(test)]
108 mod test {
109     use crate::{
110         core::uuid::Uuid,
111         gatt::server::{gatt_database::AttPermissions, test::test_att_db::TestAttDatabase},
112         packets::AttFindInformationRequestBuilder,
113         utils::packet::build_view_or_crash,
114     };
115 
116     use super::*;
117 
118     #[test]
test_long_uuids()119     fn test_long_uuids() {
120         // arrange
121         let db = TestAttDatabase::new(vec![
122             (
123                 AttAttribute {
124                     handle: AttHandle(3),
125                     type_: Uuid::new(0x01020304),
126                     permissions: AttPermissions::READABLE,
127                 },
128                 vec![4, 5],
129             ),
130             (
131                 AttAttribute {
132                     handle: AttHandle(4),
133                     type_: Uuid::new(0x01020305),
134                     permissions: AttPermissions::READABLE,
135                 },
136                 vec![4, 5],
137             ),
138             (
139                 AttAttribute {
140                     handle: AttHandle(5),
141                     type_: Uuid::new(0x01020306),
142                     permissions: AttPermissions::READABLE,
143                 },
144                 vec![4, 5],
145             ),
146         ]);
147 
148         // act
149         let att_view = build_view_or_crash(AttFindInformationRequestBuilder {
150             starting_handle: AttHandle(3).into(),
151             ending_handle: AttHandle(4).into(),
152         });
153         let response = handle_find_information_request(att_view.view(), 128, &db);
154 
155         // assert
156         let AttChild::AttFindInformationResponse(response) = response else {
157             unreachable!("{response:?}");
158         };
159         assert_eq!(
160             response,
161             AttFindInformationResponseBuilder {
162                 format: AttFindInformationResponseFormat::LONG,
163                 _child_: AttFindInformationLongResponseBuilder {
164                     data: [
165                         AttFindInformationResponseLongEntryBuilder {
166                             handle: AttHandle(3).into(),
167                             uuid: Uuid::new(0x01020304).into(),
168                         },
169                         AttFindInformationResponseLongEntryBuilder {
170                             handle: AttHandle(4).into(),
171                             uuid: Uuid::new(0x01020305).into(),
172                         }
173                     ]
174                     .into()
175                 }
176                 .into()
177             }
178         );
179     }
180 
181     #[test]
test_short_uuids()182     fn test_short_uuids() {
183         // arrange
184         let db = TestAttDatabase::new(vec![
185             (
186                 AttAttribute {
187                     handle: AttHandle(3),
188                     type_: Uuid::new(0x0102),
189                     permissions: AttPermissions::READABLE,
190                 },
191                 vec![4, 5],
192             ),
193             (
194                 AttAttribute {
195                     handle: AttHandle(4),
196                     type_: Uuid::new(0x0103),
197                     permissions: AttPermissions::READABLE,
198                 },
199                 vec![4, 5],
200             ),
201             (
202                 AttAttribute {
203                     handle: AttHandle(5),
204                     type_: Uuid::new(0x01020306),
205                     permissions: AttPermissions::READABLE,
206                 },
207                 vec![4, 5],
208             ),
209         ]);
210 
211         // act
212         let att_view = build_view_or_crash(AttFindInformationRequestBuilder {
213             starting_handle: AttHandle(3).into(),
214             ending_handle: AttHandle(5).into(),
215         });
216         let response = handle_find_information_request(att_view.view(), 128, &db);
217 
218         // assert
219         let AttChild::AttFindInformationResponse(response) = response else {
220             unreachable!("{response:?}");
221         };
222         assert_eq!(
223             response,
224             AttFindInformationResponseBuilder {
225                 format: AttFindInformationResponseFormat::SHORT,
226                 _child_: AttFindInformationShortResponseBuilder {
227                     data: [
228                         AttFindInformationResponseShortEntryBuilder {
229                             handle: AttHandle(3).into(),
230                             uuid: Uuid::new(0x0102).try_into().unwrap(),
231                         },
232                         AttFindInformationResponseShortEntryBuilder {
233                             handle: AttHandle(4).into(),
234                             uuid: Uuid::new(0x0103).try_into().unwrap(),
235                         }
236                     ]
237                     .into()
238                 }
239                 .into()
240             }
241         );
242     }
243 
244     #[test]
test_handle_validation()245     fn test_handle_validation() {
246         // arrange: empty db
247         let db = TestAttDatabase::new(vec![]);
248 
249         // act: use an invalid handle range
250         let att_view = build_view_or_crash(AttFindInformationRequestBuilder {
251             starting_handle: AttHandle(3).into(),
252             ending_handle: AttHandle(2).into(),
253         });
254         let response = handle_find_information_request(att_view.view(), 128, &db);
255 
256         // assert: got INVALID_HANDLE
257         let AttChild::AttErrorResponse(response) = response else {
258             unreachable!("{response:?}");
259         };
260         assert_eq!(
261             response,
262             AttErrorResponseBuilder {
263                 opcode_in_error: AttOpcode::FIND_INFORMATION_REQUEST,
264                 handle_in_error: AttHandle(3).into(),
265                 error_code: AttErrorCode::INVALID_HANDLE,
266             }
267         );
268     }
269 
270     #[test]
test_limit_total_size()271     fn test_limit_total_size() {
272         // arrange
273         let db = TestAttDatabase::new(vec![
274             (
275                 AttAttribute {
276                     handle: AttHandle(3),
277                     type_: Uuid::new(0x0102),
278                     permissions: AttPermissions::READABLE,
279                 },
280                 vec![4, 5],
281             ),
282             (
283                 AttAttribute {
284                     handle: AttHandle(4),
285                     type_: Uuid::new(0x0103),
286                     permissions: AttPermissions::READABLE,
287                 },
288                 vec![4, 5],
289             ),
290         ]);
291 
292         // act: use MTU = 6, so only one entry can fit
293         let att_view = build_view_or_crash(AttFindInformationRequestBuilder {
294             starting_handle: AttHandle(3).into(),
295             ending_handle: AttHandle(5).into(),
296         });
297         let response = handle_find_information_request(att_view.view(), 6, &db);
298 
299         // assert: only one entry (not two) provided
300         let AttChild::AttFindInformationResponse(response) = response else {
301             unreachable!("{response:?}");
302         };
303         assert_eq!(
304             response,
305             AttFindInformationResponseBuilder {
306                 format: AttFindInformationResponseFormat::SHORT,
307                 _child_: AttFindInformationShortResponseBuilder {
308                     data: [AttFindInformationResponseShortEntryBuilder {
309                         handle: AttHandle(3).into(),
310                         uuid: Uuid::new(0x0102).try_into().unwrap(),
311                     },]
312                     .into()
313                 }
314                 .into()
315             }
316         );
317     }
318 
319     #[test]
test_empty_output()320     fn test_empty_output() {
321         // arrange
322         let db = TestAttDatabase::new(vec![(
323             AttAttribute {
324                 handle: AttHandle(3),
325                 type_: Uuid::new(0x0102),
326                 permissions: AttPermissions::READABLE,
327             },
328             vec![4, 5],
329         )]);
330 
331         // act: use a range that matches no attributes
332         let att_view = build_view_or_crash(AttFindInformationRequestBuilder {
333             starting_handle: AttHandle(4).into(),
334             ending_handle: AttHandle(5).into(),
335         });
336         let response = handle_find_information_request(att_view.view(), 6, &db);
337 
338         // assert: got ATTRIBUTE_NOT_FOUND
339         let AttChild::AttErrorResponse(response) = response else {
340             unreachable!("{response:?}");
341         };
342         assert_eq!(
343             response,
344             AttErrorResponseBuilder {
345                 opcode_in_error: AttOpcode::FIND_INFORMATION_REQUEST,
346                 handle_in_error: AttHandle(4).into(),
347                 error_code: AttErrorCode::ATTRIBUTE_NOT_FOUND,
348             }
349         );
350     }
351 }
352