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