/src/serenity/Userland/Libraries/LibHID/ReportDescriptorParser.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2025, Sönke Holz <soenke.holz@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <AK/StringBuilder.h> |
8 | | #include <LibHID/ReportDescriptorParser.h> |
9 | | |
10 | | namespace HID { |
11 | | |
12 | | #ifndef KERNEL |
13 | | ErrorOr<void> dump_report_descriptor(ReadonlyBytes report_descriptor) |
14 | 0 | { |
15 | 0 | ItemStream stream { report_descriptor }; |
16 | |
|
17 | 0 | size_t indent_level = 0; |
18 | 0 | while (!stream.is_eof()) { |
19 | 0 | auto item_header = TRY(stream.read_item_header()); |
20 | |
|
21 | 0 | switch (item_header.type) { |
22 | 0 | case ItemType::Main: |
23 | 0 | switch (static_cast<MainItemTag>(item_header.tag)) { |
24 | 0 | case MainItemTag::Input: { |
25 | 0 | auto input_item_data = TRY(stream.read_item_data<InputItemData>(item_header)); |
26 | |
|
27 | 0 | StringBuilder args; |
28 | 0 | TRY(args.try_append(input_item_data.constant ? "Constant, "sv : "Data, "sv)); |
29 | 0 | TRY(args.try_append(input_item_data.variable ? "Variable, "sv : "Array, "sv)); |
30 | 0 | TRY(args.try_append(input_item_data.relative ? "Relative, "sv : "Absolute, "sv)); |
31 | 0 | TRY(args.try_append(input_item_data.wrap ? "Wrap, "sv : "No Wrap, "sv)); |
32 | 0 | TRY(args.try_append(input_item_data.nonlinear ? "Nonlinear, "sv : "Linear, "sv)); |
33 | 0 | TRY(args.try_append(input_item_data.no_preferred_state ? "No Preferred, "sv : "Preferred State, "sv)); |
34 | 0 | TRY(args.try_append(input_item_data.has_null_state ? "Null state, "sv : "No Null Position, "sv)); |
35 | | // Bit 7 is reserved |
36 | 0 | TRY(args.try_append(input_item_data.buffered_bytes ? "Buffered Bytes"sv : "Bit Field"sv)); |
37 | |
|
38 | 0 | outln("{: >{}}Input ({})", "", indent_level * 2, args.string_view()); |
39 | 0 | break; |
40 | 0 | } |
41 | | |
42 | 0 | case MainItemTag::Output: { |
43 | 0 | auto output_item_data = TRY(stream.read_item_data<OutputItemData>(item_header)); |
44 | |
|
45 | 0 | StringBuilder args; |
46 | 0 | TRY(args.try_append(output_item_data.constant ? "Constant, "sv : "Data, "sv)); |
47 | 0 | TRY(args.try_append(output_item_data.variable ? "Variable, "sv : "Array, "sv)); |
48 | 0 | TRY(args.try_append(output_item_data.relative ? "Relative, "sv : "Absolute, "sv)); |
49 | 0 | TRY(args.try_append(output_item_data.wrap ? "Wrap, "sv : "No Wrap, "sv)); |
50 | 0 | TRY(args.try_append(output_item_data.nonlinear ? "Nonlinear, "sv : "Linear, "sv)); |
51 | 0 | TRY(args.try_append(output_item_data.no_preferred_state ? "No Preferred, "sv : "Preferred State, "sv)); |
52 | 0 | TRY(args.try_append(output_item_data.has_null_state ? "Null state, "sv : "No Null Position, "sv)); |
53 | 0 | TRY(args.try_append(output_item_data.volatile_ ? "Volatile, "sv : "Non Volatile, "sv)); |
54 | 0 | TRY(args.try_append(output_item_data.buffered_bytes ? "Buffered Bytes"sv : "Bit Field"sv)); |
55 | |
|
56 | 0 | outln("{: >{}}Output ({})", "", indent_level * 2, args.string_view()); |
57 | 0 | break; |
58 | 0 | } |
59 | | |
60 | 0 | case MainItemTag::Feature: { |
61 | 0 | auto feature_item_data = TRY(stream.read_item_data<FeatureItemData>(item_header)); |
62 | |
|
63 | 0 | StringBuilder args; |
64 | 0 | TRY(args.try_append(feature_item_data.constant ? "Constant, "sv : "Data, "sv)); |
65 | 0 | TRY(args.try_append(feature_item_data.variable ? "Variable, "sv : "Array, "sv)); |
66 | 0 | TRY(args.try_append(feature_item_data.relative ? "Relative, "sv : "Absolute, "sv)); |
67 | 0 | TRY(args.try_append(feature_item_data.wrap ? "Wrap, "sv : "No Wrap, "sv)); |
68 | 0 | TRY(args.try_append(feature_item_data.nonlinear ? "Nonlinear, "sv : "Linear, "sv)); |
69 | 0 | TRY(args.try_append(feature_item_data.no_preferred_state ? "No Preferred, "sv : "Preferred State, "sv)); |
70 | 0 | TRY(args.try_append(feature_item_data.has_null_state ? "Null state, "sv : "No Null Position, "sv)); |
71 | 0 | TRY(args.try_append(feature_item_data.volatile_ ? "Volatile, "sv : "Non Volatile, "sv)); |
72 | 0 | TRY(args.try_append(feature_item_data.buffered_bytes ? "Buffered Bytes"sv : "Bit Field"sv)); |
73 | |
|
74 | 0 | outln("{: >{}}Feature ({})", "", indent_level * 2, args.string_view()); |
75 | 0 | break; |
76 | 0 | } |
77 | | |
78 | 0 | case MainItemTag::Collection: { |
79 | 0 | auto collection_type = static_cast<CollectionType>(TRY(stream.read_item_data_unsigned(item_header))); |
80 | 0 | outln("{: >{}}Collection ({:#x})", "", indent_level * 2, to_underlying(collection_type)); |
81 | 0 | indent_level++; |
82 | 0 | break; |
83 | 0 | } |
84 | | |
85 | 0 | case MainItemTag::EndCollection: |
86 | 0 | indent_level--; |
87 | 0 | outln("{: >{}}End Collection", "", indent_level * 2); |
88 | 0 | break; |
89 | | |
90 | 0 | default: |
91 | 0 | return Error::from_string_view_or_print_error_and_return_errno("Unknown main item tag"sv, EINVAL); |
92 | 0 | } |
93 | 0 | break; |
94 | | |
95 | 0 | case ItemType::Global: |
96 | 0 | switch (static_cast<GlobalItemTag>(item_header.tag)) { |
97 | 0 | case GlobalItemTag::UsagePage: |
98 | | // TODO: Pretty print the usage page name. |
99 | 0 | outln("{: >{}}Usage Page ({:#x})", "", indent_level * 2, TRY(stream.read_item_data_unsigned(item_header))); |
100 | 0 | break; |
101 | | |
102 | 0 | case GlobalItemTag::LogicalMinimum: |
103 | 0 | outln("{: >{}}Logical Minimum ({})", "", indent_level * 2, TRY(stream.read_item_data_signed(item_header))); |
104 | 0 | break; |
105 | | |
106 | 0 | case GlobalItemTag::LogicalMaximum: |
107 | 0 | outln("{: >{}}Logical Maximum ({})", "", indent_level * 2, TRY(stream.read_item_data_signed(item_header))); |
108 | 0 | break; |
109 | | |
110 | 0 | case GlobalItemTag::PhysicalMinimum: |
111 | 0 | outln("{: >{}}Physical Minimum ({})", "", indent_level * 2, TRY(stream.read_item_data_signed(item_header))); |
112 | 0 | break; |
113 | | |
114 | 0 | case GlobalItemTag::PhysicalMaximum: |
115 | 0 | outln("{: >{}}Physical Maximum ({})", "", indent_level * 2, TRY(stream.read_item_data_signed(item_header))); |
116 | 0 | break; |
117 | | |
118 | 0 | case GlobalItemTag::UnitExponent: |
119 | 0 | outln("{: >{}}Unit Exponent ({})", "", indent_level * 2, TRY(stream.read_item_data_signed(item_header))); |
120 | 0 | break; |
121 | | |
122 | 0 | case GlobalItemTag::Unit: |
123 | | // TODO: Pretty print the unit. |
124 | 0 | outln("{: >{}}Unit ({:#x})", "", indent_level * 2, TRY(stream.read_item_data_unsigned(item_header))); |
125 | 0 | break; |
126 | | |
127 | 0 | case GlobalItemTag::ReportSize: |
128 | 0 | outln("{: >{}}Report Size ({})", "", indent_level * 2, TRY(stream.read_item_data_unsigned(item_header))); |
129 | 0 | break; |
130 | | |
131 | 0 | case GlobalItemTag::ReportID: |
132 | 0 | outln("{: >{}}Report ID ({:#x})", "", indent_level * 2, TRY(stream.read_item_data_unsigned(item_header))); |
133 | 0 | break; |
134 | | |
135 | 0 | case GlobalItemTag::ReportCount: |
136 | 0 | outln("{: >{}}Report Count ({})", "", indent_level * 2, TRY(stream.read_item_data_unsigned(item_header))); |
137 | 0 | break; |
138 | | |
139 | 0 | case GlobalItemTag::Push: |
140 | 0 | outln("{: >{}}Push", "", indent_level * 2); |
141 | 0 | break; |
142 | | |
143 | 0 | case GlobalItemTag::Pop: |
144 | 0 | outln("{: >{}}Pop", "", indent_level * 2); |
145 | 0 | break; |
146 | | |
147 | 0 | default: |
148 | 0 | return Error::from_string_view_or_print_error_and_return_errno("Unknown global item tag"sv, EINVAL); |
149 | 0 | } |
150 | 0 | break; |
151 | | |
152 | 0 | case ItemType::Local: |
153 | 0 | switch (static_cast<LocalItemTag>(item_header.tag)) { |
154 | 0 | case LocalItemTag::Usage: |
155 | | // TODO: Pretty print the usage name. |
156 | 0 | outln("{: >{}}Usage ({:#x})", "", indent_level * 2, TRY(stream.read_item_data_unsigned(item_header))); |
157 | 0 | break; |
158 | | |
159 | 0 | case LocalItemTag::UsageMinimum: |
160 | 0 | outln("{: >{}}Usage Minimum ({:#x})", "", indent_level * 2, TRY(stream.read_item_data_unsigned(item_header))); |
161 | 0 | break; |
162 | | |
163 | 0 | case LocalItemTag::UsageMaximum: |
164 | 0 | outln("{: >{}}Usage Maximum ({:#x})", "", indent_level * 2, TRY(stream.read_item_data_unsigned(item_header))); |
165 | 0 | break; |
166 | | |
167 | 0 | case LocalItemTag::DesignatorIndex: |
168 | 0 | outln("{: >{}}Designator Index ({:#x})", "", indent_level * 2, TRY(stream.read_item_data_unsigned(item_header))); |
169 | 0 | break; |
170 | | |
171 | 0 | case LocalItemTag::DesignatorMinimum: |
172 | 0 | outln("{: >{}}Designator Minimum ({:#x})", "", indent_level * 2, TRY(stream.read_item_data_unsigned(item_header))); |
173 | 0 | break; |
174 | | |
175 | 0 | case LocalItemTag::DesignatorMaximum: |
176 | 0 | outln("{: >{}}Designator Maximum ({:#x})", "", indent_level * 2, TRY(stream.read_item_data_unsigned(item_header))); |
177 | 0 | break; |
178 | | |
179 | 0 | case LocalItemTag::StringIndex: |
180 | 0 | outln("{: >{}}String Index ({:#x})", "", indent_level * 2, TRY(stream.read_item_data_unsigned(item_header))); |
181 | 0 | break; |
182 | | |
183 | 0 | case LocalItemTag::StringMinimum: |
184 | 0 | outln("{: >{}}String Minimum ({:#x})", "", indent_level * 2, TRY(stream.read_item_data_unsigned(item_header))); |
185 | 0 | break; |
186 | | |
187 | 0 | case LocalItemTag::StringMaximum: |
188 | 0 | outln("{: >{}}String Maximum ({:#x})", "", indent_level * 2, TRY(stream.read_item_data_unsigned(item_header))); |
189 | 0 | break; |
190 | | |
191 | 0 | case LocalItemTag::Delimiter: |
192 | 0 | outln("{: >{}}Delimiter ({})", "", indent_level * 2, TRY(stream.read_item_data_unsigned(item_header))); |
193 | 0 | break; |
194 | | |
195 | 0 | default: |
196 | 0 | return Error::from_string_view_or_print_error_and_return_errno("Unknown local item tag"sv, EINVAL); |
197 | 0 | } |
198 | 0 | break; |
199 | | |
200 | 0 | case ItemType::Reserved: |
201 | 0 | if (item_header.tag == TAG_LONG_ITEM) |
202 | 0 | return Error::from_string_view_or_print_error_and_return_errno("Long items are not supported"sv, EINVAL); |
203 | 0 | else |
204 | 0 | return Error::from_string_view_or_print_error_and_return_errno("Unsupported reserved item"sv, EINVAL); |
205 | | |
206 | 0 | default: |
207 | 0 | return Error::from_string_view_or_print_error_and_return_errno("Unknown item type"sv, EINVAL); |
208 | 0 | } |
209 | 0 | } |
210 | | |
211 | 0 | return {}; |
212 | 0 | } |
213 | | #endif |
214 | | |
215 | | ReportDescriptorParser::ReportDescriptorParser(ReadonlyBytes data) |
216 | 1.47k | : m_stream(data) |
217 | 1.47k | { |
218 | 1.47k | } |
219 | | |
220 | | ErrorOr<ParsedReportDescriptor> ReportDescriptorParser::parse() |
221 | 1.47k | { |
222 | 27.0M | while (!m_stream.is_eof()) { |
223 | 27.0M | auto item_header = TRY(m_stream.read_item_header()); |
224 | 27.0M | switch (item_header.type) { |
225 | 3.34M | case ItemType::Main: |
226 | 3.34M | switch (static_cast<MainItemTag>(item_header.tag)) { |
227 | 12.8k | case MainItemTag::Input: { |
228 | 12.8k | auto input_item_data = TRY(m_stream.read_item_data<InputItemData>(item_header)); |
229 | 12.8k | TRY(add_report_fields(FieldType::Input, input_item_data)); |
230 | 12.7k | m_input_output_or_feature_item_seen.set(); |
231 | 12.7k | break; |
232 | 12.8k | } |
233 | | |
234 | 390 | case MainItemTag::Output: { |
235 | 390 | (void)TRY(m_stream.read_item_data<OutputItemData>(item_header)); |
236 | | // TODO: Handle Output items. |
237 | 387 | m_input_output_or_feature_item_seen.set(); |
238 | 387 | break; |
239 | 390 | } |
240 | | |
241 | 433 | case MainItemTag::Feature: { |
242 | 433 | (void)TRY(m_stream.read_item_data<FeatureItemData>(item_header)); |
243 | | // TODO: Handle Feature items. |
244 | 431 | m_input_output_or_feature_item_seen.set(); |
245 | 431 | break; |
246 | 433 | } |
247 | | |
248 | 1.66M | case MainItemTag::Collection: { |
249 | 1.66M | m_current_collection_tree_depth++; |
250 | | |
251 | | // Prevent generating collection trees with a huge depth. |
252 | 1.66M | if (m_current_collection_tree_depth > 50) |
253 | 1 | return Error::from_string_view_or_print_error_and_return_errno("Report descriptor defines more than 50 nested collections"sv, E2BIG); |
254 | | |
255 | 1.66M | auto collection_type = static_cast<CollectionType>(TRY(m_stream.read_item_data_unsigned(item_header))); |
256 | | |
257 | | // 6.2.2.6 Collection, End Collection Items: "[A] Usage item tag must be associated with any collection [...]." |
258 | 1.66M | if (m_current_item_state_table.local.usages.size() == 0) |
259 | 24 | return Error::from_string_view_or_print_error_and_return_errno("Collection item without a preceding Usage item"sv, EINVAL); |
260 | | |
261 | 1.66M | if (m_current_item_state_table.local.usages.size() > 1) |
262 | 40 | return Error::from_string_view_or_print_error_and_return_errno("Collection item with multiple usages"sv, EINVAL); |
263 | | |
264 | 1.66M | auto usage = m_current_item_state_table.local.usages.first(); |
265 | | |
266 | 1.66M | if (m_current_collection == nullptr) { |
267 | | // 8.4 Report Constraints: "Each top level collection must be an application collection and reports may not span more than one top level collection." |
268 | | // FIXME: Maybe also check for the second condition somehow? We also expect that behaviour, as each ApplicationCollection has a HashMap of its reports. |
269 | 614k | if (collection_type != CollectionType::Application) |
270 | 21 | return Error::from_string_view_or_print_error_and_return_errno("Top-level collection with type != Application"sv, EINVAL); |
271 | | |
272 | 614k | ApplicationCollection new_collection {}; |
273 | 614k | new_collection.parent = m_current_collection; |
274 | 614k | new_collection.type = collection_type; |
275 | 614k | new_collection.usage = usage; |
276 | | |
277 | 614k | TRY(m_parsed.application_collections.try_empend(move(new_collection))); |
278 | 614k | m_current_collection = &m_parsed.application_collections.last(); |
279 | 614k | m_current_application_collection = &m_parsed.application_collections.last(); |
280 | 1.05M | } else { |
281 | 1.05M | Collection new_collection {}; |
282 | 1.05M | new_collection.parent = m_current_collection; |
283 | 1.05M | new_collection.type = collection_type; |
284 | 1.05M | new_collection.usage = usage; |
285 | | |
286 | 1.05M | TRY(m_current_collection->child_collections.try_empend(move(new_collection))); |
287 | 1.05M | m_current_collection = &m_current_collection->child_collections.last(); |
288 | 1.05M | } |
289 | | |
290 | 1.66M | break; |
291 | 1.66M | } |
292 | | |
293 | 1.66M | case MainItemTag::EndCollection: |
294 | 1.66M | if (m_current_collection == nullptr) |
295 | 1 | return Error::from_string_view_or_print_error_and_return_errno("End Collection item with a corresponding Collection item"sv, EINVAL); |
296 | | |
297 | 1.66M | m_current_collection = m_current_collection->parent; |
298 | 1.66M | m_current_collection_tree_depth--; |
299 | | |
300 | 1.66M | break; |
301 | | |
302 | 37 | default: |
303 | 37 | return Error::from_string_view_or_print_error_and_return_errno("Unknown main item tag"sv, EINVAL); |
304 | 3.34M | } |
305 | | |
306 | | // Reset the Local items in the current item state table. |
307 | 3.34M | m_current_item_state_table.local = {}; |
308 | 3.34M | break; |
309 | | |
310 | 7.10M | case ItemType::Global: |
311 | 7.10M | switch (static_cast<GlobalItemTag>(item_header.tag)) { |
312 | 112k | case GlobalItemTag::UsagePage: |
313 | 112k | m_current_item_state_table.global.usage_page = TRY(m_stream.read_item_data_unsigned(item_header)); |
314 | 112k | break; |
315 | | |
316 | 991 | case GlobalItemTag::LogicalMinimum: |
317 | 991 | m_current_item_state_table.global.logical_minimum = TRY(m_stream.read_item_data_signed(item_header)); |
318 | 990 | break; |
319 | | |
320 | 110k | case GlobalItemTag::LogicalMaximum: |
321 | 110k | m_current_item_state_table.global.logical_maximum = TRY(m_stream.read_item_data_signed(item_header)); |
322 | 110k | break; |
323 | | |
324 | 425 | case GlobalItemTag::PhysicalMinimum: |
325 | 425 | m_current_item_state_table.global.physical_maximum = TRY(m_stream.read_item_data_signed(item_header)); |
326 | 424 | break; |
327 | | |
328 | 506 | case GlobalItemTag::PhysicalMaximum: |
329 | 506 | m_current_item_state_table.global.physical_minimum = TRY(m_stream.read_item_data_signed(item_header)); |
330 | 505 | break; |
331 | | |
332 | 2.22k | case GlobalItemTag::UnitExponent: |
333 | 2.22k | m_current_item_state_table.global.unit_exponent = TRY(m_stream.read_item_data_signed(item_header)); |
334 | 2.22k | break; |
335 | | |
336 | 4.57k | case GlobalItemTag::Unit: |
337 | 4.57k | m_current_item_state_table.global.unit = TRY(m_stream.read_item_data_unsigned(item_header)); |
338 | 4.57k | break; |
339 | | |
340 | 1.57k | case GlobalItemTag::ReportSize: { |
341 | 1.57k | auto report_size = TRY(m_stream.read_item_data_unsigned(item_header)); |
342 | | |
343 | | // 8.4 Report Constraints: An item field cannot span more than 4 bytes in a report. For example, a 32-bit item must start on a byte boundary to satisfy this condition. |
344 | 1.57k | if (report_size > 32) |
345 | 51 | return Error::from_string_view_or_print_error_and_return_errno("Report Size > 32"sv, EINVAL); |
346 | | |
347 | 1.52k | m_current_item_state_table.global.report_size = report_size; |
348 | 1.52k | break; |
349 | 1.57k | } |
350 | | |
351 | 10.9k | case GlobalItemTag::ReportID: { |
352 | 10.9k | if (!m_parsed.uses_report_ids && m_input_output_or_feature_item_seen.was_set()) |
353 | 2 | return Error::from_string_view_or_print_error_and_return_errno("Report ID item after the first Input/Output/Feature Item"sv, EINVAL); |
354 | | |
355 | 10.9k | m_parsed.uses_report_ids = true; |
356 | | |
357 | 10.9k | u8 const report_id = TRY(m_stream.read_item_data_unsigned(item_header)); |
358 | 10.9k | if (report_id == 0) |
359 | 2 | return Error::from_string_view_or_print_error_and_return_errno("Report ID item uses reserved ID 0"sv, EINVAL); |
360 | | |
361 | 10.9k | m_current_item_state_table.global.report_id = report_id; |
362 | 10.9k | break; |
363 | 10.9k | } |
364 | | |
365 | 791 | case GlobalItemTag::ReportCount: |
366 | 791 | m_current_item_state_table.global.report_count = TRY(m_stream.read_item_data_unsigned(item_header)); |
367 | 789 | break; |
368 | | |
369 | 6.84M | case GlobalItemTag::Push: |
370 | 6.84M | TRY(m_item_state_table_stack.try_append(TRY(m_current_item_state_table.clone()))); |
371 | 6.84M | break; |
372 | | |
373 | 18.4k | case GlobalItemTag::Pop: |
374 | 18.4k | if (m_item_state_table_stack.is_empty()) |
375 | 1 | return Error::from_string_view_or_print_error_and_return_errno("Pop item without a corresponding Push item"sv, EINVAL); |
376 | | |
377 | 18.4k | m_current_item_state_table = m_item_state_table_stack.take_last(); |
378 | 18.4k | break; |
379 | | |
380 | 3 | default: |
381 | 3 | return Error::from_string_view_or_print_error_and_return_errno("Unknown global item tag"sv, EINVAL); |
382 | 7.10M | } |
383 | 7.10M | break; |
384 | | |
385 | 16.6M | case ItemType::Local: |
386 | 16.6M | switch (static_cast<LocalItemTag>(item_header.tag)) { |
387 | 16.5M | case LocalItemTag::Usage: { |
388 | 16.5M | u32 usage = TRY(m_stream.read_item_data_unsigned(item_header)); |
389 | 16.5M | if (item_header.real_size() != 4) { |
390 | 16.5M | if (!m_current_item_state_table.global.usage_page.has_value()) |
391 | 3 | return Error::from_string_view_or_print_error_and_return_errno("Usage item without a preceding Usage Page item"sv, EINVAL); // FIXME: Are we supposed to handle this? |
392 | 16.5M | usage |= (static_cast<u32>(m_current_item_state_table.global.usage_page.value()) << 16); |
393 | 16.5M | } |
394 | 16.5M | TRY(m_current_item_state_table.local.usages.try_append(usage)); |
395 | 16.5M | break; |
396 | 16.5M | } |
397 | | |
398 | 5.46k | case LocalItemTag::UsageMinimum: { |
399 | 5.46k | u32 usage_minimum = TRY(m_stream.read_item_data_unsigned(item_header)); |
400 | 5.46k | if (item_header.real_size() != 4) { |
401 | 5.12k | if (!m_current_item_state_table.global.usage_page.has_value()) |
402 | 3 | return Error::from_string_view_or_print_error_and_return_errno("Usage Minimum item without a preceding Usage Page item"sv, EINVAL); // FIXME: Are we supposed to handle this? |
403 | | |
404 | 5.12k | usage_minimum |= (static_cast<u32>(m_current_item_state_table.global.usage_page.value()) << 16); |
405 | 5.12k | } |
406 | 5.46k | m_current_item_state_table.local.usage_minimum = usage_minimum; |
407 | 5.46k | break; |
408 | 5.46k | } |
409 | | |
410 | 7.79k | case LocalItemTag::UsageMaximum: { |
411 | 7.79k | u32 usage_maximum = TRY(m_stream.read_item_data_unsigned(item_header)); |
412 | 7.79k | if (item_header.real_size() != 4) { |
413 | 7.52k | if (!m_current_item_state_table.global.usage_page.has_value()) |
414 | 4 | return Error::from_string_view_or_print_error_and_return_errno("Usage Maximum item without a preceding Usage Page item"sv, EINVAL); // FIXME: Are we supposed to handle this? |
415 | | |
416 | 7.52k | usage_maximum |= (static_cast<u32>(m_current_item_state_table.global.usage_page.value()) << 16); |
417 | 7.52k | } |
418 | 7.79k | m_current_item_state_table.local.usage_maximum = usage_maximum; |
419 | 7.79k | break; |
420 | 7.79k | } |
421 | | |
422 | 6.02k | case LocalItemTag::DesignatorIndex: |
423 | 6.02k | m_current_item_state_table.local.designator_index = TRY(m_stream.read_item_data_unsigned(item_header)); |
424 | 6.01k | break; |
425 | | |
426 | 633 | case LocalItemTag::DesignatorMinimum: |
427 | 633 | m_current_item_state_table.local.degignator_minimum = TRY(m_stream.read_item_data_unsigned(item_header)); |
428 | 632 | break; |
429 | | |
430 | 2.88k | case LocalItemTag::DesignatorMaximum: |
431 | 2.88k | m_current_item_state_table.local.designator_maximum = TRY(m_stream.read_item_data_unsigned(item_header)); |
432 | 2.87k | break; |
433 | | |
434 | 520 | case LocalItemTag::StringIndex: |
435 | 520 | m_current_item_state_table.local.string_index = TRY(m_stream.read_item_data_unsigned(item_header)); |
436 | 519 | break; |
437 | | |
438 | 12.0k | case LocalItemTag::StringMinimum: |
439 | 12.0k | m_current_item_state_table.local.string_minimum = TRY(m_stream.read_item_data_unsigned(item_header)); |
440 | 12.0k | break; |
441 | | |
442 | 58.1k | case LocalItemTag::StringMaximum: |
443 | 58.1k | m_current_item_state_table.local.string_maximum = TRY(m_stream.read_item_data_unsigned(item_header)); |
444 | 58.1k | break; |
445 | | |
446 | 569 | case LocalItemTag::Delimiter: |
447 | 569 | (void)TRY(m_stream.read_item_data_unsigned(item_header)); |
448 | | // TODO: Handle Delimiter items. |
449 | 566 | break; |
450 | | |
451 | 3 | default: |
452 | 3 | return Error::from_string_view_or_print_error_and_return_errno("Unknown local item tag"sv, EINVAL); |
453 | 16.6M | } |
454 | 16.6M | break; |
455 | | |
456 | 16.6M | case ItemType::Reserved: |
457 | 32 | if (item_header.tag == TAG_LONG_ITEM) |
458 | 3 | return Error::from_string_view_or_print_error_and_return_errno("Long items are not supported"sv, EINVAL); |
459 | 29 | else |
460 | 29 | return Error::from_string_view_or_print_error_and_return_errno("Unsupported reserved item"sv, EINVAL); |
461 | | |
462 | 0 | default: |
463 | 0 | return Error::from_string_view_or_print_error_and_return_errno("Unknown item type"sv, EINVAL); |
464 | 27.0M | } |
465 | 27.0M | } |
466 | | |
467 | 1.10k | return move(m_parsed); |
468 | 1.47k | } |
469 | | |
470 | | template<typename ItemData> |
471 | | ErrorOr<void> ReportDescriptorParser::add_report_fields(FieldType field_type, ItemData item_data) |
472 | 12.8k | { |
473 | 12.8k | if (m_current_collection == nullptr) |
474 | 1 | return Error::from_string_view_or_print_error_and_return_errno("Input item without a preceding collection item"sv, EINVAL); |
475 | | |
476 | | // We always should have a current application collection if m_current_collection != nullptr. |
477 | 12.8k | VERIFY(m_current_application_collection != nullptr); |
478 | | |
479 | 12.8k | if (m_current_item_state_table.local.usage_minimum.has_value() && !m_current_item_state_table.local.usage_maximum.has_value()) |
480 | 1 | return Error::from_string_view_or_print_error_and_return_errno("Usage Minimum item without a corresponding Usage Maximum item"sv, EINVAL); |
481 | | |
482 | 12.8k | if (m_current_item_state_table.local.usage_maximum.has_value() && !m_current_item_state_table.local.usage_minimum.has_value()) |
483 | 1 | return Error::from_string_view_or_print_error_and_return_errno("Usage Maximum item without a corresponding Usage Minimum item"sv, EINVAL); |
484 | | |
485 | 12.8k | if (item_data.variable && m_current_item_state_table.local.usage_minimum.has_value() && m_current_item_state_table.local.usage_maximum.has_value()) { |
486 | 251 | if (m_current_item_state_table.local.usage_maximum.value() - m_current_item_state_table.local.usage_minimum.value() + 1 != m_current_item_state_table.global.report_count) |
487 | 49 | return Error::from_string_view_or_print_error_and_return_errno("Variable item with Usage Maximum - Usage Minimum + 1 != Report Count"sv, EINVAL); // TODO: How are we supposed to handle this? |
488 | 251 | } |
489 | | |
490 | 12.8k | if (!m_current_item_state_table.global.logical_minimum.has_value()) |
491 | 4 | return Error::from_string_view_or_print_error_and_return_errno("Input/Output/Feature item without a preceding Logical Minimum Item"sv, EINVAL); |
492 | | |
493 | 12.8k | if (!m_current_item_state_table.global.logical_maximum.has_value()) |
494 | 1 | return Error::from_string_view_or_print_error_and_return_errno("Input/Output/Feature item without a preceding Logical Maximum Item"sv, EINVAL); |
495 | | |
496 | 12.8k | if (!m_current_item_state_table.global.report_count.has_value()) |
497 | 1 | return Error::from_string_view_or_print_error_and_return_errno("Input/Output/Feature item without a preceding Report Count Item"sv, EINVAL); |
498 | | |
499 | 12.8k | if (!m_current_item_state_table.global.report_size.has_value()) |
500 | 1 | return Error::from_string_view_or_print_error_and_return_errno("Input/Output/Feature item without a preceding Report Size Item"sv, EINVAL); |
501 | | |
502 | 12.8k | auto& report_map = [this, field_type] -> HashMap<u8, Report>& { |
503 | 12.8k | switch (field_type) { |
504 | 12.8k | case FieldType::Input: |
505 | 12.8k | return m_current_application_collection->input_reports; |
506 | 0 | case FieldType::Output: |
507 | 0 | return m_current_application_collection->output_reports; |
508 | 0 | case FieldType::Feature: |
509 | 0 | return m_current_application_collection->feature_reports; |
510 | 12.8k | } |
511 | 0 | VERIFY_NOT_REACHED(); |
512 | 0 | }(); |
513 | | |
514 | | // FIXME: Since try_ensure does not return a reference to the contained value, we have to implement it manually here. |
515 | | // This is a try_ensure bug that should be fixed. |
516 | 12.8k | auto report_id = m_current_item_state_table.global.report_id.value_or(0); |
517 | 12.8k | if (report_map.find(report_id) == report_map.end()) { |
518 | 10.1k | auto result = TRY(report_map.try_set(report_id, |
519 | 10.1k | Report { |
520 | 10.1k | .size_in_bits = static_cast<size_t>(m_parsed.uses_report_ids ? 8 : 0), |
521 | 10.1k | .fields = {}, |
522 | 10.1k | })); |
523 | 10.1k | VERIFY(result == HashSetResult::InsertedNewEntry); |
524 | 10.1k | } |
525 | 12.8k | auto& report = report_map.get(report_id).release_value(); |
526 | | |
527 | 12.8k | size_t const field_size_in_bits = m_current_item_state_table.global.report_size.value(); |
528 | | |
529 | | // Reject Report Counts above 1000 to avoid excessive loop iteration counts. |
530 | 12.8k | if (m_current_item_state_table.global.report_count.value() > 1000) |
531 | 33 | return Error::from_string_view_or_print_error_and_return_errno("Report Count > 1000"sv, E2BIG); |
532 | | |
533 | 1.00M | for (size_t i = 0; i < m_current_item_state_table.global.report_count.value(); i++) { |
534 | 993k | Optional<u32> usage; |
535 | | |
536 | 993k | if (item_data.variable) { |
537 | 88.5k | if (!m_current_item_state_table.local.usages.is_empty()) { |
538 | 35.6k | if (i >= m_current_item_state_table.local.usages.size()) |
539 | 18.3k | usage = m_current_item_state_table.local.usages.last(); |
540 | 17.2k | else |
541 | 17.2k | usage = m_current_item_state_table.local.usages[i]; |
542 | 52.9k | } else if (m_current_item_state_table.local.usage_minimum.has_value()) { |
543 | 437 | usage = m_current_item_state_table.local.usage_minimum.value() + i; |
544 | 437 | } |
545 | 88.5k | } |
546 | | |
547 | 993k | size_t const start_bit_index = report.size_in_bits; |
548 | | |
549 | | // Assume Input/Output/Feature items without a preceding usage item are used for padding (6.2.2.9 Padding). |
550 | 993k | if (usage.has_value() || m_current_item_state_table.local.usage_minimum.has_value()) { |
551 | 36.8k | if (item_data.variable) |
552 | 36.0k | VERIFY(usage.has_value()); |
553 | | |
554 | 36.8k | auto field_usage_minimum = item_data.variable ? OptionalNone {} : m_current_item_state_table.local.usage_minimum; |
555 | 36.8k | auto field_usage_maximum = item_data.variable ? OptionalNone {} : m_current_item_state_table.local.usage_maximum; |
556 | | |
557 | 36.8k | Field field { |
558 | 36.8k | .start_bit_index = start_bit_index, |
559 | 36.8k | .end_bit_index = start_bit_index + field_size_in_bits, |
560 | 36.8k | .is_array = !item_data.variable, |
561 | 36.8k | .is_relative = static_cast<bool>(item_data.relative), |
562 | 36.8k | .logical_minimum = m_current_item_state_table.global.logical_minimum.value(), |
563 | 36.8k | .logical_maximum = m_current_item_state_table.global.logical_maximum.value(), |
564 | 36.8k | .usage = usage, |
565 | 36.8k | .usage_minimum = field_usage_minimum, |
566 | 36.8k | .usage_maximum = field_usage_maximum, |
567 | 36.8k | }; |
568 | | |
569 | | // Reject reports descriptors with more than 1000 fields to prevent excessive field allocation. |
570 | 36.8k | if (m_total_report_field_count > 1000) |
571 | 4 | return Error::from_string_view_or_print_error_and_return_errno("Report descriptor defines more than 1000 fields"sv, E2BIG); |
572 | | |
573 | 73.6k | TRY(report.fields.try_append(field)); |
574 | 73.6k | TRY(m_current_collection->fields.try_empend(move(field))); |
575 | | |
576 | 36.8k | m_total_report_field_count++; |
577 | 36.8k | } |
578 | | |
579 | 993k | report.size_in_bits += field_size_in_bits; |
580 | 993k | } |
581 | | |
582 | 12.7k | return {}; |
583 | 12.7k | } |
584 | | } |