Coverage Report

Created: 2026-01-16 06:48

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
28.9M
G_DEFINE_TYPE_WITH_PRIVATE(FuCsvFirmware, fu_csv_firmware, FU_TYPE_FIRMWARE)
29
28.9M
#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
338k
{
70
338k
  FuCsvFirmwarePrivate *priv = GET_PRIVATE(self);
71
338k
  g_return_val_if_fail(FU_IS_CSV_FIRMWARE(self), NULL);
72
73
338k
  if (idx >= priv->column_ids->len)
74
169k
    return NULL;
75
168k
  return g_ptr_array_index(priv->column_ids, idx);
76
338k
}
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.53k
{
115
2.53k
  FuCsvFirmwarePrivate *priv = GET_PRIVATE(self);
116
2.53k
  g_return_if_fail(FU_IS_CSV_FIRMWARE(self));
117
2.53k
  priv->write_column_ids = write_column_ids;
118
2.53k
}
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
3.75M
{
149
3.75M
  FuCsvFirmware *self = FU_CSV_FIRMWARE(user_data);
150
3.75M
  g_autoptr(FuFirmware) entry = fu_csv_entry_new();
151
3.75M
  g_autoptr(GBytes) fw = NULL;
152
153
  /* ignore blank lines */
154
3.75M
  if (token->len == 0)
155
3.42M
    return TRUE;
156
157
  /* title */
158
333k
  if (g_str_has_prefix(token->str, "#")) {
159
97.2k
    return fu_strsplit_full(token->str + 1,
160
97.2k
          token->len - 1,
161
97.2k
          ",",
162
97.2k
          fu_csv_firmware_parse_token_cb,
163
97.2k
          self,
164
97.2k
          error);
165
97.2k
  }
166
167
  /* parse entry */
168
235k
  fw = g_bytes_new(token->str, token->len);
169
235k
  fu_firmware_set_idx(entry, token_idx);
170
235k
  if (!fu_firmware_add_image(FU_FIRMWARE(self), entry, error))
171
4
    return FALSE;
172
235k
  if (!fu_firmware_parse_bytes(entry, fw, 0x0, FU_FIRMWARE_PARSE_FLAG_NONE, error))
173
84
    return FALSE;
174
235k
  return TRUE;
175
235k
}
176
177
static gboolean
178
fu_csv_firmware_parse(FuFirmware *firmware,
179
          GInputStream *stream,
180
          FuFirmwareParseFlags flags,
181
          GError **error)
182
4.19k
{
183
4.19k
  FuCsvFirmware *self = FU_CSV_FIRMWARE(firmware);
184
4.19k
  return fu_strsplit_stream(stream, 0x0, "\n", fu_csv_firmware_parse_line_cb, self, error);
185
4.19k
}
186
187
static GByteArray *
188
fu_csv_firmware_write(FuFirmware *firmware, GError **error)
189
2.33k
{
190
2.33k
  FuCsvFirmware *self = FU_CSV_FIRMWARE(firmware);
191
2.33k
  FuCsvFirmwarePrivate *priv = GET_PRIVATE(self);
192
2.33k
  g_autoptr(GByteArray) buf = g_byte_array_new();
193
2.33k
  g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware);
194
195
  /* title section */
196
2.33k
  if (priv->write_column_ids) {
197
1.70k
    g_autoptr(GString) str = g_string_new("#");
198
10.6M
    for (guint i = 0; i < priv->column_ids->len; i++) {
199
10.6M
      const gchar *column_id = g_ptr_array_index(priv->column_ids, i);
200
10.6M
      if (str->len > 1)
201
8.45M
        g_string_append(str, ",");
202
10.6M
      g_string_append(str, column_id);
203
10.6M
    }
204
1.70k
    g_string_append(str, "\n");
205
1.70k
    g_byte_array_append(buf, (const guint8 *)str->str, str->len);
206
1.70k
  }
207
208
  /* each entry */
209
163k
  for (guint i = 0; i < imgs->len; i++) {
210
160k
    FuFirmware *img = g_ptr_array_index(imgs, i);
211
160k
    g_autoptr(GBytes) img_blob = fu_firmware_write(img, error);
212
160k
    if (img_blob == NULL)
213
0
      return NULL;
214
160k
    fu_byte_array_append_bytes(buf, img_blob);
215
160k
  }
216
217
  /* success */
218
2.33k
  return g_steal_pointer(&buf);
219
2.33k
}
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.28k
{
250
4.28k
  FuCsvFirmwarePrivate *priv = GET_PRIVATE(self);
251
4.28k
  priv->column_ids = g_ptr_array_new_with_free_func(g_free);
252
4.28k
  priv->write_column_ids = TRUE;
253
4.28k
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_NO_AUTO_DETECTION);
254
4.28k
  fu_firmware_set_images_max(FU_FIRMWARE(self), 10000);
255
4.28k
  fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_CSV_ENTRY);
256
4.28k
}
257
258
static void
259
fu_csv_firmware_finalize(GObject *object)
260
4.28k
{
261
4.28k
  FuCsvFirmware *self = FU_CSV_FIRMWARE(object);
262
4.28k
  FuCsvFirmwarePrivate *priv = GET_PRIVATE(self);
263
4.28k
  g_ptr_array_unref(priv->column_ids);
264
4.28k
  G_OBJECT_CLASS(fu_csv_firmware_parent_class)->finalize(object);
265
4.28k
}
266
267
static void
268
fu_csv_firmware_class_init(FuCsvFirmwareClass *klass)
269
2
{
270
2
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
271
2
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
272
2
  object_class->finalize = fu_csv_firmware_finalize;
273
2
  firmware_class->parse = fu_csv_firmware_parse;
274
2
  firmware_class->write = fu_csv_firmware_write;
275
2
  firmware_class->export = fu_csv_firmware_export;
276
2
  firmware_class->build = fu_csv_firmware_build;
277
2
}
278
279
/**
280
 * fu_csv_firmware_new:
281
 *
282
 * Creates a new #FuFirmware
283
 *
284
 * Since: 1.9.3
285
 **/
286
FuFirmware *
287
fu_csv_firmware_new(void)
288
2.53k
{
289
2.53k
  return FU_FIRMWARE(g_object_new(FU_TYPE_CSV_FIRMWARE, NULL));
290
2.53k
}