Coverage Report

Created: 2026-06-07 07:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}