cryptnox-pos 1.0.0
Standalone USDC payment terminal firmware (ESP32 + Cryptnox smart card)
Loading...
Searching...
No Matches
settings.cpp
Go to the documentation of this file.
1/*
2 * SPDX-License-Identifier: LGPL-3.0-or-later
3 * Copyright (c) 2026 Cryptnox SA
4 */
5
10
11#include "settings.h"
12#include "settings_rules.h" /* ranges, digest, normalisation — host-tested */
13#include "civil_time.h" /* CIVIL_DST__COUNT */
14
15#include <atomic> /* the chain / network caches, read across tasks */
16#include <stdio.h> /* snprintf — payout address normalisation */
17#include <string.h>
18#include "nvs.h"
19#include "nvs_flash.h" /* settings_wipe_if_new_build — erases the partition */
20#include "esp_log.h"
21#include "esp_random.h"
22
23#include "CW_Utils.h" /* secure_wipe / secure_compare (CODING_RULES §1.4) */
24
25extern "C" {
26#include "keccak256.h"
27}
28
29#include "config.h" /* MAX_FEE / MAX_PRIORITY_FEE — compile-time fee defaults */
30
31/* Same guard main.cpp carries: a config.h without the contract compiles to an
32 * empty string that fails its decode and disables the asset. */
33#ifndef TRON_ADDR_USDT
34#define TRON_ADDR_USDT ""
35#endif
36/* Mainnet halves of both pairs: empty means the asset is refused on mainnet. */
37#ifndef TRON_ADDR_USDT_MAIN
38#define TRON_ADDR_USDT_MAIN ""
39#endif
40#ifndef ADDR_USDC_MAIN
41#define ADDR_USDC_MAIN ""
42#endif
43
44static const char *const TAG = "settings";
45
46#define NS_SETTINGS "settings"
47#define K_BRIGHTNESS "bright"
48#define K_WIFI_SSID "wifi_ssid"
49#define K_WIFI_PASS "wifi_pass"
50#define K_MAX_FEE "max_fee_gw"
51#define K_PRIO_FEE "prio_fee_gw"
52#define K_ADMIN_SALT "adm_salt"
53#define K_ADMIN_HASH "adm_hash"
54#define K_ADMIN_FAILS "adm_fails"
55#define K_CHAIN "chain"
56#define K_MAINNET "mainnet"
57/* Minutes east of UTC, stored biased — see settings_get_tz_offset_min(). */
58#define K_TZ_OFFSET "tz_off"
59#define K_TZ_DST "tz_dst"
60/* Touch calibration, one key per axis, packed min<<16 | max. */
61#define K_TOUCH_X "touch_x"
62#define K_TOUCH_Y "touch_y"
63/* BUILD_ID of the image that last wrote NVS — see settings_wipe_if_new_build. */
64#define K_BUILD_ID "build_id"
65#ifndef BUILD_ID
66#error "BUILD_ID is set from git by the project CMakeLists.txt"
67#endif
68/* The sale between broadcast and verdict — see settings_inflight_save. */
69#define K_INFLIGHT "inflight"
70/* Payout addresses, each stored twice — see settings_get_payout. */
71#define K_PAY_ETH "pay_eth"
72#define K_PAY_ETH2 "pay_eth_e"
73#define K_PAY_TRX "pay_trx"
74#define K_PAY_TRX2 "pay_trx_e"
75/* Token contracts, same treatment — see settings_get_contract. One key pair per
76 * deployment, so a network switch never carries a testnet contract to mainnet. */
77#define K_CT_ETH "ct_eth"
78#define K_CT_ETH2 "ct_eth_e"
79#define K_CT_TRX "ct_trx"
80#define K_CT_TRX2 "ct_trx_e"
81#define K_CT_ETH_M "ct_eth_m"
82#define K_CT_ETH_M2 "ct_eth_me"
83#define K_CT_TRX_M "ct_trx_m"
84#define K_CT_TRX_M2 "ct_trx_me"
85
86/* ADMIN_SALT_LEN, ADMIN_HASH_LEN and ADMIN_CODE_HASH_MAX are in settings_rules.h. */
87
88#define DEFAULT_BRIGHTNESS 80U
89
90/* config.h carries the fees in wei; the UI works in Gwei. */
91#define WEI_PER_GWEI 1000000000ULL
92#define DEFAULT_MAX_FEE_GWEI (uint32_t)(MAX_FEE / WEI_PER_GWEI)
93#define DEFAULT_PRIORITY_FEE_GWEI (uint32_t)(MAX_PRIORITY_FEE / WEI_PER_GWEI)
94
95/******************************************************************
96 * NVS scalar helpers
97 *
98 * A failed read falls back to the default, never a guess, and is not logged
99 * (reads are frequent and the default is correct). A failed write is logged.
100 ******************************************************************/
101static uint8_t nvs_u8_get(const char *key, uint8_t def)
102{
103 uint8_t val = def;
104 nvs_handle_t h;
105 if (nvs_open(NS_SETTINGS, NVS_READONLY, &h) == ESP_OK) {
106 (void)nvs_get_u8(h, key, &val);
107 nvs_close(h);
108 }
109 return val;
110}
111
112static void nvs_u8_set(const char *key, uint8_t val)
113{
114 nvs_handle_t h;
115 if (nvs_open(NS_SETTINGS, NVS_READWRITE, &h) == ESP_OK) {
116 (void)nvs_set_u8(h, key, val);
117 (void)nvs_commit(h);
118 nvs_close(h);
119 } else {
120 ESP_LOGW(TAG, "%s: nvs_open failed", key);
121 }
122}
123
124static uint32_t nvs_u32_get(const char *key, uint32_t def)
125{
126 uint32_t val = def;
127 nvs_handle_t h;
128 if (nvs_open(NS_SETTINGS, NVS_READONLY, &h) == ESP_OK) {
129 (void)nvs_get_u32(h, key, &val);
130 nvs_close(h);
131 }
132 return val;
133}
134
135static void nvs_u32_set(const char *key, uint32_t val)
136{
137 nvs_handle_t h;
138 if (nvs_open(NS_SETTINGS, NVS_READWRITE, &h) == ESP_OK) {
139 (void)nvs_set_u32(h, key, val);
140 (void)nvs_commit(h);
141 nvs_close(h);
142 } else {
143 ESP_LOGW(TAG, "%s: nvs_open failed", key);
144 }
145}
146
147/******************************************************************
148 * Chain and network caches
149 *
150 * Read on nearly every UI pass (per keypad digit), so cached in RAM; -1 means
151 * "not read yet". Atomic: the UI task writes, the main task reads mid-payment.
152 * The chain cache is written through settings_set_chain(). The network cache
153 * keeps the boot value until restart (provision.cpp reboots to apply it).
154 ******************************************************************/
155static std::atomic<int16_t> s_chain_cache{-1};
156static std::atomic<int8_t> s_mainnet_cache{-1};
157
159static void cache_invalidate(void)
160{
161 s_chain_cache.store(-1);
162 s_mainnet_cache.store(-1);
163}
164
166{
167 const int16_t cached = s_chain_cache.load();
168 if (cached >= 0) { return (pos_chain_t)cached; }
169
171 /* Unknown value = a downgrade or a corrupt cell; fall back to the default
172 * rather than charge on a chain no code path can handle. */
173 const uint8_t stored = nvs_u8_get(K_CHAIN, (uint8_t)POS_CHAIN_ETH_USDC);
174 if (stored < (uint8_t)POS_CHAIN__COUNT) {
175 chain = (pos_chain_t)stored;
176 }
177 s_chain_cache.store((int16_t)chain);
178 return chain;
179}
180
182{
183 nvs_u8_set(K_CHAIN, (uint8_t)chain);
184 /* After the write, not before: a reader that arrives in between gets the old
185 * value, which is the one still in flash. */
186 s_chain_cache.store((int16_t)chain);
187}
188
190{
191 const int8_t cached = s_mainnet_cache.load();
192 if (cached >= 0) { return (cached != 0); }
193
194 /* Every way of not knowing (no key, unopenable namespace) lands on mainnet:
195 * a terminal that guesses "testnet" reports payments that settle nowhere. */
196 const bool mainnet = (nvs_u8_get(K_MAINNET, 1U) != 0U);
197 s_mainnet_cache.store(mainnet ? 1 : 0);
198 return mainnet;
199}
200
201void settings_set_mainnet(bool mainnet)
202{
203 nvs_u8_set(K_MAINNET, mainnet ? 1U : 0U);
204 /* Flash only, not the cache: the cache stays the boot value until the
205 * restart, so a sale signed in the window before it still gets the chain id,
206 * endpoint and contract slot of the deployment it was built against. */
207 ESP_LOGW(TAG, "network set to %s", mainnet ? "mainnet" : "testnet");
208}
209
210const char *settings_net_str(const char *testnet, const char *mainnet)
211{
212 return settings_get_mainnet() ? mainnet : testnet;
213}
214
219
220void settings_set_brightness(uint8_t pct)
221{
222 if (pct > 100U) { pct = 100U; }
224}
225
227{
228 /* Stored biased by 720 so it fits the unsigned helpers the rest of this
229 * file uses, and so a missing key reads as UTC rather than as UTC-12. */
230 const uint32_t raw = nvs_u32_get(K_TZ_OFFSET, (uint32_t)TZ_OFFSET_BIAS);
231 return tz_offset_decode(raw); /* nonsense in NVS is UTC, not a wild clock */
232}
233
234bool settings_set_tz_offset_min(int16_t minutes)
235{
236 if (!tz_offset_valid(minutes)) {
237 return false;
238 }
240 return true;
241}
242
244{
246}
247
248bool settings_set_tz_dst(uint8_t rule)
249{
250 if (!tz_dst_valid(rule)) { return false; }
251 nvs_u8_set(K_TZ_DST, rule);
252 return true;
253}
254
255/* Packed two per u32 (min<<16 | max) — two keys instead of four, and an axis
256 * can never be half-written. */
257#define TOUCH_CAL_DEF_MIN 200U
258#define TOUCH_CAL_DEF_MAX 3800U
259
260void settings_get_touch_cal(uint16_t *x_min, uint16_t *x_max,
261 uint16_t *y_min, uint16_t *y_max)
262{
263 const uint32_t def = (TOUCH_CAL_DEF_MIN << 16) | TOUCH_CAL_DEF_MAX;
264 uint32_t x = nvs_u32_get(K_TOUCH_X, def);
265 uint32_t y = nvs_u32_get(K_TOUCH_Y, def);
266 *x_min = (uint16_t)(x >> 16); *x_max = (uint16_t)(x & 0xFFFFU);
267 *y_min = (uint16_t)(y >> 16); *y_max = (uint16_t)(y & 0xFFFFU);
268}
269
270void settings_set_touch_cal(uint16_t x_min, uint16_t x_max,
271 uint16_t y_min, uint16_t y_max)
272{
273 /* A smaller span is a double-tap on one spot; storing it would leave the
274 * panel, calibration screen included, untappable. */
275 const uint16_t MIN_SPAN = 500U;
276 if ((x_max < x_min + MIN_SPAN) || (y_max < y_min + MIN_SPAN)) {
277 ESP_LOGW(TAG, "touch cal rejected: span too small");
278 return;
279 }
280 nvs_u32_set(K_TOUCH_X, ((uint32_t)x_min << 16) | x_max);
281 nvs_u32_set(K_TOUCH_Y, ((uint32_t)y_min << 16) | y_max);
282}
283
285{
286 bool present = false;
287 nvs_handle_t h;
288 if (nvs_open(NS_SETTINGS, NVS_READONLY, &h) == ESP_OK) {
289 char ssid[33] = {0};
290 size_t len = sizeof(ssid);
291 present = (nvs_get_str(h, K_WIFI_SSID, ssid, &len) == ESP_OK) &&
292 (ssid[0] != '\0');
293 nvs_close(h);
294 }
295 return present;
296}
297
298bool settings_get_wifi(char *ssid, size_t ssid_n, char *pass, size_t pass_n)
299{
300 if ((ssid == NULL) || (pass == NULL) || (ssid_n == 0U) || (pass_n == 0U)) {
301 return false;
302 }
303 ssid[0] = '\0';
304 pass[0] = '\0';
305
306 bool ok = false;
307 nvs_handle_t h;
308 if (nvs_open(NS_SETTINGS, NVS_READONLY, &h) == ESP_OK) {
309 size_t ls = ssid_n;
310 size_t lp = pass_n;
311 if ((nvs_get_str(h, K_WIFI_SSID, ssid, &ls) == ESP_OK) &&
312 (nvs_get_str(h, K_WIFI_PASS, pass, &lp) == ESP_OK) &&
313 (ssid[0] != '\0')) {
314 ok = true;
315 } else {
316 ssid[0] = '\0';
317 pass[0] = '\0';
318 }
319 nvs_close(h);
320 }
321 return ok;
322}
323
324void settings_set_wifi(const char *ssid, const char *pass)
325{
326 if ((ssid == NULL) || (pass == NULL)) { return; }
327 nvs_handle_t h;
328 if (nvs_open(NS_SETTINGS, NVS_READWRITE, &h) == ESP_OK) {
329 (void)nvs_set_str(h, K_WIFI_SSID, ssid);
330 (void)nvs_set_str(h, K_WIFI_PASS, pass);
331 (void)nvs_commit(h);
332 nvs_close(h);
333 } else {
334 ESP_LOGW(TAG, "wifi: nvs_open failed");
335 }
336}
337
338/* Fee bounds (settings_rules.h) are enforced here, not by the caller: these feed
339 * tx.max_fee, the gas ceiling the customer's card pays. Out of range in flash is
340 * a corrupt cell and reads as the default. */
341static uint32_t fee_get(const char *key, uint32_t dflt)
342{
343 const uint32_t v = nvs_u32_get(key, dflt);
344 return ((v < FEE_GWEI_MIN) || (v > FEE_GWEI_MAX)) ? dflt : v;
345}
346
351
356
357bool settings_set_fees_gwei(uint32_t max_gwei, uint32_t prio_gwei)
358{
359 if (fee_pair_check(max_gwei, prio_gwei) != FEE_PAIR_OK) { return false; }
360 nvs_u32_set(K_MAX_FEE, max_gwei);
361 nvs_u32_set(K_PRIO_FEE, prio_gwei);
362 return true;
363}
364
365/* admin_derive() — keccak256(salt || code) — is in settings_rules.h. */
366
367static void admin_set_fails(uint8_t n)
368{
370}
371
373{
374 bool present = false;
375 nvs_handle_t h;
376 if (nvs_open(NS_SETTINGS, NVS_READONLY, &h) == ESP_OK) {
377 size_t len = 0U;
378 present = (nvs_get_blob(h, K_ADMIN_HASH, NULL, &len) == ESP_OK) &&
379 (len == ADMIN_HASH_LEN);
380 nvs_close(h);
381 }
382 return present;
383}
384
385bool settings_set_admin_code(const char *code)
386{
387 if (code == NULL) { return false; }
388
389 uint8_t salt[ADMIN_SALT_LEN];
390 esp_fill_random(salt, sizeof(salt));
391
392 uint8_t hash[ADMIN_HASH_LEN];
393 admin_derive(code, salt, hash);
394
395 /* Reported rather than swallowed: the menu — factory reset included — is
396 * unreachable without a stored code, so a silent write failure would leave
397 * a terminal only a USB erase can rescue. */
398 bool ok = false;
399 nvs_handle_t h;
400 if (nvs_open(NS_SETTINGS, NVS_READWRITE, &h) == ESP_OK) {
401 ok = (nvs_set_blob(h, K_ADMIN_SALT, salt, sizeof(salt)) == ESP_OK) &&
402 (nvs_set_blob(h, K_ADMIN_HASH, hash, sizeof(hash)) == ESP_OK) &&
403 (nvs_set_u8(h, K_ADMIN_FAILS, 0U) == ESP_OK) &&
404 (nvs_commit(h) == ESP_OK);
405 nvs_close(h);
406 ESP_LOGI(TAG, "admin code set: %s", ok ? "ok" : "FAILED");
407 } else {
408 ESP_LOGW(TAG, "admin code: nvs_open failed");
409 }
410 CW_Utils::secure_wipe(hash, sizeof(hash));
411 return ok;
412}
413
414bool settings_check_admin_code(const char *code)
415{
416 if (code == NULL) { return false; }
417
418 uint8_t salt[ADMIN_SALT_LEN];
419 uint8_t stored[ADMIN_HASH_LEN];
420 bool have = false;
421
422 nvs_handle_t h;
423 if (nvs_open(NS_SETTINGS, NVS_READONLY, &h) == ESP_OK) {
424 size_t ls = sizeof(salt);
425 size_t lh = sizeof(stored);
426 have = (nvs_get_blob(h, K_ADMIN_SALT, salt, &ls) == ESP_OK) &&
427 (nvs_get_blob(h, K_ADMIN_HASH, stored, &lh) == ESP_OK) &&
428 (ls == ADMIN_SALT_LEN) && (lh == ADMIN_HASH_LEN);
429 nvs_close(h);
430 }
431 if (!have) { return false; } /* no code stored — nothing to match */
432
433 uint8_t calc[ADMIN_HASH_LEN];
434 admin_derive(code, salt, calc);
435 const bool ok = CW_Utils::secure_compare(calc, stored, ADMIN_HASH_LEN);
436 CW_Utils::secure_wipe(calc, sizeof(calc));
437
438 if (ok) {
439 if (settings_admin_fail_count() != 0U) { admin_set_fails(0U); }
440 } else {
441 const uint8_t n = settings_admin_fail_count();
442 admin_set_fails((n < 255U) ? (uint8_t)(n + 1U) : 255U);
443 ESP_LOGW(TAG, "admin unlock failed (%u consecutive)", (unsigned)(n + 1U));
444 }
445 return ok;
446}
447
449{
450 return nvs_u8_get(K_ADMIN_FAILS, 0U);
451}
452
453/* Money-carrying addresses (payout recipient, token contract): a value and an
454 * echo copy, compared on read, so a torn write or flipped bit in NVS cannot
455 * silently redirect a payment or switch the asset. */
456
466static bool dual_get(const char *k_val, const char *k_echo, const char *def,
467 const char *what, char *out, size_t n)
468{
469 if ((out == NULL) || (n == 0U)) { return false; }
470 out[0] = '\0';
471
472 char val[SETTINGS_PAYOUT_MAX] = { 0 };
473 char echo[SETTINGS_PAYOUT_MAX] = { 0 };
474 bool stored = false;
475
476 nvs_handle_t h;
477 if (nvs_open(NS_SETTINGS, NVS_READONLY, &h) == ESP_OK) {
478 size_t lv = sizeof(val);
479 size_t le = sizeof(echo);
480 stored = (nvs_get_str(h, k_val, val, &lv) == ESP_OK) &&
481 (nvs_get_str(h, k_echo, echo, &le) == ESP_OK);
482 nvs_close(h);
483 }
484
485 /* secure_compare, not strcmp: this decides where money goes, so the
486 * comparison must not leak on length or short-circuit on the first byte. */
487 if (stored && !CW_Utils::secure_compare(reinterpret_cast<const uint8_t *>(val),
488 reinterpret_cast<const uint8_t *>(echo),
489 sizeof(val))) {
490 ESP_LOGE(TAG, "%s: stored copies disagree - using config.h", what);
491 stored = false;
492 }
493
494 (void)snprintf(out, n, "%s", stored ? val : def);
495 CW_Utils::secure_wipe(reinterpret_cast<uint8_t *>(val), sizeof(val));
496 CW_Utils::secure_wipe(reinterpret_cast<uint8_t *>(echo), sizeof(echo));
497 return stored;
498}
499
501static bool dual_set(const char *k_val, const char *k_echo, bool tron,
502 const char *what, const char *addr)
503{
504 /* Normalise to the form the getter hands back, so the echo comparison
505 * compares like with like on the next boot. */
506 char norm[SETTINGS_PAYOUT_MAX];
507 if (!settings_addr_normalise(tron, addr, norm)) { return false; }
508
509 bool ok = false;
510 nvs_handle_t h;
511 if (nvs_open(NS_SETTINGS, NVS_READWRITE, &h) == ESP_OK) {
512 ok = (nvs_set_str(h, k_val, norm) == ESP_OK) &&
513 (nvs_set_str(h, k_echo, norm) == ESP_OK) &&
514 (nvs_commit(h) == ESP_OK);
515 nvs_close(h);
516 }
517 if (ok) {
518 ESP_LOGW(TAG, "%s set to %s", what, norm);
519 } else {
520 ESP_LOGE(TAG, "%s: NVS write failed", what);
521 }
522 return ok;
523}
524
525bool settings_get_payout(bool tron, char *out, size_t n)
526{
527 /* Ethereum addresses are handed out "0x"-prefixed so every caller can parse
528 * them directly; config.h stores them bare, the setup form accepts either. */
529 return tron
530 ? dual_get(K_PAY_TRX, K_PAY_TRX2, TRON_ADDR_TO, "payout(tron)", out, n)
531 : dual_get(K_PAY_ETH, K_PAY_ETH2, "0x" ADDR_TO, "payout(eth)", out, n);
532}
533
534bool settings_has_payout(bool tron)
535{
536 char scratch[SETTINGS_PAYOUT_MAX];
537 const bool stored = settings_get_payout(tron, scratch, sizeof(scratch));
538 CW_Utils::secure_wipe(reinterpret_cast<uint8_t *>(scratch), sizeof(scratch));
539 return stored;
540}
541
542bool settings_set_payout(bool tron, const char *addr)
543{
544 return tron
545 ? dual_set(K_PAY_TRX, K_PAY_TRX2, true, "payout(tron)", addr)
546 : dual_set(K_PAY_ETH, K_PAY_ETH2, false, "payout(eth)", addr);
547}
548
549bool settings_get_contract(pos_chain_t chain, char *out, size_t n)
550{
551 const bool m = settings_get_mainnet();
552 const bool tron = (chain == POS_CHAIN_TRON_USDT);
553 if (!tron && (chain != POS_CHAIN_ETH_USDC)) {
554 if ((out != NULL) && (n > 0U)) { out[0] = '\0'; }
555 return false;
556 }
557 return tron
560 "contract(tron)", out, n)
562 m ? "0x" ADDR_USDC_MAIN : "0x" ADDR_USDC,
563 "contract(eth)", out, n);
564}
565
566bool settings_set_contract(pos_chain_t chain, const char *addr)
567{
568 const bool m = settings_get_mainnet();
569 const bool tron = (chain == POS_CHAIN_TRON_USDT);
570 if (!tron && (chain != POS_CHAIN_ETH_USDC)) { return false; }
571 return tron
573 true, "contract(tron)", addr)
575 false, "contract(eth)", addr);
576}
577
579{
580 nvs_handle_t h;
581 uint32_t stored = 0U; /* no key yet reads as 0, i.e. older than any build */
582 if (nvs_open(NS_SETTINGS, NVS_READONLY, &h) == ESP_OK) {
583 (void)nvs_get_u32(h, K_BUILD_ID, &stored);
584 nvs_close(h);
585 }
586 /* Only equality keeps the settings. Newer, older or no stamp means another
587 * image wrote this NVS, possibly one built to plant a payout address. An
588 * ordering test would let such an image stamp a large number and keep it.
589 * Logged either way so both numbers are on the console. */
590 if (stored == BUILD_ID) {
591 ESP_LOGI(TAG, "build %u - settings written by this build, kept",
592 (unsigned)BUILD_ID);
593 return false;
594 }
595
596 ESP_LOGW(TAG, "stamped %u, running build %u - erasing NVS",
597 (unsigned)stored, (unsigned)BUILD_ID);
598 esp_err_t err = nvs_flash_erase();
599 if (err == ESP_OK) { err = nvs_flash_init(); }
600 if (err != ESP_OK) {
601 /* No stamp written, so the next boot retries. Meanwhile the unit keeps
602 * its old settings: one that still takes payments beats a brick. */
603 ESP_LOGE(TAG, "NVS erase failed (%s) - settings kept", esp_err_to_name(err));
604 return false;
605 }
606
607 /* Stamp straight away: without it every boot would wipe again. */
608 if (nvs_open(NS_SETTINGS, NVS_READWRITE, &h) == ESP_OK) {
609 (void)nvs_set_u32(h, K_BUILD_ID, (uint32_t)BUILD_ID);
610 (void)nvs_commit(h);
611 nvs_close(h);
612 }
613 /* The caches describe the erased partition. Nothing has read them yet at
614 * this point, but invalidating does not rely on that ordering. */
616 return true;
617}
618
620{
621 nvs_handle_t h;
622 if (nvs_open(NS_SETTINGS, NVS_READWRITE, &h) == ESP_OK) {
623 /* Drops brightness, Wi-Fi creds, the admin code and any operator-set
624 * payout address, so the terminal comes back up in first-run setup. */
625 (void)nvs_erase_all(h);
626 /* Restore the build stamp, or the next boot would wipe again and report
627 * an update that never happened. */
628 (void)nvs_set_u32(h, K_BUILD_ID, (uint32_t)BUILD_ID);
629 (void)nvs_commit(h);
630 nvs_close(h);
631 ESP_LOGW(TAG, "settings: factory reset");
632 }
633 cache_invalidate(); /* the chain and network flag went with the erase */
634
635 /* provision.cpp's namespace, cleared here because this function means
636 * "forget the operator" and a new one must not inherit any stored keys. */
637 if (nvs_open("prov", NVS_READWRITE, &h) == ESP_OK) {
638 (void)nvs_erase_all(h);
639 (void)nvs_commit(h);
640 nvs_close(h);
641 ESP_LOGW(TAG, "settings: portal namespace cleared");
642 }
643}
644
645bool settings_inflight_save(const void *rec, size_t n)
646{
647 if ((rec == NULL) || (n == 0U)) { return false; }
648 nvs_handle_t h;
649 if (nvs_open(NS_SETTINGS, NVS_READWRITE, &h) != ESP_OK) {
650 ESP_LOGE(TAG, "inflight: nvs_open failed - sale not persisted");
651 return false;
652 }
653 const bool ok = (nvs_set_blob(h, K_INFLIGHT, rec, n) == ESP_OK) &&
654 (nvs_commit(h) == ESP_OK);
655 nvs_close(h);
656 if (!ok) { ESP_LOGE(TAG, "inflight: write failed - sale not persisted"); }
657 return ok;
658}
659
660bool settings_inflight_load(void *rec, size_t n)
661{
662 if ((rec == NULL) || (n == 0U)) { return false; }
663 nvs_handle_t h;
664 if (nvs_open(NS_SETTINGS, NVS_READONLY, &h) != ESP_OK) { return false; }
665 size_t len = n;
666 const esp_err_t err = nvs_get_blob(h, K_INFLIGHT, rec, &len);
667 nvs_close(h);
668 /* A size mismatch is a different layout; settings_wipe_if_new_build already
669 * prevents inheriting one, so this is belt and braces, not migration. */
670 return (err == ESP_OK) && (len == n);
671}
672
674{
675 nvs_handle_t h;
676 if (nvs_open(NS_SETTINGS, NVS_READWRITE, &h) != ESP_OK) { return; }
677 /* NOT_FOUND (no broadcast happened) is the ordinary case; writes nothing. */
678 if (nvs_erase_key(h, K_INFLIGHT) == ESP_OK) { (void)nvs_commit(h); }
679 nvs_close(h);
680}
TZ-independent calendar arithmetic and date-string parsing.
@ CIVIL_DST_NONE
Definition civil_time.h:100
#define TRON_ADDR_USDT
#define ADDR_USDC_MAIN
#define TRON_ADDR_USDT_MAIN
static const char *const TAG
Definition eth_rpc.cpp:33
Original Keccak-256 digest as used by Ethereum.
static esp_err_t ok(httpd_req_t *req, const char *msg)
200 with a plain-text message.
static uint32_t nvs_u32_get(const char *key, uint32_t def)
Definition settings.cpp:124
bool settings_check_admin_code(const char *code)
Check a candidate code, maintaining the failure counter.
Definition settings.cpp:414
static void cache_invalidate(void)
Forget both, so the next read goes back to flash. For the erase paths.
Definition settings.cpp:159
#define K_PAY_ETH
Definition settings.cpp:71
#define K_TOUCH_X
Definition settings.cpp:61
#define K_TZ_DST
Definition settings.cpp:59
bool settings_has_wifi(void)
true if a Wi-Fi SSID has been stored.
Definition settings.cpp:284
#define K_MAINNET
Definition settings.cpp:56
bool settings_get_wifi(char *ssid, size_t ssid_n, char *pass, size_t pass_n)
Read the stored Wi-Fi credentials.
Definition settings.cpp:298
#define K_CT_TRX2
Definition settings.cpp:80
#define K_ADMIN_FAILS
Definition settings.cpp:54
bool settings_set_tz_offset_min(int16_t minutes)
Store the panel clock's UTC offset.
Definition settings.cpp:234
static uint32_t fee_get(const char *key, uint32_t dflt)
Definition settings.cpp:341
#define K_TOUCH_Y
Definition settings.cpp:62
#define K_WIFI_SSID
Definition settings.cpp:48
#define K_CT_TRX_M2
Definition settings.cpp:84
#define K_WIFI_PASS
Definition settings.cpp:49
#define K_CT_ETH
Definition settings.cpp:77
bool settings_wipe_if_new_build(void)
Erase NVS unless this exact build is the one that wrote it.
Definition settings.cpp:578
const char * settings_net_str(const char *testnet, const char *mainnet)
Pick the string belonging to the network the terminal is on.
Definition settings.cpp:210
#define K_PAY_TRX
Definition settings.cpp:73
#define K_TZ_OFFSET
Definition settings.cpp:58
#define K_INFLIGHT
Definition settings.cpp:69
#define K_BRIGHTNESS
Definition settings.cpp:47
#define K_CT_ETH2
Definition settings.cpp:78
pos_chain_t settings_get_chain(void)
Selected chain, or POS_CHAIN_ETH_USDC if never set.
Definition settings.cpp:165
bool settings_get_contract(pos_chain_t chain, char *out, size_t n)
Read the token-contract address for a network.
Definition settings.cpp:549
static bool dual_set(const char *k_val, const char *k_echo, bool tron, const char *what, const char *addr)
Write a dual-stored address, normalising Ethereum to "0x"-prefixed.
Definition settings.cpp:501
static uint8_t nvs_u8_get(const char *key, uint8_t def)
Definition settings.cpp:101
int16_t settings_get_tz_offset_min(void)
The panel clock's standard (winter) offset from UTC, in minutes east.
Definition settings.cpp:226
#define K_CT_TRX
Definition settings.cpp:79
#define K_ADMIN_HASH
Definition settings.cpp:53
static std::atomic< int16_t > s_chain_cache
Definition settings.cpp:155
uint8_t settings_get_tz_dst(void)
The clock's DST rule, a civil_dst_t; CIVIL_DST_NONE when unset.
Definition settings.cpp:243
uint32_t settings_get_priority_fee_gwei(void)
EIP-1559 max priority fee (tip) per gas, in Gwei.
Definition settings.cpp:352
bool settings_set_fees_gwei(uint32_t max_gwei, uint32_t prio_gwei)
Persist the max fee and the tip per gas (Gwei), as a pair.
Definition settings.cpp:357
void settings_set_wifi(const char *ssid, const char *pass)
Persist Wi-Fi credentials (plaintext — see README threat model).
Definition settings.cpp:324
bool settings_get_payout(bool tron, char *out, size_t n)
Read the payout address for a network.
Definition settings.cpp:525
bool settings_set_admin_code(const char *code)
Store a new admin code, with a fresh random salt.
Definition settings.cpp:385
uint8_t settings_get_brightness(void)
Backlight level in percent, or 80 if never set.
Definition settings.cpp:215
static std::atomic< int8_t > s_mainnet_cache
Definition settings.cpp:156
static void nvs_u8_set(const char *key, uint8_t val)
Definition settings.cpp:112
#define NS_SETTINGS
Definition settings.cpp:46
#define DEFAULT_MAX_FEE_GWEI
Definition settings.cpp:92
void settings_set_chain(pos_chain_t chain)
Persist the selected chain.
Definition settings.cpp:181
#define K_CT_ETH_M2
Definition settings.cpp:82
#define TOUCH_CAL_DEF_MIN
Definition settings.cpp:257
bool settings_inflight_load(void *rec, size_t n)
Read the persisted sale back. false if none, or not n bytes.
Definition settings.cpp:660
#define DEFAULT_PRIORITY_FEE_GWEI
Definition settings.cpp:93
#define K_CHAIN
Definition settings.cpp:55
void settings_set_mainnet(bool mainnet)
Persist the production/test network choice.
Definition settings.cpp:201
#define K_CT_TRX_M
Definition settings.cpp:83
#define K_BUILD_ID
Definition settings.cpp:64
#define K_ADMIN_SALT
Definition settings.cpp:52
bool settings_set_contract(pos_chain_t chain, const char *addr)
Persist a token-contract address, value and echo copy.
Definition settings.cpp:566
void settings_get_touch_cal(uint16_t *x_min, uint16_t *x_max, uint16_t *y_min, uint16_t *y_max)
Raw XPT2046 range that maps to the panel's four edges.
Definition settings.cpp:260
bool settings_set_payout(bool tron, const char *addr)
Persist a payout address, writing both the value and its echo copy.
Definition settings.cpp:542
void settings_factory_reset(void)
Erase all stored settings (brightness, auto, Wi-Fi creds, fees).
Definition settings.cpp:619
uint8_t settings_admin_fail_count(void)
Consecutive failed unlock attempts, persisted.
Definition settings.cpp:448
#define K_MAX_FEE
Definition settings.cpp:50
bool settings_set_tz_dst(uint8_t rule)
Store the DST rule.
Definition settings.cpp:248
#define K_PAY_ETH2
Definition settings.cpp:72
void settings_set_brightness(uint8_t pct)
Persist the backlight level (0..100).
Definition settings.cpp:220
#define DEFAULT_BRIGHTNESS
Definition settings.cpp:88
bool settings_has_admin_code(void)
true once an admin code exists.
Definition settings.cpp:372
#define K_PAY_TRX2
Definition settings.cpp:74
void settings_set_touch_cal(uint16_t x_min, uint16_t x_max, uint16_t y_min, uint16_t y_max)
Persist a calibration. Rejected (and ignored) if an axis is inverted or collapsed — a bad store here ...
Definition settings.cpp:270
bool settings_get_mainnet(void)
true when the terminal is on the production networks.
Definition settings.cpp:189
#define K_CT_ETH_M
Definition settings.cpp:81
void settings_inflight_clear(void)
Drop the persisted sale. Writes nothing when there is none.
Definition settings.cpp:673
#define K_PRIO_FEE
Definition settings.cpp:51
bool settings_has_payout(bool tron)
Whether an operator has actually set the payout address for a network.
Definition settings.cpp:534
uint32_t settings_get_max_fee_gwei(void)
EIP-1559 max fee per gas, in Gwei.
Definition settings.cpp:347
static void admin_set_fails(uint8_t n)
Definition settings.cpp:367
#define TOUCH_CAL_DEF_MAX
Definition settings.cpp:258
static bool dual_get(const char *k_val, const char *k_echo, const char *def, const char *what, char *out, size_t n)
Read a dual-stored address, falling back to def on any doubt.
Definition settings.cpp:466
bool settings_inflight_save(const void *rec, size_t n)
Persist the sale between broadcast and verdict (opaque record).
Definition settings.cpp:645
static void nvs_u32_set(const char *key, uint32_t val)
Definition settings.cpp:135
Persistent device settings stored in NVS (backlight, Wi-Fi creds).
pos_chain_t
Which chain (and therefore which asset) the terminal charges in.
Definition settings.h:33
@ POS_CHAIN_ETH_USDC
Definition settings.h:34
@ POS_CHAIN__COUNT
Definition settings.h:43
@ POS_CHAIN_TRON_USDT
Definition settings.h:36
#define SETTINGS_PAYOUT_MAX
Longest payout address plus NUL — "0x" + 40 hex, or 34 base58 Tron.
Definition settings.h:214
What a setting may be, apart from where it is stored.
static void admin_derive(const char *code, const uint8_t *salt, uint8_t out[ADMIN_HASH_LEN])
Derive the stored digest: a single keccak256 over salt || code.
static bool settings_addr_normalise(bool tron, const char *addr, char norm[SETTINGS_PAYOUT_MAX])
The form a payout address or contract is stored in: Tron as given, Ethereum "0x"-prefixed whichever w...
static uint32_t tz_offset_encode(int16_t minutes)
A valid offset in its stored, biased form.
#define FEE_GWEI_MIN
@ FEE_PAIR_OK
static bool tz_dst_valid(long rule)
true for a DST rule the clock knows.
static uint8_t tz_dst_decode(uint8_t r)
A stored DST rule, or no DST if it is not one the clock knows.
static int16_t tz_offset_decode(uint32_t raw)
The stored form back to minutes; nonsense in NVS is UTC, not a wild clock.
static bool tz_offset_valid(long minutes)
true for an offset the clock accepts (TZ_OFFSET_MIN .. _MAX).
#define ADMIN_SALT_LEN
#define TZ_OFFSET_BIAS
static fee_pair_t fee_pair_check(unsigned long max_gwei, unsigned long prio_gwei)
Check a (max fee, tip) pair in Gwei; the range is checked first.
#define ADMIN_HASH_LEN
#define FEE_GWEI_MAX