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