Coverage Report

Created: 2026-04-28 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-csv-firmware.c
Line
Count
Source
1
/*
2
 * Copyright 2023 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
#include "config.h"
8
9
#include "fu-byte-array.h"
10
#include "fu-common.h"
11
#include "fu-csv-entry.h"
12
#include "fu-csv-firmware-private.h"
13
#include "fu-string.h"
14
15
/**
16
 * FuCsvFirmware:
17
 *
18
 * A comma seporated value file.
19
 *
20
 * See also: [class@FuFirmware]
21
 */
22
23
typedef struct {
24
  GPtrArray *column_ids;
25
  gboolean write_column_ids;
26
} FuCsvFirmwarePrivate;
27
28
26.1M
G_DEFINE_TYPE_WITH_PRIVATE(FuCsvFirmware, fu_csv_firmware, FU_TYPE_FIRMWARE)
29
26.1M
#define GET_PRIVATE(o) (fu_csv_firmware_get_instance_private(o))
30
31
/**
32
 * fu_csv_firmware_add_column_id:
33
 * @self: a #FuFirmware
34
 * @column_id: (not nullable): string, e.g. `component_generation`
35
 *
36
 * Adds a column ID.
37
 *
38
 * There are several optional magic column IDs that map to #FuFirmware properties:
39
 *
40
 * * `$id` sets the firmware ID
41
 * * `$idx` sets the firmware index
42
 * * `$version` sets the firmware version
43
 * * `$version_raw` sets the raw firmware version
44
 *
45
 * Since: 1.9.3
46
 **/
47
void
48
fu_csv_firmware_add_column_id(FuCsvFirmware *self, const gchar *column_id)
49
12.2M
{
50
12.2M
  FuCsvFirmwarePrivate *priv = GET_PRIVATE(self);
51
12.2M
  g_return_if_fail(FU_IS_CSV_FIRMWARE(self));
52
12.2M
  g_return_if_fail(column_id != NULL);
53
12.2M
  g_ptr_array_add(priv->column_ids, g_strdup(column_id));
54
12.2M
}
55
56
/**
57
 * fu_csv_firmware_get_column_id:
58
 * @self: a #FuFirmware
59
 * @idx: column ID idx
60
 *
61
 * Gets the column ID for a specific index position.
62
 *
63
 * Returns: a string, or %NULL if not found
64
 *
65
 * Since: 1.9.3
66
 **/
67
const gchar *
68
fu_csv_firmware_get_column_id(FuCsvFirmware *self, guint idx)
69
331k
{
70
331k
  FuCsvFirmwarePrivate *priv = GET_PRIVATE(self);
71
331k
  g_return_val_if_fail(FU_IS_CSV_FIRMWARE(self), NULL);
72
73
331k
  if (idx >= priv->column_ids->len)
74
123k
    return NULL;
75
208k
  return g_ptr_array_index(priv->column_ids, idx);
76
331k
}
77
78
/**
79
 * fu_csv_firmware_get_idx_for_column_id:
80
 * @self: a #FuFirmware
81
 * @column_id: (not nullable): string, e.g. `component_generation`
82
 *
83
 * Gets the column index for a given column ID.
84
 *
85
 * Returns: position, or %G_MAXUINT if unset
86
 *
87
 * Since: 1.9.3
88
 **/
89
guint
90
fu_csv_firmware_get_idx_for_column_id(FuCsvFirmware *self, const gchar *column_id)
91
0
{
92
0
  FuCsvFirmwarePrivate *priv = GET_PRIVATE(self);
93
0
  g_return_val_if_fail(FU_IS_CSV_FIRMWARE(self), G_MAXUINT);
94
0
  g_return_val_if_fail(column_id != NULL, G_MAXUINT);
95
0
  for (guint i = 0; i < priv->column_ids->len; i++) {
96
0
    const gchar *column_id_tmp = g_ptr_array_index(priv->column_ids, i);
97
0
    if (g_strcmp0(column_id_tmp, column_id) == 0)
98
0
      return i;
99
0
  }
100
0
  return G_MAXUINT;
101
0
}
102
103
/**
104
 * fu_csv_firmware_set_write_column_ids:
105
 * @self: a #FuFirmware
106
 * @write_column_ids: boolean
107
 *
108
 * Sets if we should write the column ID headers on export.
109
 *
110
 * Since: 2.0.0
111
 **/
112
void
113
fu_csv_firmware_set_write_column_ids(FuCsvFirmware *self, gboolean write_column_ids)
114
2.40k
{
115
2.40k
  FuCsvFirmwarePrivate *priv = GET_PRIVATE(self);
116
2.40k
  g_return_if_fail(FU_IS_CSV_FIRMWARE(self));
117
2.40k
  priv->write_column_ids = write_column_ids;
118
2.40k
}
119
120
/**
121
 * fu_csv_firmware_get_write_column_ids:
122
 * @self: a #FuFirmware
123
 *
124
 * Gets if we should write the column ID headers on export.
125
 *
126
 * Returns: boolean.
127
 *
128
 * Since: 2.0.0
129
 **/
130
gboolean
131
fu_csv_firmware_get_write_column_ids(FuCsvFirmware *self)
132
0
{
133
0
  FuCsvFirmwarePrivate *priv = GET_PRIVATE(self);
134
0
  g_return_val_if_fail(FU_IS_CSV_FIRMWARE(self), FALSE);
135
0
  return priv->write_column_ids;
136
0
}
137
138
static gboolean
139
fu_csv_firmware_parse_token_cb(GString *token, guint token_idx, gpointer user_data, GError **error)
140
12.2M
{
141
12.2M
  FuCsvFirmware *self = FU_CSV_FIRMWARE(user_data);
142
12.2M
  fu_csv_firmware_add_column_id(self, token->str);
143
12.2M
  return TRUE;
144
12.2M
}
145
146
static gboolean
147
fu_csv_firmware_parse_line_cb(GString *token, guint token_idx, gpointer user_data, GError **error)
148
1.02M
{
149
1.02M
  FuCsvFirmware *self = FU_CSV_FIRMWARE(user_data);
150
1.02M
  g_autoptr(FuFirmware) entry = fu_csv_entry_new();
151
1.02M
  g_autoptr(GBytes) fw = NULL;
152
153
  /* ignore blank lines */
154
1.02M
  if (token->len == 0)
155
615k
    return TRUE;
156
157
  /* title */
158
409k
  if (g_str_has_prefix(token->str, "#")) {
159
216k
    return fu_strsplit_full(token->str + 1,
160
216k
          token->len - 1,
161
216k
          ",",
162
216k
          fu_csv_firmware_parse_token_cb,
163
216k
          self,
164
216k
          error);
165
216k
  }
166
167
  /* parse entry */
168
192k
  fw = g_bytes_new(token->str, token->len);
169
192k
  fu_firmware_set_idx(entry, token_idx);
170
192k
  if (!fu_firmware_add_image(FU_FIRMWARE(self), entry, error))
171
2
    return FALSE;
172
192k
  if (!fu_firmware_parse_bytes(entry, fw, 0x0, FU_FIRMWARE_PARSE_FLAG_NONE, error))
173
95
    return FALSE;
174
192k
  return TRUE;
175
192k
}
176
177
static gboolean
178
fu_csv_firmware_parse(FuFirmware *firmware,
179
          GInputStream *stream,
180
          FuFirmwareParseFlags flags,
181
          GError **error)
182
4.09k
{
183
4.09k
  FuCsvFirmware *self = FU_CSV_FIRMWARE(firmware);
184
4.09k
  return fu_strsplit_stream(stream, 0x0, "\n", fu_csv_firmware_parse_line_cb, self, error);
185
4.09k
}
186
187
static GByteArray *
188
fu_csv_firmware_write(FuFirmware *firmware, GError **error)
189
2.24k
{
190
2.24k
  FuCsvFirmware *self = FU_CSV_FIRMWARE(firmware);
191
2.24k
  FuCsvFirmwarePrivate *priv = GET_PRIVATE(self);
192
2.24k
  g_autoptr(GByteArray) buf = g_byte_array_new();
193
2.24k
  g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware);
194
195
  /* title section */
196
2.24k
  if (priv->write_column_ids) {
197
1.67k
    g_autoptr(GString) str = g_string_new("#");
198
10.1M
    for (guint i = 0; i < priv->column_ids->len; i++) {
199
10.1M
      const gchar *column_id = g_ptr_array_index(priv->column_ids, i);
200
10.1M
      if (str->len > 1)
201
8.01M
        g_string_append(str, ",");
202
10.1M
      g_string_append(str, column_id);
203
10.1M
    }
204
1.67k
    g_string_append(str, "\n");
205
1.67k
    g_byte_array_append(buf, (const guint8 *)str->str, str->len);
206
1.67k
  }
207
208
  /* each entry */
209
147k
  for (guint i = 0; i < imgs->len; i++) {
210
145k
    FuFirmware *img = g_ptr_array_index(imgs, i);
211
145k
    g_autoptr(GBytes) img_blob = fu_firmware_write(img, error);
212
145k
    if (img_blob == NULL)
213
0
      return NULL;
214
145k
    fu_byte_array_append_bytes(buf, img_blob);
215
145k
  }
216
217
  /* success */
218
2.24k
  return g_steal_pointer(&buf);
219
2.24k
}
220
221
static void
222
fu_csv_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
223
0
{
224
0
  FuCsvFirmware *self = FU_CSV_FIRMWARE(firmware);
225
0
  FuCsvFirmwarePrivate *priv = GET_PRIVATE(self);
226
0
  fu_xmlb_builder_insert_kb(bn, "write_column_ids", priv->write_column_ids);
227
0
}
228
229
static gboolean
230
fu_csv_firmware_build(FuFirmware *firmware, XbNode *n, GError **error)
231
0
{
232
0
  FuCsvFirmware *self = FU_CSV_FIRMWARE(firmware);
233
0
  FuCsvFirmwarePrivate *priv = GET_PRIVATE(self);
234
0
  const gchar *tmp;
235
236
  /* optional properties */
237
0
  tmp = xb_node_query_text(n, "write_column_ids", NULL);
238
0
  if (tmp != NULL) {
239
0
    if (!fu_strtobool(tmp, &priv->write_column_ids, error))
240
0
      return FALSE;
241
0
  }
242
243
  /* success */
244
0
  return TRUE;
245
0
}
246
247
static void
248
fu_csv_firmware_init(FuCsvFirmware *self)
249
4.13k
{
250
4.13k
  FuCsvFirmwarePrivate *priv = GET_PRIVATE(self);
251
4.13k
  priv->column_ids = g_ptr_array_new_with_free_func(g_free);
252
4.13k
  priv->write_column_ids = TRUE;
253
4.13k
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_NO_AUTO_DETECTION);
254
4.13k
  fu_firmware_set_images_max(FU_FIRMWARE(self), 10000);
255
4.13k
  fu_firmware_set_size_max(FU_FIRMWARE(self), 16 * FU_MB);
256
4.13k
  fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_CSV_ENTRY);
257
4.13k
}
258
259
static void
260
fu_csv_firmware_finalize(GObject *object)
261
4.13k
{
262
4.13k
  FuCsvFirmware *self = FU_CSV_FIRMWARE(object);
263
4.13k
  FuCsvFirmwarePrivate *priv = GET_PRIVATE(self);
264
4.13k
  g_ptr_array_unref(priv->column_ids);
265
4.13k
  G_OBJECT_CLASS(fu_csv_firmware_parent_class)->finalize(object);
266
4.13k
}
267
268
static void
269
fu_csv_firmware_class_init(FuCsvFirmwareClass *klass)
270
2
{
271
2
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
272
2
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
273
2
  object_class->finalize = fu_csv_firmware_finalize;
274
2
  firmware_class->parse = fu_csv_firmware_parse;
275
2
  firmware_class->write = fu_csv_firmware_write;
276
2
  firmware_class->export = fu_csv_firmware_export;
277
2
  firmware_class->build = fu_csv_firmware_build;
278
2
}
279
280
/**
281
 * fu_csv_firmware_new:
282
 *
283
 * Creates a new #FuFirmware
284
 *
285
 * Since: 1.9.3
286
 **/
287
FuFirmware *
288
fu_csv_firmware_new(void)
289
2.40k
{
290
2.40k
  return FU_FIRMWARE(g_object_new(FU_TYPE_CSV_FIRMWARE, NULL));
291
2.40k
}