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