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