Coverage Report

Created: 2026-10-28 07:47

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