/src/fwupd/libfwupdplugin/fu-hid-report-item.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright 2023 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | 0 | #define G_LOG_DOMAIN "FuHidDevice" |
8 | | |
9 | | #include "config.h" |
10 | | |
11 | | #include "fu-byte-array.h" |
12 | | #include "fu-common.h" |
13 | | #include "fu-hid-report-item.h" |
14 | | #include "fu-input-stream.h" |
15 | | #include "fu-partial-input-stream.h" |
16 | | #include "fu-string.h" |
17 | | |
18 | | /** |
19 | | * FuHidReportItem: |
20 | | * |
21 | | * See also: [class@FuHidDescriptor] |
22 | | */ |
23 | | |
24 | | struct _FuHidReportItem { |
25 | | FuFirmware parent_instance; |
26 | | guint32 value; |
27 | | }; |
28 | | |
29 | | G_DEFINE_TYPE(FuHidReportItem, fu_hid_report_item, FU_TYPE_FIRMWARE) |
30 | | |
31 | | FuHidItemKind |
32 | | fu_hid_report_item_get_kind(FuHidReportItem *self) |
33 | 31.4M | { |
34 | 31.4M | g_return_val_if_fail(FU_IS_HID_REPORT_ITEM(self), 0); |
35 | 31.4M | return fu_firmware_get_idx(FU_FIRMWARE(self)) & 0b11; |
36 | 31.4M | } |
37 | | |
38 | | guint32 |
39 | | fu_hid_report_item_get_value(FuHidReportItem *self) |
40 | 30.9M | { |
41 | 30.9M | g_return_val_if_fail(FU_IS_HID_REPORT_ITEM(self), 0); |
42 | 30.9M | return self->value; |
43 | 30.9M | } |
44 | | |
45 | | static void |
46 | | fu_hid_report_item_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) |
47 | 111k | { |
48 | 111k | FuHidReportItem *self = FU_HID_REPORT_ITEM(firmware); |
49 | 111k | fu_xmlb_builder_insert_kv(bn, |
50 | 111k | "kind", |
51 | 111k | fu_hid_item_kind_to_string(fu_hid_report_item_get_kind(self))); |
52 | 111k | fu_xmlb_builder_insert_kx(bn, "value", self->value); |
53 | 111k | } |
54 | | |
55 | | static gboolean |
56 | | fu_hid_report_item_parse(FuFirmware *firmware, |
57 | | GInputStream *stream, |
58 | | FuFirmwareParseFlags flags, |
59 | | GError **error) |
60 | 111k | { |
61 | 111k | FuHidReportItem *self = FU_HID_REPORT_ITEM(firmware); |
62 | 111k | const guint8 size_lookup[] = {0, 1, 2, 4}; |
63 | 111k | guint8 data_size; |
64 | 111k | guint8 tag; |
65 | 111k | guint8 val = 0; |
66 | | |
67 | 111k | if (!fu_input_stream_read_u8(stream, 0x0, &val, error)) |
68 | 0 | return FALSE; |
69 | 111k | data_size = size_lookup[val & 0b11]; |
70 | 111k | tag = (val & 0b11111100) >> 2; |
71 | 111k | fu_firmware_set_idx(firmware, tag); |
72 | 111k | fu_firmware_set_id(firmware, fu_hid_item_tag_to_string(tag)); |
73 | | |
74 | 111k | if (tag == FU_HID_ITEM_TAG_LONG && data_size == 2) { |
75 | 3.89k | gsize streamsz = 0; |
76 | 3.89k | if (!fu_input_stream_size(stream, &streamsz, error)) |
77 | 0 | return FALSE; |
78 | 3.89k | if (streamsz < 1) { |
79 | 0 | g_set_error_literal(error, |
80 | 0 | FWUPD_ERROR, |
81 | 0 | FWUPD_ERROR_INVALID_DATA, |
82 | 0 | "not enough data to read long tag"); |
83 | 0 | return FALSE; |
84 | 0 | } |
85 | 3.89k | if (!fu_input_stream_read_u8(stream, 1, &data_size, error)) |
86 | 12 | return FALSE; |
87 | 107k | } else { |
88 | 107k | g_autoptr(GInputStream) partial_stream = NULL; |
89 | 107k | if (data_size == 1) { |
90 | 21.9k | guint8 value = 0; |
91 | 21.9k | if (!fu_input_stream_read_u8(stream, 1, &value, error)) |
92 | 26 | return FALSE; |
93 | 21.9k | self->value = value; |
94 | 85.3k | } else if (data_size == 2) { |
95 | 3.19k | guint16 value = 0; |
96 | 3.19k | if (!fu_input_stream_read_u16(stream, 1, &value, G_LITTLE_ENDIAN, error)) |
97 | 35 | return FALSE; |
98 | 3.16k | self->value = value; |
99 | 82.1k | } else if (data_size == 4) { |
100 | 6.37k | if (!fu_input_stream_read_u32(stream, |
101 | 6.37k | 1, |
102 | 6.37k | &self->value, |
103 | 6.37k | G_LITTLE_ENDIAN, |
104 | 6.37k | error)) |
105 | 33 | return FALSE; |
106 | 6.37k | } |
107 | 107k | partial_stream = fu_partial_input_stream_new(stream, 1, data_size, error); |
108 | 107k | if (partial_stream == NULL) { |
109 | 0 | g_prefix_error(error, "failed to cut HID payload: "); |
110 | 0 | return FALSE; |
111 | 0 | } |
112 | 107k | if (!fu_firmware_set_stream(firmware, partial_stream, error)) |
113 | 0 | return FALSE; |
114 | 107k | } |
115 | | |
116 | | /* success */ |
117 | 111k | fu_firmware_set_size(firmware, 1 + data_size); |
118 | 111k | return TRUE; |
119 | 111k | } |
120 | | |
121 | | static GByteArray * |
122 | | fu_hid_report_item_write(FuFirmware *firmware, GError **error) |
123 | 6.79k | { |
124 | 6.79k | FuHidReportItem *self = FU_HID_REPORT_ITEM(firmware); |
125 | 6.79k | g_autoptr(GByteArray) st = g_byte_array_new(); |
126 | 6.79k | guint8 tmp = fu_firmware_get_idx(firmware) << 2; |
127 | | |
128 | 6.79k | if (self->value == 0) { |
129 | 4.83k | fu_byte_array_append_uint8(st, tmp); |
130 | 4.83k | } else if (self->value <= G_MAXUINT8) { |
131 | 1.13k | tmp |= 0b01; |
132 | 1.13k | fu_byte_array_append_uint8(st, tmp); |
133 | 1.13k | fu_byte_array_append_uint8(st, self->value); |
134 | 1.13k | } else if (self->value <= G_MAXUINT16) { |
135 | 429 | tmp |= 0b10; |
136 | 429 | fu_byte_array_append_uint8(st, tmp); |
137 | 429 | fu_byte_array_append_uint16(st, self->value, G_LITTLE_ENDIAN); |
138 | 429 | } else { |
139 | 389 | tmp |= 0b11; |
140 | 389 | fu_byte_array_append_uint8(st, tmp); |
141 | 389 | fu_byte_array_append_uint32(st, self->value, G_LITTLE_ENDIAN); |
142 | 389 | } |
143 | | |
144 | | /* success */ |
145 | 6.79k | return g_steal_pointer(&st); |
146 | 6.79k | } |
147 | | |
148 | | static gboolean |
149 | | fu_hid_report_item_build(FuFirmware *firmware, XbNode *n, GError **error) |
150 | 0 | { |
151 | 0 | FuHidReportItem *self = FU_HID_REPORT_ITEM(firmware); |
152 | 0 | const gchar *tmp; |
153 | 0 | guint64 value = 0; |
154 | | |
155 | | /* optional data */ |
156 | 0 | tmp = xb_node_query_text(n, "idx", NULL); |
157 | 0 | if (tmp != NULL) { |
158 | 0 | if (!fu_strtoull(tmp, &value, 0x0, G_MAXUINT8, FU_INTEGER_BASE_AUTO, error)) |
159 | 0 | return FALSE; |
160 | 0 | fu_firmware_set_idx(firmware, value); |
161 | 0 | fu_firmware_set_id(firmware, fu_hid_item_tag_to_string(value)); |
162 | 0 | } |
163 | 0 | tmp = xb_node_query_text(n, "id", NULL); |
164 | 0 | if (tmp != NULL) { |
165 | 0 | fu_firmware_set_id(firmware, tmp); |
166 | 0 | fu_firmware_set_idx(firmware, fu_hid_item_tag_from_string(tmp)); |
167 | 0 | } |
168 | 0 | tmp = xb_node_query_text(n, "value", NULL); |
169 | 0 | if (tmp != NULL) { |
170 | 0 | if (!fu_strtoull(tmp, &value, 0x0, G_MAXUINT32, FU_INTEGER_BASE_AUTO, error)) |
171 | 0 | return FALSE; |
172 | 0 | self->value = value; |
173 | 0 | } |
174 | | |
175 | | /* success */ |
176 | 0 | return TRUE; |
177 | 0 | } |
178 | | |
179 | | static void |
180 | | fu_hid_report_item_init(FuHidReportItem *self) |
181 | 111k | { |
182 | 111k | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_NO_AUTO_DETECTION); |
183 | 111k | } |
184 | | |
185 | | static void |
186 | | fu_hid_report_item_class_init(FuHidReportItemClass *klass) |
187 | 1 | { |
188 | 1 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
189 | 1 | firmware_class->export = fu_hid_report_item_export; |
190 | 1 | firmware_class->parse = fu_hid_report_item_parse; |
191 | 1 | firmware_class->write = fu_hid_report_item_write; |
192 | 1 | firmware_class->build = fu_hid_report_item_build; |
193 | 1 | } |
194 | | |
195 | | /** |
196 | | * fu_hid_report_item_new: |
197 | | * |
198 | | * Creates a new HID report item |
199 | | * |
200 | | * Returns: (transfer full): a #FuHidReportItem |
201 | | * |
202 | | * Since: 1.9.4 |
203 | | **/ |
204 | | FuHidReportItem * |
205 | | fu_hid_report_item_new(void) |
206 | 111k | { |
207 | 111k | return g_object_new(FU_TYPE_HID_REPORT_ITEM, NULL); |
208 | 111k | } |