ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
config_io.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file config_io.cpp
6 * @brief Schema-driven config export / restore over the config store.
7 *
8 * Delegates value storage to services/storage/config_store (which has a host in-memory
9 * backend), so the whole serialize / parse round-trip is host-tested.
10 */
11
13#include "shared_primitives/strbuf.h" // pc_sb frame builder
14
15#if PC_ENABLE_CONFIG_IO
16
19#include <stdio.h>
20#include <string.h>
21
22namespace
23{
24#define PC_VAL_MAX 128 // export/import value field cap
25#define PC_KEY_MAX 16 // NVS key cap (15 + null)
26
27// Look up a key in the schema; write its pc_cfg_type to *out and return true, or return false if absent.
28bool field_type(const pc_cfg_field *fields, size_t n, const char *key, pc_cfg_type *out)
29{
30 for (size_t i = 0; i < n; i++)
31 {
32 if (fields[i].key && strcmp(fields[i].key, key) == 0)
33 {
34 *out = fields[i].type;
35 return true;
36 }
37 }
38 return false;
39}
40
41// Append "<key>=<val>\n" to out at *pos, overflow-safe. Returns false on overflow.
42bool append_kv(char *out, size_t cap, size_t *pos, const char *key, const char *val)
43{
44 size_t kn = strnlen(key, cap + 1), vn = strnlen(val, cap + 1);
45 size_t need = kn + 1 + vn + 1; // key '=' val '\n'
46 if (*pos + need >= cap) // keep room for the null terminator
47 {
48 return false;
49 }
50 memcpy(out + *pos, key, kn);
51 *pos += kn;
52 out[(*pos)++] = '=';
53 memcpy(out + *pos, val, vn);
54 *pos += vn;
55 out[(*pos)++] = '\n';
56 out[*pos] = '\0';
57 return true;
58}
59} // namespace
60
61int pc_config_export(const char *ns, const pc_cfg_field *fields, size_t n, char *out, size_t cap)
62{
63 if (!out || cap == 0)
64 {
65 return 0;
66 }
67 out[0] = '\0';
68 if (!fields || !pc_config_begin(ns)) // GCOVR_EXCL_BR_LINE the begin()-fails half is unreachable: the
69 {
70 return 0; // host config_store backend's pc_config_begin always returns true
71 }
72
73 size_t pos = 0;
74 for (size_t i = 0; i < n; i++)
75 {
76 char val[PC_VAL_MAX];
77 if (fields[i].type == pc_cfg_type::PC_CFG_U32)
78 {
79 pc_sb sb_val = {val, sizeof(val), 0, true};
80 pc_sb_u32(&sb_val, (uint32_t)((unsigned)pc_config_get_u32(fields[i].key, 0)));
81 if (pc_sb_finish(&sb_val) == 0)
82 {
83 val[0] = '\0';
84 }
85 }
86 else
87 {
88 pc_config_get_str(fields[i].key, val, sizeof(val), "");
89 }
90
91 if (!append_kv(out, cap, &pos, fields[i].key, val))
92 {
93 out[0] = '\0';
94 return 0; // fail closed on overflow
95 }
96 }
97 return (int)pos;
98}
99
100// Set one key=val pair against the field table; returns true iff a matching field was found and its
101// setter accepted the value. Extracted so the import loop stays flat (one dispatch, no nested type switch).
102static bool config_apply_field(const pc_cfg_field *fields, size_t n, const char *key, const char *val)
103{
104 pc_cfg_type t;
105 if (!field_type(fields, n, key, &t))
106 {
107 return false;
108 }
109 if (t == pc_cfg_type::PC_CFG_U32)
110 {
111 return pc_config_set_u32(key, (uint32_t)pc_strtoul(val, nullptr));
112 }
113 if (t == pc_cfg_type::PC_CFG_STR)
114 {
115 return pc_config_set_str(key, val);
116 }
117 return false;
118}
119
120int pc_config_import(const char *ns, const pc_cfg_field *fields, size_t n, const char *text, size_t len)
121{
122 if (!text || !fields || !pc_config_begin(ns)) // GCOVR_EXCL_BR_LINE the begin()-fails half is unreachable:
123 {
124 return 0; // the host config_store backend's pc_config_begin always returns true
125 }
126
127 int count = 0;
128 size_t i = 0;
129 while (i < len)
130 {
131 // Find the end of this line.
132 size_t eol = i;
133 while (eol < len && text[eol] != '\n')
134 {
135 eol++;
136 }
137
138 // Split the line on the first '='.
139 size_t eq = i;
140 while (eq < eol && text[eq] != '=')
141 {
142 eq++;
143 }
144
145 if (eq >= eol) // no '=' on this line
146 {
147 i = eol + 1;
148 continue;
149 }
150 size_t klen = eq - i;
151 size_t vlen = eol - (eq + 1);
152 if (klen > 0 && klen < PC_KEY_MAX && vlen < PC_VAL_MAX)
153 {
154 char key[PC_KEY_MAX];
155 char val[PC_VAL_MAX];
156 memcpy(key, text + i, klen);
157 key[klen] = '\0';
158 memcpy(val, text + eq + 1, vlen);
159 val[vlen] = '\0';
160 if (config_apply_field(fields, n, key, val))
161 {
162 count++;
163 }
164 }
165 i = eol + 1; // skip the newline
166 }
167 return count;
168}
169
170#endif // PC_ENABLE_CONFIG_IO
Schema-driven config export / restore (PC_ENABLE_CONFIG_IO).
Typed NVS configuration store (PC_ENABLE_CONFIG_STORE).
Tiny no-stdlib base-10 number parsers (strtol/strtoul/strtof replacements).
unsigned long pc_strtoul(const char *s, const char **end)
Parse a base-10 unsigned long; sets end past the digits (or to s).
Definition numparse.h:58
Bounded no-heap string builder that fails closed on overflow (one shared copy).
size_t pc_sb_finish(pc_sb *b)
NUL-terminate and return the built length, or 0 if the build overflowed.
Definition strbuf.h:648
void pc_sb_u32(pc_sb *b, uint32_t v)
Append v as decimal (no leading zeros; "0" for zero).
Definition strbuf.h:303
Bump-append target; ok latches false once an append would overflow cap.
Definition strbuf.h:30