upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/tests/unit/test_beacon_price.c
diff options
context:
space:
mode:
authorYour Name <you@example.com>2026-05-19 13:14:48 +0530
committerYour Name <you@example.com>2026-05-19 13:14:48 +0530
commitfe6aa9663d4cdabdc6e71db6068f8cd9e3739ffe (patch)
tree8cadb07243c07a6b3fa9453b239c9ac5cb02b454 /tests/unit/test_beacon_price.c
parent77031f06a9a87320d011f501590985161d1eb305 (diff)
feat: WiFi beacon price discovery via Vendor IE (two-board verified)
Price discovery allows TollGate ESP32 boards to advertise their per-step price via WiFi Vendor-Specific Information Elements (OUI 0xC0FFEE) in beacon and probe response frames. Nearby boards passively scan and build a market view of competing TollGates without requiring internet access. Features: - beacon_price.c/h: 26-byte packed Vendor IE payload (price, step, metric, mint_hash, geohash, npub_hash), injected via esp_wifi_set_vendor_ie() - market.c/h: Passive WiFi scan receiver, vendor IE callback parsing, BSSID-correlated market entries, effective price ranking - GET /market API endpoint: JSON market snapshot with discovered entries - AP-only services: beacon + market + API start on WIFI_EVENT_AP_START, independent of STA connectivity - STA reconnect fix: 2s delay between retries creates scan windows; s_sta_connecting guard prevents double-connect - write-config-ap-only-a/b Makefile targets for STA-less testing - market_tick() in main loop, client price comparison logging Hardware verified: both boards discover each other via Vendor IE beacons. Board A sees TollGate-C0E9CA (RSSI=-30), Board B sees TollGate-B96D80 (RSSI=-25). test-market.mjs: 9/9, test-price-discovery.mjs: 7/7 per board. Unit tests: 45 new assertions across test_beacon_price (28) and test_market (17). All 15 test suites pass. ESP-IDF build clean for ESP32-S3.
Diffstat (limited to 'tests/unit/test_beacon_price.c')
-rw-r--r--tests/unit/test_beacon_price.c132
1 files changed, 132 insertions, 0 deletions
diff --git a/tests/unit/test_beacon_price.c b/tests/unit/test_beacon_price.c
new file mode 100644
index 0000000..9574478
--- /dev/null
+++ b/tests/unit/test_beacon_price.c
@@ -0,0 +1,132 @@
1#include "test_framework.h"
2#include "../../main/config.h"
3#include "../../main/identity.h"
4#include <string.h>
5#include <stdio.h>
6#include <mbedtls/sha256.h>
7
8#include "../../main/beacon_price.h"
9
10static tollgate_config_t g_test_config;
11static tollgate_identity_t g_test_identity;
12
13const tollgate_config_t *tollgate_config_get(void) { return &g_test_config; }
14const tollgate_identity_t *identity_get(void) { return &g_test_identity; }
15
16int main(void)
17{
18 printf("=== test_beacon_price ===\n");
19
20 memset(&g_test_config, 0, sizeof(g_test_config));
21 strncpy(g_test_config.mint_url, "https://testnut.cashu.space", sizeof(g_test_config.mint_url) - 1);
22 strncpy(g_test_config.metric, "milliseconds", sizeof(g_test_config.metric) - 1);
23 g_test_config.price_per_step = 21;
24 g_test_config.step_size_ms = 60000;
25 strncpy(g_test_config.nostr_geohash, "u281w0dfz", sizeof(g_test_config.nostr_geohash) - 1);
26
27 memset(&g_test_identity, 0, sizeof(g_test_identity));
28 strncpy(g_test_identity.npub_hex, "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", 64);
29 g_test_identity.initialized = true;
30
31 printf("\n--- tollgate_price_payload_t size ---\n");
32 {
33 ASSERT_EQ_INT(26, (int)TOLLGATE_IE_PAYLOAD_SIZE, "payload is 26 bytes");
34 ASSERT_EQ_INT(32, (int)TOLLGATE_IE_TOTAL_SIZE, "total IE is 32 bytes");
35 }
36
37 printf("\n--- beacon_price_hash_mint ---\n");
38 {
39 uint8_t hash[4];
40 beacon_price_hash_mint("https://testnut.cashu.space", hash);
41
42 uint8_t expected[32];
43 mbedtls_sha256((const unsigned char *)"https://testnut.cashu.space",
44 strlen("https://testnut.cashu.space"), expected, 0);
45 ASSERT_MEM_EQ(expected, hash, 4, "mint_hash matches SHA-256 prefix");
46
47 uint8_t hash2[4];
48 beacon_price_hash_mint("https://other.mint.url", hash2);
49 ASSERT(memcmp(hash, hash2, 4) != 0, "different mint URLs produce different hashes");
50 }
51
52 printf("\n--- beacon_price_hash_npub ---\n");
53 {
54 uint8_t hash[4];
55 beacon_price_hash_npub("abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", hash);
56
57 uint8_t expected[32];
58 mbedtls_sha256((const unsigned char *)"abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
59 64, expected, 0);
60 ASSERT_MEM_EQ(expected, hash, 4, "npub_hash matches SHA-256 prefix");
61 }
62
63 printf("\n--- beacon_price_build_ie (time metric) ---\n");
64 {
65 tollgate_price_ie_t ie;
66 beacon_price_build_ie(&ie);
67
68 ASSERT_EQ_INT(0xDD, ie.element_id, "element_id is 0xDD");
69 ASSERT_EQ_INT(4 + 26, ie.length, "length is 30 (4 header + 26 payload)");
70 ASSERT_EQ_INT(0xC0, ie.vendor_oui[0], "OUI byte 0");
71 ASSERT_EQ_INT(0xFF, ie.vendor_oui[1], "OUI byte 1");
72 ASSERT_EQ_INT(0xEE, ie.vendor_oui[2], "OUI byte 2");
73 ASSERT_EQ_INT(0x01, ie.vendor_oui_type, "OUI type is 0x01");
74
75 ASSERT_EQ_INT(1, ie.payload.version, "version is 1");
76 ASSERT_EQ_INT(0, ie.payload.metric, "metric is 0 (milliseconds)");
77 ASSERT_EQ_INT(21, ie.payload.price_per_step, "price is 21");
78 ASSERT_EQ_INT(60000, (int)ie.payload.step_size, "step_size is 60000");
79
80 uint8_t expected_mint_hash[4];
81 beacon_price_hash_mint("https://testnut.cashu.space", expected_mint_hash);
82 ASSERT_MEM_EQ(expected_mint_hash, ie.payload.mint_hash, 4, "mint_hash matches");
83
84 ASSERT_EQ_INT(9, ie.payload.geohash_len, "geohash_len is 9");
85 ASSERT(memcmp(ie.payload.geohash, "u281w0dfz", 9) == 0, "geohash matches");
86 }
87
88 printf("\n--- beacon_price_build_ie (bytes metric) ---\n");
89 {
90 strncpy(g_test_config.metric, "bytes", sizeof(g_test_config.metric) - 1);
91 g_test_config.step_size_bytes = 22020096;
92 g_test_config.price_per_step = 5;
93
94 tollgate_price_ie_t ie;
95 beacon_price_build_ie(&ie);
96
97 ASSERT_EQ_INT(1, ie.payload.metric, "metric is 1 (bytes)");
98 ASSERT_EQ_INT(5, ie.payload.price_per_step, "price is 5");
99 ASSERT_EQ_INT(22020096, (int)ie.payload.step_size, "step_size is 22020096 bytes");
100
101 strncpy(g_test_config.metric, "milliseconds", sizeof(g_test_config.metric) - 1);
102 g_test_config.step_size_ms = 60000;
103 g_test_config.price_per_step = 21;
104 }
105
106 printf("\n--- roundtrip: build → parse ---\n");
107 {
108 tollgate_price_ie_t ie;
109 beacon_price_build_ie(&ie);
110
111 vendor_ie_data_t *vnd_ie = (vendor_ie_data_t *)&ie;
112
113 ASSERT(vnd_ie->length >= 4 + (int)TOLLGATE_IE_PAYLOAD_SIZE, "vendor IE length sufficient");
114
115 const tollgate_price_payload_t *parsed = (const tollgate_price_payload_t *)vnd_ie->payload;
116 ASSERT_EQ_INT(1, parsed->version, "parsed version");
117 ASSERT_EQ_INT(0, parsed->metric, "parsed metric");
118 ASSERT_EQ_INT(21, parsed->price_per_step, "parsed price");
119 ASSERT_EQ_INT(60000, (int)parsed->step_size, "parsed step_size");
120 ASSERT_EQ_INT(9, parsed->geohash_len, "parsed geohash_len");
121 }
122
123 printf("\n--- struct packing check ---\n");
124 {
125 tollgate_price_ie_t ie;
126 memset(&ie, 0, sizeof(ie));
127 int expected_size = 2 + 3 + 1 + 26;
128 ASSERT_EQ_INT(expected_size, (int)sizeof(tollgate_price_ie_t), "no padding in struct");
129 }
130
131 TEST_SUMMARY();
132}