upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYour Name <you@example.com>2026-05-23 06:24:33 +0530
committerYour Name <you@example.com>2026-05-23 06:24:33 +0530
commit0642900ae44d84cfe24abe6911dbed4cd31ab8ad (patch)
tree01f960d54819133fe185ffe8d559bca9bdcf6bd9
parent57e363fd30ef7715ac1dfb1484b8375e06160e1c (diff)
Phase 8a-8b: extract beacon_price and market into component
- tollgate_core_beacon.c/h: pure logic (hash_mint, hash_npub, build_ie) with platform-agnostic tollgate_beacon_config_t input struct - tollgate_core_market.c/h: market table logic (parse_ie, find_cheapest, update_ssid) with platform callbacks for time - main/beacon_price.c: thin shim delegates to component, WiFi IE start/stop - main/market.h: typedef aliases to component types - All 21 unit tests pass, ESP-IDF build passes - Hardware smoke test: 5/6 pass (AP, portal, grant/revoke, firewall) - Market scanner discovers other TollGates via vendor IEs on hardware
-rw-r--r--components/tollgate_core/CMakeLists.txt2
-rw-r--r--components/tollgate_core/src/tollgate_core_beacon.c48
-rw-r--r--components/tollgate_core/src/tollgate_core_beacon.h53
-rw-r--r--components/tollgate_core/src/tollgate_core_market.c121
-rw-r--r--components/tollgate_core/src/tollgate_core_market.h49
-rw-r--r--main/beacon_price.c54
-rw-r--r--main/beacon_price.h31
-rw-r--r--main/market.c190
-rw-r--r--main/market.h28
-rw-r--r--tests/unit/Makefile12
-rwxr-xr-xtests/unit/test_beacon_pricebin35592 -> 37680 bytes
-rwxr-xr-xtests/unit/test_marketbin49360 -> 58064 bytes
12 files changed, 362 insertions, 226 deletions
diff --git a/components/tollgate_core/CMakeLists.txt b/components/tollgate_core/CMakeLists.txt
index 1b7d84a..3cd5e85 100644
--- a/components/tollgate_core/CMakeLists.txt
+++ b/components/tollgate_core/CMakeLists.txt
@@ -6,6 +6,8 @@ idf_component_register(
6 "src/tollgate_core_session.c" 6 "src/tollgate_core_session.c"
7 "src/tollgate_core_mining.c" 7 "src/tollgate_core_mining.c"
8 "src/tollgate_core_stratum_proxy.c" 8 "src/tollgate_core_stratum_proxy.c"
9 "src/tollgate_core_beacon.c"
10 "src/tollgate_core_market.c"
9 INCLUDE_DIRS "include" "src" 11 INCLUDE_DIRS "include" "src"
10 REQUIRES lwip json esp_http_client mbedtls log esp_netif 12 REQUIRES lwip json esp_http_client mbedtls log esp_netif
11 PRIV_REQUIRES esp_wifi) 13 PRIV_REQUIRES esp_wifi)
diff --git a/components/tollgate_core/src/tollgate_core_beacon.c b/components/tollgate_core/src/tollgate_core_beacon.c
new file mode 100644
index 0000000..927aa03
--- /dev/null
+++ b/components/tollgate_core/src/tollgate_core_beacon.c
@@ -0,0 +1,48 @@
1#include "tollgate_core_beacon.h"
2#include "mbedtls/sha256.h"
3#include <string.h>
4
5void tollgate_core_beacon_hash_mint(const char *mint_url, uint8_t hash_out[4])
6{
7 uint8_t full_hash[32];
8 mbedtls_sha256((const unsigned char *)mint_url, strlen(mint_url), full_hash, 0);
9 memcpy(hash_out, full_hash, 4);
10}
11
12void tollgate_core_beacon_hash_npub(const char *npub_hex, uint8_t hash_out[4])
13{
14 uint8_t full_hash[32];
15 mbedtls_sha256((const unsigned char *)npub_hex, strlen(npub_hex), full_hash, 0);
16 memcpy(hash_out, full_hash, 4);
17}
18
19void tollgate_core_beacon_build_ie(const tollgate_beacon_config_t *cfg, tollgate_price_ie_t *ie)
20{
21 memset(ie, 0, sizeof(*ie));
22 ie->element_id = TOLLGATE_IE_ELEMENT_ID;
23 ie->length = 4 + TOLLGATE_IE_PAYLOAD_SIZE;
24 ie->vendor_oui[0] = TOLLGATE_OUI_0;
25 ie->vendor_oui[1] = TOLLGATE_OUI_1;
26 ie->vendor_oui[2] = TOLLGATE_OUI_2;
27 ie->vendor_oui_type = TOLLGATE_IE_TYPE;
28
29 tollgate_price_payload_t *p = &ie->payload;
30 p->version = TOLLGATE_IE_VERSION;
31 p->metric = (strcmp(cfg->metric, "bytes") == 0) ? 1 : 0;
32 p->price_per_step = (uint16_t)cfg->price_per_step;
33
34 bool is_bytes = (strcmp(cfg->metric, "bytes") == 0);
35 p->step_size = is_bytes ? (uint32_t)cfg->step_size_bytes : (uint32_t)cfg->step_size_ms;
36
37 tollgate_core_beacon_hash_mint(cfg->mint_url, p->mint_hash);
38
39 p->geohash_len = (uint8_t)strnlen(cfg->geohash, TOLLGATE_IE_GEOHASH_MAX);
40 memcpy(p->geohash, cfg->geohash, p->geohash_len);
41 if (p->geohash_len < TOLLGATE_IE_GEOHASH_MAX) {
42 memset(p->geohash + p->geohash_len, 0, TOLLGATE_IE_GEOHASH_MAX - p->geohash_len);
43 }
44
45 if (cfg->identity_initialized && cfg->npub_hex) {
46 tollgate_core_beacon_hash_npub(cfg->npub_hex, p->npub_hash);
47 }
48}
diff --git a/components/tollgate_core/src/tollgate_core_beacon.h b/components/tollgate_core/src/tollgate_core_beacon.h
new file mode 100644
index 0000000..27ad3fe
--- /dev/null
+++ b/components/tollgate_core/src/tollgate_core_beacon.h
@@ -0,0 +1,53 @@
1#ifndef TOLLGATE_CORE_BEACON_H
2#define TOLLGATE_CORE_BEACON_H
3
4#include <stdint.h>
5#include <stdbool.h>
6
7#define TOLLGATE_OUI_0 0xC0
8#define TOLLGATE_OUI_1 0xFF
9#define TOLLGATE_OUI_2 0xEE
10#define TOLLGATE_IE_TYPE 0x01
11#define TOLLGATE_IE_VERSION 1
12#define TOLLGATE_IE_ELEMENT_ID 0xDD
13
14#define TOLLGATE_IE_GEOHASH_MAX 9
15
16typedef struct __attribute__((packed)) {
17 uint8_t version;
18 uint8_t metric;
19 uint16_t price_per_step;
20 uint32_t step_size;
21 uint8_t mint_hash[4];
22 uint8_t geohash_len;
23 char geohash[TOLLGATE_IE_GEOHASH_MAX];
24 uint8_t npub_hash[4];
25} tollgate_price_payload_t;
26
27#define TOLLGATE_IE_PAYLOAD_SIZE sizeof(tollgate_price_payload_t)
28#define TOLLGATE_IE_TOTAL_SIZE (6 + TOLLGATE_IE_PAYLOAD_SIZE)
29
30typedef struct __attribute__((packed)) {
31 uint8_t element_id;
32 uint8_t length;
33 uint8_t vendor_oui[3];
34 uint8_t vendor_oui_type;
35 tollgate_price_payload_t payload;
36} tollgate_price_ie_t;
37
38typedef struct {
39 const char *mint_url;
40 const char *metric;
41 int price_per_step;
42 int step_size_ms;
43 int step_size_bytes;
44 const char *geohash;
45 const char *npub_hex;
46 bool identity_initialized;
47} tollgate_beacon_config_t;
48
49void tollgate_core_beacon_hash_mint(const char *mint_url, uint8_t hash_out[4]);
50void tollgate_core_beacon_hash_npub(const char *npub_hex, uint8_t hash_out[4]);
51void tollgate_core_beacon_build_ie(const tollgate_beacon_config_t *cfg, tollgate_price_ie_t *ie);
52
53#endif
diff --git a/components/tollgate_core/src/tollgate_core_market.c b/components/tollgate_core/src/tollgate_core_market.c
new file mode 100644
index 0000000..8530212
--- /dev/null
+++ b/components/tollgate_core/src/tollgate_core_market.c
@@ -0,0 +1,121 @@
1#include "tollgate_core_market.h"
2#include <string.h>
3
4static tollgate_market_t s_market;
5static tollgate_market_get_time_ms_fn s_get_time_ms;
6
7static int find_entry_by_bssid(const uint8_t bssid[6])
8{
9 for (int i = 0; i < TG_MARKET_MAX_ENTRIES; i++) {
10 if (s_market.entries[i].valid && memcmp(s_market.entries[i].bssid, bssid, 6) == 0) {
11 return i;
12 }
13 }
14 return -1;
15}
16
17static int find_free_slot(void)
18{
19 for (int i = 0; i < TG_MARKET_MAX_ENTRIES; i++) {
20 if (!s_market.entries[i].valid) return i;
21 }
22 int oldest = 0;
23 int64_t oldest_time = s_market.entries[0].discovered_ms;
24 for (int i = 1; i < TG_MARKET_MAX_ENTRIES; i++) {
25 if (s_market.entries[i].discovered_ms < oldest_time) {
26 oldest_time = s_market.entries[i].discovered_ms;
27 oldest = i;
28 }
29 }
30 return oldest;
31}
32
33void tollgate_core_market_init(tollgate_market_get_time_ms_fn get_time_ms)
34{
35 memset(&s_market, 0, sizeof(s_market));
36 s_get_time_ms = get_time_ms;
37}
38
39void tollgate_core_market_parse_ie(const uint8_t sa[6], const tollgate_vendor_ie_t *ie, int rssi,
40 const uint8_t *self_npub_hash)
41{
42 if (!ie || ie->payload_len < TOLLGATE_IE_PAYLOAD_SIZE) return;
43
44 static const uint8_t oui[3] = { TOLLGATE_OUI_0, TOLLGATE_OUI_1, TOLLGATE_OUI_2 };
45 if (memcmp(ie->vendor_oui, oui, 3) != 0) return;
46 if (ie->vendor_oui_type != TOLLGATE_IE_TYPE) return;
47
48 const tollgate_price_payload_t *payload = (const tollgate_price_payload_t *)ie->payload;
49 if (payload->version != TOLLGATE_IE_VERSION) return;
50
51 if (self_npub_hash && memcmp(payload->npub_hash, self_npub_hash, 4) == 0) return;
52
53 int idx = find_entry_by_bssid(sa);
54 if (idx < 0) {
55 idx = find_free_slot();
56 if (s_market.count < TG_MARKET_MAX_ENTRIES) s_market.count++;
57 }
58
59 tollgate_market_entry_t *entry = &s_market.entries[idx];
60 memcpy(entry->bssid, sa, 6);
61 entry->rssi = (int8_t)rssi;
62 entry->price_per_step = payload->price_per_step;
63 entry->step_size = payload->step_size;
64 entry->metric = payload->metric;
65 memcpy(entry->mint_hash, payload->mint_hash, 4);
66 memcpy(entry->npub_hash, payload->npub_hash, 4);
67
68 uint8_t gh_len = payload->geohash_len;
69 if (gh_len > TOLLGATE_IE_GEOHASH_MAX) gh_len = TOLLGATE_IE_GEOHASH_MAX;
70 memcpy(entry->geohash, payload->geohash, gh_len);
71 entry->geohash[gh_len] = '\0';
72
73 entry->discovered_ms = s_get_time_ms ? s_get_time_ms() : 0;
74 entry->valid = true;
75 entry->ssid[0] = '\0';
76}
77
78int tollgate_core_market_find_cheapest(const tollgate_market_t *market)
79{
80 int cheapest = -1;
81 uint32_t best_eff_price = UINT32_MAX;
82
83 for (int i = 0; i < TG_MARKET_MAX_ENTRIES; i++) {
84 if (!market->entries[i].valid) continue;
85 if (market->entries[i].ssid[0] == '\0') continue;
86
87 uint32_t step = market->entries[i].step_size;
88 if (step == 0) continue;
89
90 uint32_t eff;
91 if (market->entries[i].metric == 0) {
92 eff = (uint32_t)market->entries[i].price_per_step * 60000 / step;
93 } else {
94 eff = (uint32_t)market->entries[i].price_per_step * 1048576 / step;
95 }
96
97 if (eff < best_eff_price) {
98 best_eff_price = eff;
99 cheapest = i;
100 }
101 }
102 return cheapest;
103}
104
105const tollgate_market_t *tollgate_core_market_get(void)
106{
107 return &s_market;
108}
109
110void tollgate_core_market_update_ssid(const uint8_t bssid[6], const char *ssid, int8_t rssi)
111{
112 for (int i = 0; i < TG_MARKET_MAX_ENTRIES; i++) {
113 if (!s_market.entries[i].valid) continue;
114 if (memcmp(s_market.entries[i].bssid, bssid, 6) == 0) {
115 memcpy(s_market.entries[i].ssid, ssid, 32);
116 s_market.entries[i].ssid[32] = '\0';
117 s_market.entries[i].rssi = rssi;
118 break;
119 }
120 }
121}
diff --git a/components/tollgate_core/src/tollgate_core_market.h b/components/tollgate_core/src/tollgate_core_market.h
new file mode 100644
index 0000000..a89a1db
--- /dev/null
+++ b/components/tollgate_core/src/tollgate_core_market.h
@@ -0,0 +1,49 @@
1#ifndef TOLLGATE_CORE_MARKET_H
2#define TOLLGATE_CORE_MARKET_H
3
4#include "tollgate_core_beacon.h"
5#include <stdint.h>
6#include <stddef.h>
7#include <stdbool.h>
8
9#define TG_MARKET_MAX_ENTRIES 10
10
11typedef struct {
12 uint8_t bssid[6];
13 char ssid[33];
14 int8_t rssi;
15 uint16_t price_per_step;
16 uint32_t step_size;
17 uint8_t metric;
18 uint8_t mint_hash[4];
19 uint8_t npub_hash[4];
20 char geohash[TOLLGATE_IE_GEOHASH_MAX + 1];
21 int64_t discovered_ms;
22 bool valid;
23} tollgate_market_entry_t;
24
25typedef struct {
26 tollgate_market_entry_t entries[TG_MARKET_MAX_ENTRIES];
27 int count;
28 int64_t last_scan_ms;
29 bool scanning;
30 int consecutive_failures;
31} tollgate_market_t;
32
33typedef struct {
34 const uint8_t *vendor_oui;
35 uint8_t vendor_oui_type;
36 const uint8_t *payload;
37 size_t payload_len;
38} tollgate_vendor_ie_t;
39
40typedef int64_t (*tollgate_market_get_time_ms_fn)(void);
41
42void tollgate_core_market_init(tollgate_market_get_time_ms_fn get_time_ms);
43void tollgate_core_market_parse_ie(const uint8_t sa[6], const tollgate_vendor_ie_t *ie, int rssi,
44 const uint8_t *self_npub_hash);
45int tollgate_core_market_find_cheapest(const tollgate_market_t *market);
46const tollgate_market_t *tollgate_core_market_get(void);
47void tollgate_core_market_update_ssid(const uint8_t bssid[6], const char *ssid, int8_t rssi);
48
49#endif
diff --git a/main/beacon_price.c b/main/beacon_price.c
index b87e289..2f92ef3 100644
--- a/main/beacon_price.c
+++ b/main/beacon_price.c
@@ -1,9 +1,9 @@
1#include "beacon_price.h" 1#include "beacon_price.h"
2#include "tollgate_core_beacon.h"
2#include "config.h" 3#include "config.h"
3#include "identity.h" 4#include "identity.h"
4#include "esp_log.h" 5#include "esp_log.h"
5#include "esp_wifi.h" 6#include "esp_wifi.h"
6#include "mbedtls/sha256.h"
7#include <string.h> 7#include <string.h>
8 8
9static const char *TAG = "beacon_price"; 9static const char *TAG = "beacon_price";
@@ -11,16 +11,12 @@ static bool s_active = false;
11 11
12void beacon_price_hash_mint(const char *mint_url, uint8_t hash_out[4]) 12void beacon_price_hash_mint(const char *mint_url, uint8_t hash_out[4])
13{ 13{
14 uint8_t full_hash[32]; 14 tollgate_core_beacon_hash_mint(mint_url, hash_out);
15 mbedtls_sha256((const unsigned char *)mint_url, strlen(mint_url), full_hash, 0);
16 memcpy(hash_out, full_hash, 4);
17} 15}
18 16
19void beacon_price_hash_npub(const char *npub_hex, uint8_t hash_out[4]) 17void beacon_price_hash_npub(const char *npub_hex, uint8_t hash_out[4])
20{ 18{
21 uint8_t full_hash[32]; 19 tollgate_core_beacon_hash_npub(npub_hex, hash_out);
22 mbedtls_sha256((const unsigned char *)npub_hex, strlen(npub_hex), full_hash, 0);
23 memcpy(hash_out, full_hash, 4);
24} 20}
25 21
26void beacon_price_build_ie(tollgate_price_ie_t *ie) 22void beacon_price_build_ie(tollgate_price_ie_t *ie)
@@ -28,38 +24,22 @@ void beacon_price_build_ie(tollgate_price_ie_t *ie)
28 const tollgate_config_t *cfg = tollgate_config_get(); 24 const tollgate_config_t *cfg = tollgate_config_get();
29 const tollgate_identity_t *id = identity_get(); 25 const tollgate_identity_t *id = identity_get();
30 26
31 memset(ie, 0, sizeof(*ie)); 27 tollgate_beacon_config_t bcfg = {
32 ie->element_id = WIFI_VENDOR_IE_ELEMENT_ID; 28 .mint_url = cfg->mint_url,
33 ie->length = 4 + TOLLGATE_IE_PAYLOAD_SIZE; 29 .metric = cfg->metric,
34 ie->vendor_oui[0] = TOLLGATE_OUI_0; 30 .price_per_step = cfg->price_per_step,
35 ie->vendor_oui[1] = TOLLGATE_OUI_1; 31 .step_size_ms = cfg->step_size_ms,
36 ie->vendor_oui[2] = TOLLGATE_OUI_2; 32 .step_size_bytes = cfg->step_size_bytes,
37 ie->vendor_oui_type = TOLLGATE_IE_TYPE; 33 .geohash = cfg->nostr_geohash,
38 34 .npub_hex = (id && id->initialized) ? id->npub_hex : NULL,
39 tollgate_price_payload_t *p = &ie->payload; 35 .identity_initialized = (id && id->initialized),
40 p->version = TOLLGATE_IE_VERSION; 36 };
41 p->metric = (strcmp(cfg->metric, "bytes") == 0) ? 1 : 0; 37 tollgate_core_beacon_build_ie(&bcfg, ie);
42 p->price_per_step = (uint16_t)cfg->price_per_step;
43
44 bool is_bytes = (strcmp(cfg->metric, "bytes") == 0);
45 p->step_size = is_bytes ? (uint32_t)cfg->step_size_bytes : (uint32_t)cfg->step_size_ms;
46
47 beacon_price_hash_mint(cfg->mint_url, p->mint_hash);
48
49 p->geohash_len = (uint8_t)strnlen(cfg->nostr_geohash, TOLLGATE_IE_GEOHASH_MAX);
50 memcpy(p->geohash, cfg->nostr_geohash, p->geohash_len);
51 if (p->geohash_len < TOLLGATE_IE_GEOHASH_MAX) {
52 memset(p->geohash + p->geohash_len, 0, TOLLGATE_IE_GEOHASH_MAX - p->geohash_len);
53 }
54
55 if (id && id->initialized) {
56 beacon_price_hash_npub(id->npub_hex, p->npub_hash);
57 }
58 38
59 ESP_LOGI(TAG, "Built IE: price=%lu sats, step=%lu, metric=%s, geohash=%.*s", 39 ESP_LOGI(TAG, "Built IE: price=%lu sats, step=%lu, metric=%s, geohash=%.*s",
60 (unsigned long)p->price_per_step, (unsigned long)p->step_size, 40 (unsigned long)ie->payload.price_per_step, (unsigned long)ie->payload.step_size,
61 p->metric ? "bytes" : "milliseconds", 41 ie->payload.metric ? "bytes" : "milliseconds",
62 p->geohash_len, p->geohash); 42 ie->payload.geohash_len, ie->payload.geohash);
63} 43}
64 44
65esp_err_t beacon_price_start(void) 45esp_err_t beacon_price_start(void)
diff --git a/main/beacon_price.h b/main/beacon_price.h
index cb2eb5b..a11697c 100644
--- a/main/beacon_price.h
+++ b/main/beacon_price.h
@@ -1,40 +1,11 @@
1#ifndef BEACON_PRICE_H 1#ifndef BEACON_PRICE_H
2#define BEACON_PRICE_H 2#define BEACON_PRICE_H
3 3
4#include "tollgate_core_beacon.h"
4#include "esp_err.h" 5#include "esp_err.h"
5#include <stdint.h> 6#include <stdint.h>
6#include <stdbool.h> 7#include <stdbool.h>
7 8
8#define TOLLGATE_OUI_0 0xC0
9#define TOLLGATE_OUI_1 0xFF
10#define TOLLGATE_OUI_2 0xEE
11#define TOLLGATE_IE_TYPE 0x01
12#define TOLLGATE_IE_VERSION 1
13
14#define TOLLGATE_IE_GEOHASH_MAX 9
15
16typedef struct __attribute__((packed)) {
17 uint8_t version;
18 uint8_t metric;
19 uint16_t price_per_step;
20 uint32_t step_size;
21 uint8_t mint_hash[4];
22 uint8_t geohash_len;
23 char geohash[TOLLGATE_IE_GEOHASH_MAX];
24 uint8_t npub_hash[4];
25} tollgate_price_payload_t;
26
27#define TOLLGATE_IE_PAYLOAD_SIZE sizeof(tollgate_price_payload_t)
28#define TOLLGATE_IE_TOTAL_SIZE (6 + TOLLGATE_IE_PAYLOAD_SIZE)
29
30typedef struct __attribute__((packed)) {
31 uint8_t element_id;
32 uint8_t length;
33 uint8_t vendor_oui[3];
34 uint8_t vendor_oui_type;
35 tollgate_price_payload_t payload;
36} tollgate_price_ie_t;
37
38esp_err_t beacon_price_start(void); 9esp_err_t beacon_price_start(void);
39esp_err_t beacon_price_stop(void); 10esp_err_t beacon_price_stop(void);
40void beacon_price_build_ie(tollgate_price_ie_t *ie); 11void beacon_price_build_ie(tollgate_price_ie_t *ie);
diff --git a/main/market.c b/main/market.c
index c8a0b6d..34dd36d 100644
--- a/main/market.c
+++ b/main/market.c
@@ -1,5 +1,6 @@
1#include "market.h" 1#include "market.h"
2#include "beacon_price.h" 2#include "tollgate_core_market.h"
3#include "tollgate_core_beacon.h"
3#include "config.h" 4#include "config.h"
4#include "identity.h" 5#include "identity.h"
5#include "esp_log.h" 6#include "esp_log.h"
@@ -9,109 +10,49 @@
9#include <string.h> 10#include <string.h>
10 11
11static const char *TAG = "market"; 12static const char *TAG = "market";
12static market_t s_market;
13static bool s_initialized = false; 13static bool s_initialized = false;
14static esp_event_handler_instance_t s_scan_done_handler = NULL;
14 15
15static int64_t get_time_ms(void) 16static int64_t get_time_ms(void)
16{ 17{
17 return (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; 18 return (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS;
18} 19}
19 20
20static bool oui_matches(const uint8_t oui[3]) 21static void vendor_ie_cb(void *ctx, wifi_vendor_ie_type_t type,
21{ 22 const uint8_t sa[6], const vendor_ie_data_t *vnd_ie, int rssi)
22 return oui[0] == TOLLGATE_OUI_0 && oui[1] == TOLLGATE_OUI_1 && oui[2] == TOLLGATE_OUI_2;
23}
24
25static int find_entry_by_bssid(const uint8_t bssid[6])
26{
27 for (int i = 0; i < MARKET_MAX_ENTRIES; i++) {
28 if (s_market.entries[i].valid && memcmp(s_market.entries[i].bssid, bssid, 6) == 0) {
29 return i;
30 }
31 }
32 return -1;
33}
34
35static int find_free_slot(void)
36{
37 for (int i = 0; i < MARKET_MAX_ENTRIES; i++) {
38 if (!s_market.entries[i].valid) return i;
39 }
40 int oldest = 0;
41 int64_t oldest_time = s_market.entries[0].discovered_ms;
42 for (int i = 1; i < MARKET_MAX_ENTRIES; i++) {
43 if (s_market.entries[i].discovered_ms < oldest_time) {
44 oldest_time = s_market.entries[i].discovered_ms;
45 oldest = i;
46 }
47 }
48 return oldest;
49}
50
51void market_parse_vendor_ie(const uint8_t sa[6], const vendor_ie_data_t *ie, int rssi)
52{ 23{
53 if (!ie || ie->length < 4 + TOLLGATE_IE_PAYLOAD_SIZE) return; 24 (void)ctx;
54 if (!oui_matches(ie->vendor_oui)) return; 25 (void)type;
55 if (ie->vendor_oui_type != TOLLGATE_IE_TYPE) return; 26 if (!vnd_ie) return;
56 27
57 const tollgate_price_payload_t *payload = (const tollgate_price_payload_t *)ie->payload; 28 tollgate_vendor_ie_t ie = {
58 if (payload->version != TOLLGATE_IE_VERSION) return; 29 .vendor_oui = vnd_ie->vendor_oui,
30 .vendor_oui_type = vnd_ie->vendor_oui_type,
31 .payload = vnd_ie->payload,
32 .payload_len = vnd_ie->length - 4,
33 };
59 34
60 const tollgate_identity_t *id = identity_get(); 35 const tollgate_identity_t *id = identity_get();
36 uint8_t self_hash[4] = {0};
37 const uint8_t *self_ptr = NULL;
61 if (id && id->initialized) { 38 if (id && id->initialized) {
62 uint8_t my_npub_hash[4]; 39 tollgate_core_beacon_hash_npub(id->npub_hex, self_hash);
63 beacon_price_hash_npub(id->npub_hex, my_npub_hash); 40 self_ptr = self_hash;
64 if (memcmp(payload->npub_hash, my_npub_hash, 4) == 0) return;
65 } 41 }
66 42
67 int idx = find_entry_by_bssid(sa); 43 tollgate_core_market_parse_ie(sa, &ie, rssi, self_ptr);
68 if (idx < 0) {
69 idx = find_free_slot();
70 if (s_market.count < MARKET_MAX_ENTRIES) s_market.count++;
71 }
72 44
73 market_entry_t *entry = &s_market.entries[idx]; 45 ESP_LOGI(TAG, "Discovered TollGate %02X:%02X:%02X:%02X:%02X:%02X RSSI=%d",
74 memcpy(entry->bssid, sa, 6); 46 sa[0], sa[1], sa[2], sa[3], sa[4], sa[5], rssi);
75 entry->rssi = (int8_t)rssi;
76 entry->price_per_step = payload->price_per_step;
77 entry->step_size = payload->step_size;
78 entry->metric = payload->metric;
79 memcpy(entry->mint_hash, payload->mint_hash, 4);
80 memcpy(entry->npub_hash, payload->npub_hash, 4);
81
82 uint8_t gh_len = payload->geohash_len;
83 if (gh_len > TOLLGATE_IE_GEOHASH_MAX) gh_len = TOLLGATE_IE_GEOHASH_MAX;
84 memcpy(entry->geohash, payload->geohash, gh_len);
85 entry->geohash[gh_len] = '\0';
86
87 entry->discovered_ms = get_time_ms();
88 entry->valid = true;
89 entry->ssid[0] = '\0';
90
91 ESP_LOGI(TAG, "Discovered TollGate %02X:%02X:%02X:%02X:%02X:%02X price=%lu sats step=%lu metric=%s RSSI=%d",
92 sa[0], sa[1], sa[2], sa[3], sa[4], sa[5],
93 (unsigned long)payload->price_per_step, (unsigned long)payload->step_size,
94 payload->metric ? "bytes" : "milliseconds", rssi);
95}
96
97static void vendor_ie_cb(void *ctx, wifi_vendor_ie_type_t type,
98 const uint8_t sa[6], const vendor_ie_data_t *vnd_ie, int rssi)
99{
100 (void)ctx;
101 (void)type;
102 if (!vnd_ie) return;
103 market_parse_vendor_ie(sa, vnd_ie, rssi);
104} 47}
105 48
106static void scan_done_cb(void *arg, esp_event_base_t event_base, 49static void scan_done_cb(void *arg, esp_event_base_t event_base,
107 int32_t event_id, void *event_data) 50 int32_t event_id, void *event_data)
108{ 51{
109 (void)arg; 52 (void)arg; (void)event_base; (void)event_id; (void)event_data;
110 (void)event_base;
111 (void)event_id;
112 (void)event_data;
113 53
114 s_market.scanning = false; 54 tollgate_market_t *m = (tollgate_market_t *)tollgate_core_market_get();
55 m->scanning = false;
115 56
116 uint16_t ap_count = 0; 57 uint16_t ap_count = 0;
117 esp_wifi_scan_get_ap_num(&ap_count); 58 esp_wifi_scan_get_ap_num(&ap_count);
@@ -124,27 +65,17 @@ static void scan_done_cb(void *arg, esp_event_base_t event_base,
124 esp_wifi_scan_get_ap_records(&max_aps, ap_records); 65 esp_wifi_scan_get_ap_records(&max_aps, ap_records);
125 66
126 for (int i = 0; i < max_aps; i++) { 67 for (int i = 0; i < max_aps; i++) {
127 for (int j = 0; j < MARKET_MAX_ENTRIES; j++) { 68 tollgate_core_market_update_ssid(ap_records[i].bssid, (const char *)ap_records[i].ssid, ap_records[i].rssi);
128 if (!s_market.entries[j].valid) continue;
129 if (memcmp(s_market.entries[j].bssid, ap_records[i].bssid, 6) == 0) {
130 memcpy(s_market.entries[j].ssid, ap_records[i].ssid, 32);
131 s_market.entries[j].ssid[32] = '\0';
132 s_market.entries[j].rssi = ap_records[i].rssi;
133 break;
134 }
135 }
136 } 69 }
137 free(ap_records); 70 free(ap_records);
138 s_market.last_scan_ms = get_time_ms(); 71 m->last_scan_ms = get_time_ms();
139 72
140 ESP_LOGI(TAG, "Scan complete: %d APs, %d TollGates found", max_aps, s_market.count); 73 ESP_LOGI(TAG, "Scan complete: %d APs, %d TollGates found", max_aps, m->count);
141} 74}
142 75
143static esp_event_handler_instance_t s_scan_done_handler = NULL;
144
145esp_err_t market_init(void) 76esp_err_t market_init(void)
146{ 77{
147 memset(&s_market, 0, sizeof(s_market)); 78 tollgate_core_market_init(get_time_ms);
148 79
149 esp_err_t ret = esp_wifi_set_vendor_ie_cb(vendor_ie_cb, NULL); 80 esp_err_t ret = esp_wifi_set_vendor_ie_cb(vendor_ie_cb, NULL);
150 if (ret != ESP_OK) { 81 if (ret != ESP_OK) {
@@ -172,10 +103,11 @@ void market_tick(void)
172 const tollgate_config_t *cfg = tollgate_config_get(); 103 const tollgate_config_t *cfg = tollgate_config_get();
173 if (!cfg->market_enabled) return; 104 if (!cfg->market_enabled) return;
174 105
175 if (s_market.scanning) return; 106 tollgate_market_t *m = (tollgate_market_t *)tollgate_core_market_get();
107 if (m->scanning) return;
176 108
177 int64_t now = get_time_ms(); 109 int64_t now = get_time_ms();
178 int64_t elapsed = now - s_market.last_scan_ms; 110 int64_t elapsed = now - m->last_scan_ms;
179 int64_t interval_ms = (int64_t)cfg->market_scan_interval_s * 1000; 111 int64_t interval_ms = (int64_t)cfg->market_scan_interval_s * 1000;
180 if (elapsed < interval_ms) return; 112 if (elapsed < interval_ms) return;
181 113
@@ -190,48 +122,46 @@ void market_tick(void)
190 122
191 esp_err_t ret = esp_wifi_scan_start(&scan_config, false); 123 esp_err_t ret = esp_wifi_scan_start(&scan_config, false);
192 if (ret == ESP_OK) { 124 if (ret == ESP_OK) {
193 s_market.scanning = true; 125 m->scanning = true;
194 s_market.last_scan_ms = now; 126 m->last_scan_ms = now;
195 s_market.consecutive_failures = 0; 127 m->consecutive_failures = 0;
196 ESP_LOGD(TAG, "Market scan started");
197 } else { 128 } else {
198 s_market.consecutive_failures++; 129 m->consecutive_failures++;
199 s_market.last_scan_ms = now; 130 m->last_scan_ms = now;
200 if (s_market.consecutive_failures <= 3 || s_market.consecutive_failures % 30 == 0) { 131 if (m->consecutive_failures <= 3 || m->consecutive_failures % 30 == 0) {
201 ESP_LOGW(TAG, "Scan start failed: %s (failures: %d)", esp_err_to_name(ret), s_market.consecutive_failures); 132 ESP_LOGW(TAG, "Scan start failed: %s (failures: %d)", esp_err_to_name(ret), m->consecutive_failures);
202 } 133 }
203 } 134 }
204} 135}
205 136
206const market_t *market_get(void) 137const market_t *market_get(void)
207{ 138{
208 return &s_market; 139 return (const market_t *)tollgate_core_market_get();
209} 140}
210 141
211int market_find_cheapest(void) 142int market_find_cheapest(void)
212{ 143{
213 int cheapest = -1; 144 return tollgate_core_market_find_cheapest(tollgate_core_market_get());
214 uint32_t best_eff_price = UINT32_MAX; 145}
215
216 for (int i = 0; i < MARKET_MAX_ENTRIES; i++) {
217 if (!s_market.entries[i].valid) continue;
218 if (s_market.entries[i].ssid[0] == '\0') continue;
219
220 uint32_t step = s_market.entries[i].step_size;
221 if (step == 0) continue;
222
223 uint32_t eff;
224 if (s_market.entries[i].metric == 0) {
225 eff = (uint32_t)s_market.entries[i].price_per_step * 60000 / step;
226 } else {
227 uint32_t eff_mb = (uint32_t)s_market.entries[i].price_per_step * 1048576 / step;
228 eff = eff_mb;
229 }
230 146
231 if (eff < best_eff_price) { 147void market_parse_vendor_ie(const uint8_t sa[6], const vendor_ie_data_t *ie, int rssi)
232 best_eff_price = eff; 148{
233 cheapest = i; 149 if (!ie || ie->length < 4 + (int)TOLLGATE_IE_PAYLOAD_SIZE) return;
234 } 150
151 tollgate_vendor_ie_t vnd = {
152 .vendor_oui = ie->vendor_oui,
153 .vendor_oui_type = ie->vendor_oui_type,
154 .payload = ie->payload,
155 .payload_len = ie->length - 4,
156 };
157
158 const tollgate_identity_t *id = identity_get();
159 uint8_t self_hash[4] = {0};
160 const uint8_t *self_ptr = NULL;
161 if (id && id->initialized) {
162 tollgate_core_beacon_hash_npub(id->npub_hex, self_hash);
163 self_ptr = self_hash;
235 } 164 }
236 return cheapest; 165
166 tollgate_core_market_parse_ie(sa, &vnd, rssi, self_ptr);
237} 167}
diff --git a/main/market.h b/main/market.h
index 6dbf43c..be6ef0a 100644
--- a/main/market.h
+++ b/main/market.h
@@ -1,35 +1,17 @@
1#ifndef MARKET_H 1#ifndef MARKET_H
2#define MARKET_H 2#define MARKET_H
3 3
4#include "beacon_price.h" 4#include "tollgate_core_beacon.h"
5#include "tollgate_core_market.h"
5#include "esp_wifi.h" 6#include "esp_wifi.h"
6#include "esp_err.h" 7#include "esp_err.h"
7#include <stdint.h> 8#include <stdint.h>
8#include <stdbool.h> 9#include <stdbool.h>
9 10
10#define MARKET_MAX_ENTRIES 10 11#define MARKET_MAX_ENTRIES TG_MARKET_MAX_ENTRIES
11 12
12typedef struct { 13typedef tollgate_market_entry_t market_entry_t;
13 uint8_t bssid[6]; 14typedef tollgate_market_t market_t;
14 char ssid[33];
15 int8_t rssi;
16 uint16_t price_per_step;
17 uint32_t step_size;
18 uint8_t metric;
19 uint8_t mint_hash[4];
20 uint8_t npub_hash[4];
21 char geohash[TOLLGATE_IE_GEOHASH_MAX + 1];
22 int64_t discovered_ms;
23 bool valid;
24} market_entry_t;
25
26typedef struct {
27 market_entry_t entries[MARKET_MAX_ENTRIES];
28 int count;
29 int64_t last_scan_ms;
30 bool scanning;
31 int consecutive_failures;
32} market_t;
33 15
34esp_err_t market_init(void); 16esp_err_t market_init(void);
35void market_tick(void); 17void market_tick(void);
diff --git a/tests/unit/Makefile b/tests/unit/Makefile
index 3890243..9347516 100644
--- a/tests/unit/Makefile
+++ b/tests/unit/Makefile
@@ -69,8 +69,8 @@ TG_CORE_OBJS_CFLAGS := -include stubs/mint_health.h -include stubs/esp_netif.h -
69test_session: test_session.c $(TG_CORE_SRC)/tollgate_core_cashu.c $(TG_CORE_SRC)/tollgate_core_session.c $(TG_CORE_SRC)/tollgate_core_firewall.c $(TG_CORE_SRC)/tollgate_core.c $(TG_CORE_SRC)/tollgate_core_mining.c $(TG_CORE_SRC)/tollgate_core_dns.c $(TG_CORE_SRC)/tollgate_core_stratum_proxy.c 69test_session: test_session.c $(TG_CORE_SRC)/tollgate_core_cashu.c $(TG_CORE_SRC)/tollgate_core_session.c $(TG_CORE_SRC)/tollgate_core_firewall.c $(TG_CORE_SRC)/tollgate_core.c $(TG_CORE_SRC)/tollgate_core_mining.c $(TG_CORE_SRC)/tollgate_core_dns.c $(TG_CORE_SRC)/tollgate_core_stratum_proxy.c
70 $(CC) $(CFLAGS) $(TG_CORE_OBJS_CFLAGS) $^ -o $@ $(LDFLAGS) 70 $(CC) $(CFLAGS) $(TG_CORE_OBJS_CFLAGS) $^ -o $@ $(LDFLAGS)
71 71
72test_tollgate_client: test_tollgate_client.c $(REPO_ROOT)/main/market.c $(REPO_ROOT)/main/beacon_price.c 72test_tollgate_client: test_tollgate_client.c $(REPO_ROOT)/main/market.c $(REPO_ROOT)/main/beacon_price.c $(TG_CORE_SRC)/tollgate_core_beacon.c $(TG_CORE_SRC)/tollgate_core_market.c
73 $(CC) $(CFLAGS) -I $(REPO_ROOT)/main $< $(REPO_ROOT)/main/market.c $(REPO_ROOT)/main/beacon_price.c -o $@ $(LDFLAGS) 73 $(CC) $(CFLAGS) -I $(REPO_ROOT)/main $< $(REPO_ROOT)/main/market.c $(REPO_ROOT)/main/beacon_price.c $(TG_CORE_SRC)/tollgate_core_beacon.c $(TG_CORE_SRC)/tollgate_core_market.c -o $@ $(LDFLAGS)
74 74
75test_lnurl_pay: test_lnurl_pay.c 75test_lnurl_pay: test_lnurl_pay.c
76 $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) 76 $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
@@ -90,11 +90,11 @@ test_cvm_server: test_cvm_server.c
90test_mint_health: test_mint_health.c $(REPO_ROOT)/main/mint_health.c 90test_mint_health: test_mint_health.c $(REPO_ROOT)/main/mint_health.c
91 $(CC) -Wall -Wextra -Wno-unused-parameter -Wno-unused-function -Wno-sign-compare -std=gnu17 -g -O0 -DTEST_HOST -include stubs/esp_err.h -I $(REPO_ROOT)/main -I stubs -I $(SECP256K1_INC) -I $(SECP256K1_CFG) -I /usr/include/cjson $< $(REPO_ROOT)/main/mint_health.c -o $@ $(LDFLAGS) 91 $(CC) -Wall -Wextra -Wno-unused-parameter -Wno-unused-function -Wno-sign-compare -std=gnu17 -g -O0 -DTEST_HOST -include stubs/esp_err.h -I $(REPO_ROOT)/main -I stubs -I $(SECP256K1_INC) -I $(SECP256K1_CFG) -I /usr/include/cjson $< $(REPO_ROOT)/main/mint_health.c -o $@ $(LDFLAGS)
92 92
93test_beacon_price: test_beacon_price.c $(REPO_ROOT)/main/beacon_price.c 93test_beacon_price: test_beacon_price.c $(REPO_ROOT)/main/beacon_price.c $(TG_CORE_SRC)/tollgate_core_beacon.c
94 $(CC) $(CFLAGS) -I $(REPO_ROOT)/main $< $(REPO_ROOT)/main/beacon_price.c -o $@ $(LDFLAGS) 94 $(CC) $(CFLAGS) -I $(REPO_ROOT)/main $< $(REPO_ROOT)/main/beacon_price.c $(TG_CORE_SRC)/tollgate_core_beacon.c -o $@ $(LDFLAGS)
95 95
96test_market: test_market.c $(REPO_ROOT)/main/market.c $(REPO_ROOT)/main/beacon_price.c 96test_market: test_market.c $(REPO_ROOT)/main/market.c $(REPO_ROOT)/main/beacon_price.c $(TG_CORE_SRC)/tollgate_core_beacon.c $(TG_CORE_SRC)/tollgate_core_market.c
97 $(CC) $(CFLAGS) -I $(REPO_ROOT)/main $< $(REPO_ROOT)/main/market.c $(REPO_ROOT)/main/beacon_price.c -o $@ $(LDFLAGS) 97 $(CC) $(CFLAGS) -I $(REPO_ROOT)/main $< $(REPO_ROOT)/main/market.c $(REPO_ROOT)/main/beacon_price.c $(TG_CORE_SRC)/tollgate_core_beacon.c $(TG_CORE_SRC)/tollgate_core_market.c -o $@ $(LDFLAGS)
98 98
99test_mining_payment: test_mining_payment.c $(TG_CORE_SRC)/tollgate_core_mining.c $(TG_CORE_SRC)/tollgate_core.c $(TG_CORE_SRC)/tollgate_core_session.c $(TG_CORE_SRC)/tollgate_core_firewall.c $(TG_CORE_SRC)/tollgate_core_cashu.c $(TG_CORE_SRC)/tollgate_core_dns.c $(TG_CORE_SRC)/tollgate_core_stratum_proxy.c 99test_mining_payment: test_mining_payment.c $(TG_CORE_SRC)/tollgate_core_mining.c $(TG_CORE_SRC)/tollgate_core.c $(TG_CORE_SRC)/tollgate_core_session.c $(TG_CORE_SRC)/tollgate_core_firewall.c $(TG_CORE_SRC)/tollgate_core_cashu.c $(TG_CORE_SRC)/tollgate_core_dns.c $(TG_CORE_SRC)/tollgate_core_stratum_proxy.c
100 $(CC) $(CFLAGS) $(TG_CORE_OBJS_CFLAGS) $^ -o $@ $(LDFLAGS) 100 $(CC) $(CFLAGS) $(TG_CORE_OBJS_CFLAGS) $^ -o $@ $(LDFLAGS)
diff --git a/tests/unit/test_beacon_price b/tests/unit/test_beacon_price
index 5e4bf90..ff76ae6 100755
--- a/tests/unit/test_beacon_price
+++ b/tests/unit/test_beacon_price
Binary files differ
diff --git a/tests/unit/test_market b/tests/unit/test_market
index d68ab15..49ccad4 100755
--- a/tests/unit/test_market
+++ b/tests/unit/test_market
Binary files differ