Coverage Report

Created: 2026-07-16 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-csv-entry.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-common.h"
10
#include "fu-csv-entry.h"
11
#include "fu-csv-firmware-private.h"
12
#include "fu-input-stream.h"
13
#include "fu-string.h"
14
15
/**
16
 * FuCsvEntry:
17
 *
18
 * A comma seporated value entry.
19
 *
20
 * See also: [class@FuFirmware]
21
 */
22
23
typedef struct {
24
  GPtrArray *values; /* element-type utf-8 */
25
} FuCsvEntryPrivate;
26
27
1.49M
G_DEFINE_TYPE_WITH_PRIVATE(FuCsvEntry, fu_csv_entry, FU_TYPE_FIRMWARE)
28
1.49M
#define GET_PRIVATE(o) (fu_csv_entry_get_instance_private(o))
29
30
319k
#define FU_CSV_ENTRY_COLUMNS_MAX 1000u
31
32
/**
33
 * fu_csv_entry_add_value:
34
 * @self: a #FuFirmware
35
 * @value: (not nullable): string
36
 *
37
 * Adds a string value to the entry.
38
 *
39
 * Since: 1.9.3
40
 **/
41
void
42
fu_csv_entry_add_value(FuCsvEntry *self, const gchar *value)
43
0
{
44
0
  FuCsvEntryPrivate *priv = GET_PRIVATE(self);
45
0
  g_return_if_fail(FU_IS_CSV_ENTRY(self));
46
0
  g_return_if_fail(value != NULL);
47
0
  g_ptr_array_add(priv->values, g_strdup(value));
48
0
}
49
50
/**
51
 * fu_csv_entry_get_value_by_idx:
52
 * @self: a #FuFirmware
53
 * @idx: column ID idx
54
 *
55
 * Gets the entry value for a specific index.
56
 *
57
 * Returns: a string, or %NULL if unset
58
 *
59
 * Since: 1.9.3
60
 **/
61
const gchar *
62
fu_csv_entry_get_value_by_idx(FuCsvEntry *self, guint idx)
63
0
{
64
0
  FuCsvEntryPrivate *priv = GET_PRIVATE(self);
65
0
  g_return_val_if_fail(FU_IS_CSV_ENTRY(self), NULL);
66
0
  if (idx >= priv->values->len)
67
0
    return NULL;
68
0
  return g_ptr_array_index(priv->values, idx);
69
0
}
70
71
/**
72
 * fu_csv_entry_get_value_by_column_id:
73
 * @self: a #FuFirmware
74
 * @column_id: (not nullable): string, e.g. `component_generation`
75
 *
76
 * Gets the entry value for a specific column ID.
77
 *
78
 * Returns: a string, or %NULL if unset or the column ID cannot be found
79
 *
80
 * Since: 1.9.3
81
 **/
82
const gchar *
83
fu_csv_entry_get_value_by_column_id(FuCsvEntry *self, const gchar *column_id)
84
0
{
85
0
  FuCsvEntryPrivate *priv = GET_PRIVATE(self);
86
0
  FuCsvFirmware *parent = FU_CSV_FIRMWARE(fu_firmware_get_parent(FU_FIRMWARE(self)));
87
0
  guint idx = fu_csv_firmware_get_idx_for_column_id(parent, column_id);
88
89
0
  g_return_val_if_fail(FU_IS_CSV_ENTRY(self), NULL);
90
0
  g_return_val_if_fail(FU_IS_CSV_FIRMWARE(parent), NULL);
91
0
  g_return_val_if_fail(idx != G_MAXUINT, NULL);
92
0
  g_return_val_if_fail(column_id != NULL, NULL);
93
94
  /* the column may be defined in the header but absent from this shorter row */
95
0
  if (idx >= priv->values->len)
96
0
    return NULL;
97
0
  return g_ptr_array_index(priv->values, idx);
98
0
}
99
100
gboolean
101
fu_csv_entry_get_value_by_column_id_uint64(FuCsvEntry *self,
102
             const gchar *column_id,
103
             guint64 *value,
104
             GError **error)
105
0
{
106
0
  const gchar *str_value = fu_csv_entry_get_value_by_column_id(self, column_id);
107
108
0
  if (str_value == NULL) {
109
0
    g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "value not found");
110
0
    return FALSE;
111
0
  }
112
113
0
  return fu_strtoull(str_value, value, 0, G_MAXUINT64, FU_INTEGER_BASE_AUTO, error);
114
0
}
115
116
static void
117
fu_csv_entry_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
118
0
{
119
0
  FuCsvEntry *self = FU_CSV_ENTRY(firmware);
120
0
  FuCsvEntryPrivate *priv = GET_PRIVATE(self);
121
0
  FuCsvFirmware *parent = FU_CSV_FIRMWARE(fu_firmware_get_parent(firmware));
122
0
  g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bn, "values", NULL);
123
124
0
  for (guint i = 0; i < priv->values->len; i++) {
125
0
    const gchar *value = g_ptr_array_index(priv->values, i);
126
0
    const gchar *key = fu_csv_firmware_get_column_id(parent, i);
127
0
    if (key != NULL)
128
0
      fu_xmlb_builder_insert_kv(bc, key, value);
129
0
  }
130
0
}
131
132
static gboolean
133
fu_csv_entry_build(FuFirmware *firmware, XbNode *n, GError **error)
134
0
{
135
0
  FuCsvEntry *self = FU_CSV_ENTRY(firmware);
136
0
  FuCsvFirmware *parent = FU_CSV_FIRMWARE(fu_firmware_get_parent(firmware));
137
0
  gboolean add_columns;
138
0
  g_autoptr(GPtrArray) values = NULL;
139
140
  /* sanity check */
141
0
  if (parent == NULL) {
142
0
    g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "no parent");
143
0
    return FALSE;
144
0
  }
145
146
0
  values = xb_node_query(n, "values/*", 0, error);
147
0
  if (values == NULL) {
148
0
    fwupd_error_convert(error);
149
0
    return FALSE;
150
0
  }
151
0
  add_columns = fu_csv_firmware_get_column_id(parent, 0) == NULL;
152
0
  for (guint i = 0; i < values->len; i++) {
153
0
    XbNode *c = g_ptr_array_index(values, i);
154
0
    if (add_columns && xb_node_get_element(c) != NULL)
155
0
      fu_csv_firmware_add_column_id(parent, xb_node_get_element(c));
156
0
    fu_csv_entry_add_value(self, xb_node_get_text(c));
157
0
  }
158
0
  return TRUE;
159
0
}
160
161
static gboolean
162
fu_csv_entry_parse_token_cb(GString *token, guint token_idx, gpointer user_data, GError **error)
163
319k
{
164
319k
  FuCsvEntry *self = FU_CSV_ENTRY(user_data);
165
319k
  FuCsvEntryPrivate *priv = GET_PRIVATE(self);
166
319k
  FuCsvFirmware *parent = FU_CSV_FIRMWARE(fu_firmware_get_parent(FU_FIRMWARE(self)));
167
319k
  const gchar *column_id;
168
169
  /* sanity check */
170
319k
  if (parent == NULL) {
171
0
    g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "no parent");
172
0
    return FALSE;
173
0
  }
174
319k
  if (token_idx > FU_CSV_ENTRY_COLUMNS_MAX) {
175
10
    g_set_error(error,
176
10
          FWUPD_ERROR,
177
10
          FWUPD_ERROR_INVALID_DATA,
178
10
          "too many columns, limit is %u",
179
10
          FU_CSV_ENTRY_COLUMNS_MAX);
180
10
    return FALSE;
181
10
  }
182
183
319k
  column_id = fu_csv_firmware_get_column_id(parent, token_idx);
184
319k
  if (g_strcmp0(column_id, "$id") == 0) {
185
57.2k
    fu_firmware_set_id(FU_FIRMWARE(self), token->str);
186
262k
  } else if (g_strcmp0(column_id, "$idx") == 0) {
187
1.05k
    guint64 value = 0;
188
1.05k
    if (!fu_strtoull(token->str, &value, 0, G_MAXUINT64, FU_INTEGER_BASE_AUTO, error))
189
34
      return FALSE;
190
1.01k
    fu_firmware_set_idx(FU_FIRMWARE(self), value);
191
261k
  } else if (g_strcmp0(column_id, "$version") == 0) {
192
8.40k
    fu_firmware_set_version(FU_FIRMWARE(self), token->str); /* nocheck:set-version */
193
252k
  } else if (g_strcmp0(column_id, "$version_raw") == 0) {
194
9.58k
    guint64 value = 0;
195
9.58k
    if (!fu_strtoull(token->str, &value, 0, G_MAXUINT64, FU_INTEGER_BASE_AUTO, error))
196
29
      return FALSE;
197
9.55k
    fu_firmware_set_version_raw(FU_FIRMWARE(self), value);
198
9.55k
  }
199
200
  /* always save to value so we can write it back out */
201
319k
  g_ptr_array_add(priv->values, g_strdup(token->str));
202
319k
  return TRUE;
203
319k
}
204
205
static gboolean
206
fu_csv_entry_parse(FuFirmware *firmware,
207
       FuInputStream *stream,
208
       FuFirmwareParseFlags flags,
209
       GError **error)
210
173k
{
211
173k
  FuCsvEntry *self = FU_CSV_ENTRY(firmware);
212
173k
  return fu_strsplit_stream(stream, 0x0, ",", fu_csv_entry_parse_token_cb, self, error);
213
173k
}
214
215
static GByteArray *
216
fu_csv_entry_write(FuFirmware *firmware, GError **error)
217
117k
{
218
117k
  FuCsvEntry *self = FU_CSV_ENTRY(firmware);
219
117k
  FuCsvEntryPrivate *priv = GET_PRIVATE(self);
220
117k
  g_autoptr(GByteArray) buf = g_byte_array_new();
221
117k
  g_autoptr(GString) str = g_string_new(NULL);
222
223
  /* single line */
224
340k
  for (guint i = 0; i < priv->values->len; i++) {
225
223k
    const gchar *value = g_ptr_array_index(priv->values, i);
226
223k
    if (str->len > 0)
227
64.1k
      g_string_append(str, ",");
228
223k
    if (value != NULL)
229
223k
      g_string_append(str, value);
230
223k
  }
231
117k
  g_string_append(str, "\n");
232
117k
  g_byte_array_append(buf, (const guint8 *)str->str, str->len);
233
234
  /* success */
235
117k
  return g_steal_pointer(&buf);
236
117k
}
237
238
static void
239
fu_csv_entry_init(FuCsvEntry *self)
240
441k
{
241
441k
  FuCsvEntryPrivate *priv = GET_PRIVATE(self);
242
441k
  priv->values = g_ptr_array_new_with_free_func(g_free);
243
441k
}
244
245
static void
246
fu_csv_entry_finalize(GObject *object)
247
441k
{
248
441k
  FuCsvEntry *self = FU_CSV_ENTRY(object);
249
441k
  FuCsvEntryPrivate *priv = GET_PRIVATE(self);
250
441k
  g_ptr_array_unref(priv->values);
251
441k
  G_OBJECT_CLASS(fu_csv_entry_parent_class)->finalize(object);
252
441k
}
253
254
static void
255
fu_csv_entry_class_init(FuCsvEntryClass *klass)
256
2
{
257
2
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
258
2
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
259
2
  object_class->finalize = fu_csv_entry_finalize;
260
2
  firmware_class->parse = fu_csv_entry_parse;
261
2
  firmware_class->write = fu_csv_entry_write;
262
2
  firmware_class->build = fu_csv_entry_build;
263
2
  firmware_class->export = fu_csv_entry_export;
264
2
  fu_firmware_set_size_max(firmware_class, 1 * FU_MB);
265
2
}
266
267
/**
268
 * fu_csv_entry_new:
269
 *
270
 * Creates a new #FuFirmware
271
 *
272
 * Since: 1.9.3
273
 **/
274
FuFirmware *
275
fu_csv_entry_new(void)
276
441k
{
277
441k
  return FU_FIRMWARE(g_object_new(FU_TYPE_CSV_ENTRY, NULL));
278
441k
}