upleb.uk

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

summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
authorYour Name <you@example.com>2026-05-18 18:50:20 +0530
committerYour Name <you@example.com>2026-05-18 18:50:20 +0530
commitf6369f5b87f5dab17a03315d82454fdcdd668ec9 (patch)
treebc16c22f87253eafa680a30da370b1822d855ca4 /main
parentd8c55d2a072433e16ae5c84b7b7c676587f6f2d9 (diff)
feat: extract tollgate_core ESP-IDF component
Extract shared TollGate business logic into a reusable ESP-IDF component that can be consumed by esp-miner via the IDF Component Manager. Component structure: components/tollgate_core/ include/tollgate_core.h — public API include/tollgate_platform.h — platform interface (config callbacks) src/tollgate_core.c — orchestrator (init, payment, tick, owner) src/tollgate_core_cashu.c/h — Cashu V3 token decode/verify src/tollgate_core_dns.c/h — per-client DNS hijack/forward src/tollgate_core_firewall.c/h — per-client NAT filter src/tollgate_core_session.c/h — session lifecycle Key design decisions: - Platform interface pattern: consumers implement tollgate_platform_t (config getters + optional spend_proofs wallet hook) - Cross-module wiring (session→firewall→dns) stays internal - No direct config.h dependency — all config via platform callbacks - spend_proofs can be NULL (accepts payment without local wallet) Standalone app updated: - main/tollgate_platform.c implements platform via config singleton - main/tollgate_main.c calls tollgate_core_init/tick/dns_start - main/tollgate_api.c routes payment through tollgate_core_process_payment - Removed cashu.c, dns_server.c, firewall.c, session.c from CMakeLists Not yet build-verified — blocked on multi-mint, price-discovery, cvm-integration, and display-fix branches merging to master.
Diffstat (limited to 'main')
-rw-r--r--main/CMakeLists.txt6
-rw-r--r--main/lwip_tollgate_hooks.h4
-rw-r--r--main/tollgate_api.c156
-rw-r--r--main/tollgate_main.c30
-rw-r--r--main/tollgate_platform.c64
5 files changed, 91 insertions, 169 deletions
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
index 9b0fb1c..5f195c3 100644
--- a/main/CMakeLists.txt
+++ b/main/CMakeLists.txt
@@ -1,10 +1,7 @@
1idf_component_register(SRCS "tollgate_main.c" 1idf_component_register(SRCS "tollgate_main.c"
2 "config.c" 2 "config.c"
3 "dns_server.c" 3 "tollgate_platform.c"
4 "captive_portal.c" 4 "captive_portal.c"
5 "firewall.c"
6 "cashu.c"
7 "session.c"
8 "tollgate_api.c" 5 "tollgate_api.c"
9 "identity.c" 6 "identity.c"
10 "nostr_event.c" 7 "nostr_event.c"
@@ -22,4 +19,5 @@ idf_component_register(SRCS "tollgate_main.c"
22 REQUIRES esp_wifi esp_event esp_netif nvs_flash esp_http_server 19 REQUIRES esp_wifi esp_event esp_netif nvs_flash esp_http_server
23 lwip json esp_http_client mbedtls esp-tls log spiffs 20 lwip json esp_http_client mbedtls esp-tls log spiffs
24 nucula_lib secp256k1 axs15231b qrcode 21 nucula_lib secp256k1 axs15231b qrcode
22 tollgate_core
25 PRIV_REQUIRES esp-tls) 23 PRIV_REQUIRES esp-tls)
diff --git a/main/lwip_tollgate_hooks.h b/main/lwip_tollgate_hooks.h
index 76017be..2953c48 100644
--- a/main/lwip_tollgate_hooks.h
+++ b/main/lwip_tollgate_hooks.h
@@ -3,8 +3,8 @@
3 3
4#include "lwip/pbuf.h" 4#include "lwip/pbuf.h"
5 5
6int tollgate_ip4_canforward_filter(struct pbuf *p, u32_t dest_addr_hostorder); 6int tollgate_core_ip4_canforward_filter(struct pbuf *p, u32_t dest_addr_hostorder);
7 7
8#define LWIP_HOOK_IP4_CANFORWARD(p, addr) tollgate_ip4_canforward_filter(p, addr) 8#define LWIP_HOOK_IP4_CANFORWARD(p, addr) tollgate_core_ip4_canforward_filter(p, addr)
9 9
10#endif 10#endif
diff --git a/main/tollgate_api.c b/main/tollgate_api.c
index 650b0f3..62f75ee 100644
--- a/main/tollgate_api.c
+++ b/main/tollgate_api.c
@@ -1,8 +1,6 @@
1#include "tollgate_api.h" 1#include "tollgate_api.h"
2#include "cashu.h" 2#include "tollgate_core.h"
3#include "config.h" 3#include "config.h"
4#include "session.h"
5#include "firewall.h"
6#include "nucula_wallet.h" 4#include "nucula_wallet.h"
7#include "esp_log.h" 5#include "esp_log.h"
8#include "cJSON.h" 6#include "cJSON.h"
@@ -184,37 +182,11 @@ static esp_err_t api_post_payment(httpd_req_t *req)
184 182
185 ESP_LOGI(TAG, "Payment received: %d bytes", total); 183 ESP_LOGI(TAG, "Payment received: %d bytes", total);
186 184
187 cashu_token_t *token = malloc(sizeof(cashu_token_t)); 185 esp_err_t err = tollgate_core_process_payment(client_ip, body);
188 if (!token) {
189 cJSON *notice = create_notice("error", "session-error", "Out of memory");
190 char *json = cJSON_PrintUnformatted(notice);
191 httpd_resp_set_status(req, "503 Service Unavailable");
192 httpd_resp_set_type(req, "application/json");
193 httpd_resp_send(req, json, strlen(json));
194 cJSON_free(json);
195 cJSON_Delete(notice);
196 return ESP_OK;
197 }
198 esp_err_t err = cashu_decode_token(body, token);
199 char *body_copy = strdup(body);
200 free(body); 186 free(body);
201 187
202 if (err != ESP_OK) { 188 if (err != ESP_OK) {
203 free(token); 189 cJSON *notice = create_notice("error", "payment-error", "Payment processing failed");
204 cJSON *notice = create_notice("error", "payment-error-invalid", "Failed to decode Cashu token");
205 char *json = cJSON_PrintUnformatted(notice);
206 httpd_resp_set_status(req, "400 Bad Request");
207 httpd_resp_set_type(req, "application/json");
208 httpd_resp_send(req, json, strlen(json));
209 cJSON_free(json);
210 cJSON_Delete(notice);
211 return ESP_OK;
212 }
213
214 const char *mint_url = token->mint_url[0] ? token->mint_url : tollgate_config_get()->mint_url;
215 if (!cashu_is_mint_accepted(mint_url)) {
216 free(token);
217 cJSON *notice = create_notice("error", "payment-error-mint-not-accepted", "Mint not accepted");
218 char *json = cJSON_PrintUnformatted(notice); 190 char *json = cJSON_PrintUnformatted(notice);
219 httpd_resp_set_status(req, "402 Payment Required"); 191 httpd_resp_set_status(req, "402 Payment Required");
220 httpd_resp_set_type(req, "application/json"); 192 httpd_resp_set_type(req, "application/json");
@@ -224,97 +196,14 @@ static esp_err_t api_post_payment(httpd_req_t *req)
224 return ESP_OK; 196 return ESP_OK;
225 } 197 }
226 198
227 cashu_proof_state_t *states = malloc(CASHU_MAX_PROOFS * sizeof(cashu_proof_state_t));
228 if (!states) {
229 free(token);
230 cJSON *notice = create_notice("error", "session-error", "Out of memory");
231 char *json = cJSON_PrintUnformatted(notice);
232 httpd_resp_set_status(req, "503 Service Unavailable");
233 httpd_resp_set_type(req, "application/json");
234 httpd_resp_send(req, json, strlen(json));
235 cJSON_free(json);
236 cJSON_Delete(notice);
237 return ESP_OK;
238 }
239 int state_count = 0;
240 err = cashu_check_proof_states(mint_url, token, states, &state_count);
241 ESP_LOGI(TAG, "Stack HWM after checkstate: %u", uxTaskGetStackHighWaterMark(NULL));
242 if (err != ESP_OK) {
243 free(states);
244 free(token);
245 cJSON *notice = create_notice("error", "payment-error-verification", "Failed to verify token with mint");
246 char *json = cJSON_PrintUnformatted(notice);
247 httpd_resp_set_status(req, "502 Bad Gateway");
248 httpd_resp_set_type(req, "application/json");
249 httpd_resp_send(req, json, strlen(json));
250 cJSON_free(json);
251 cJSON_Delete(notice);
252 return ESP_OK;
253 }
254
255 for (int i = 0; i < state_count; i++) {
256 if (states[i].spent) {
257 free(states);
258 free(token);
259 cJSON *notice = create_notice("error", "payment-error-token-spent", "Token already spent");
260 char *json = cJSON_PrintUnformatted(notice);
261 httpd_resp_set_status(req, "402 Payment Required");
262 httpd_resp_set_type(req, "application/json");
263 httpd_resp_send(req, json, strlen(json));
264 cJSON_free(json);
265 cJSON_Delete(notice);
266 return ESP_OK;
267 }
268 }
269
270 const tollgate_config_t *cfg = tollgate_config_get(); 199 const tollgate_config_t *cfg = tollgate_config_get();
271 bool is_bytes = (strcmp(cfg->metric, "bytes") == 0); 200 uint64_t allotment = 0;
272 uint64_t step_size = is_bytes ? (uint64_t)cfg->step_size_bytes : (uint64_t)cfg->step_size_ms;
273 uint64_t allotment = cashu_calculate_allotment(token->total_amount, cfg->price_per_step,
274 cfg->metric, step_size);
275 if (allotment == 0) {
276 free(states);
277 free(token);
278 cJSON *notice = create_notice("error", "payment-error-insufficient", "Token value too low");
279 char *json = cJSON_PrintUnformatted(notice);
280 httpd_resp_set_status(req, "402 Payment Required");
281 httpd_resp_set_type(req, "application/json");
282 httpd_resp_send(req, json, strlen(json));
283 cJSON_free(json);
284 cJSON_Delete(notice);
285 return ESP_OK;
286 }
287
288 session_t *session;
289 if (is_bytes) {
290 session = session_create_bytes(client_ip, allotment);
291 } else {
292 session = session_create(client_ip, allotment);
293 }
294 if (!session) {
295 free(states);
296 free(token);
297 cJSON *notice = create_notice("error", "session-error", "Failed to create session");
298 char *json = cJSON_PrintUnformatted(notice);
299 httpd_resp_set_status(req, "503 Service Unavailable");
300 httpd_resp_set_type(req, "application/json");
301 httpd_resp_send(req, json, strlen(json));
302 cJSON_free(json);
303 cJSON_Delete(notice);
304 return ESP_OK;
305 }
306
307 cJSON *session_event = create_session_event(client_ip, allotment); 201 cJSON *session_event = create_session_event(client_ip, allotment);
308 char *json = cJSON_PrintUnformatted(session_event); 202 char *json = cJSON_PrintUnformatted(session_event);
309 httpd_resp_set_type(req, "application/json"); 203 httpd_resp_set_type(req, "application/json");
310 httpd_resp_send(req, json, strlen(json)); 204 httpd_resp_send(req, json, strlen(json));
311 cJSON_free(json); 205 cJSON_free(json);
312 cJSON_Delete(session_event); 206 cJSON_Delete(session_event);
313
314 nucula_wallet_receive(body_copy);
315
316 free(states);
317 free(token);
318 return ESP_OK; 207 return ESP_OK;
319} 208}
320 209
@@ -323,29 +212,15 @@ static esp_err_t api_get_usage(httpd_req_t *req)
323 uint32_t client_ip = 0; 212 uint32_t client_ip = 0;
324 get_client_ip(req, &client_ip); 213 get_client_ip(req, &client_ip);
325 214
326 session_t *session = session_find_by_ip(client_ip); 215 char *status_json = tollgate_core_get_status_json();
327 if (!session || !session->active) { 216 if (status_json) {
328 httpd_resp_set_type(req, "text/plain"); 217 httpd_resp_set_type(req, "application/json");
329 httpd_resp_send(req, "-1/-1", 5); 218 httpd_resp_send(req, status_json, strlen(status_json));
330 return ESP_OK; 219 cJSON_free(status_json);
331 }
332
333 const tollgate_config_t *cfg = tollgate_config_get();
334 bool is_bytes = (strcmp(cfg->metric, "bytes") == 0);
335
336 char resp[64];
337 if (is_bytes) {
338 int64_t remaining = (int64_t)session->allotment_bytes - (int64_t)session->bytes_consumed;
339 if (remaining < 0) remaining = 0;
340 snprintf(resp, sizeof(resp), "%lld/%llu", (long long)remaining, (unsigned long long)session->allotment_bytes);
341 } else { 220 } else {
342 int64_t elapsed = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS - session->start_time_ms; 221 httpd_resp_set_type(req, "text/plain");
343 int64_t remaining = session->allotment_ms - elapsed; 222 httpd_resp_send(req, "{}", 2);
344 if (remaining < 0) remaining = 0;
345 snprintf(resp, sizeof(resp), "%lld/%llu", (long long)remaining, (unsigned long long)session->allotment_ms);
346 } 223 }
347 httpd_resp_set_type(req, "text/plain");
348 httpd_resp_send(req, resp, strlen(resp));
349 return ESP_OK; 224 return ESP_OK;
350} 225}
351 226
@@ -354,15 +229,10 @@ static esp_err_t api_get_whoami(httpd_req_t *req)
354 uint32_t client_ip = 0; 229 uint32_t client_ip = 0;
355 char resp[96]; 230 char resp[96];
356 if (get_client_ip(req, &client_ip) == ESP_OK) { 231 if (get_client_ip(req, &client_ip) == ESP_OK) {
357 char mac[18] = {0};
358 esp_ip4_addr_t ip = { .addr = client_ip }; 232 esp_ip4_addr_t ip = { .addr = client_ip };
359 if (firewall_get_mac_for_ip(client_ip, mac, sizeof(mac)) == ESP_OK) { 233 snprintf(resp, sizeof(resp), "ip=" IPSTR, IP2STR(&ip));
360 snprintf(resp, sizeof(resp), "ip=" IPSTR " mac=%s", IP2STR(&ip), mac);
361 } else {
362 snprintf(resp, sizeof(resp), "ip=" IPSTR " mac=unknown", IP2STR(&ip));
363 }
364 } else { 234 } else {
365 snprintf(resp, sizeof(resp), "ip=unknown mac=unknown"); 235 snprintf(resp, sizeof(resp), "ip=unknown");
366 } 236 }
367 httpd_resp_set_type(req, "text/plain"); 237 httpd_resp_set_type(req, "text/plain");
368 httpd_resp_send(req, resp, strlen(resp)); 238 httpd_resp_send(req, resp, strlen(resp));
diff --git a/main/tollgate_main.c b/main/tollgate_main.c
index c0ff65f..697dae3 100644
--- a/main/tollgate_main.c
+++ b/main/tollgate_main.c
@@ -13,10 +13,8 @@
13#include "dhcpserver/dhcpserver.h" 13#include "dhcpserver/dhcpserver.h"
14#include "config.h" 14#include "config.h"
15#include "identity.h" 15#include "identity.h"
16#include "dns_server.h" 16#include "tollgate_core.h"
17#include "captive_portal.h" 17#include "captive_portal.h"
18#include "firewall.h"
19#include "session.h"
20#include "tollgate_api.h" 18#include "tollgate_api.h"
21#include "nucula_wallet.h" 19#include "nucula_wallet.h"
22#include "wifistr.h" 20#include "wifistr.h"
@@ -73,11 +71,13 @@ static void wifi_event_handler(void *arg, esp_event_base_t event_base,
73 ESP_LOGI(TAG, "Station connected: MAC=%02x:%02x:%02x:%02x:%02x:%02x", 71 ESP_LOGI(TAG, "Station connected: MAC=%02x:%02x:%02x:%02x:%02x:%02x",
74 event->mac[0], event->mac[1], event->mac[2], 72 event->mac[0], event->mac[1], event->mac[2],
75 event->mac[3], event->mac[4], event->mac[5]); 73 event->mac[3], event->mac[4], event->mac[5]);
74 tollgate_core_client_connected(event->mac, 0);
76 } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED) { 75 } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED) {
77 wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *)event_data; 76 wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *)event_data;
78 ESP_LOGI(TAG, "Station disconnected: MAC=%02x:%02x:%02x:%02x:%02x:%02x", 77 ESP_LOGI(TAG, "Station disconnected: MAC=%02x:%02x:%02x:%02x:%02x:%02x",
79 event->mac[0], event->mac[1], event->mac[2], 78 event->mac[0], event->mac[1], event->mac[2],
80 event->mac[3], event->mac[4], event->mac[5]); 79 event->mac[3], event->mac[4], event->mac[5]);
80 tollgate_core_client_disconnected(event->mac);
81 } 81 }
82} 82}
83 83
@@ -115,13 +115,6 @@ static void ip_event_handler(void *arg, esp_event_base_t event_base,
115 } 115 }
116} 116}
117 117
118static void wallet_init_task(void *pvParameters)
119{
120 const tollgate_config_t *cfg = tollgate_config_get();
121 nucula_wallet_init(cfg->mint_url);
122 vTaskDelete(NULL);
123}
124
125static void publish_wifistr_task(void *pvParameters) 118static void publish_wifistr_task(void *pvParameters)
126{ 119{
127 vTaskDelay(pdMS_TO_TICKS(5000)); 120 vTaskDelay(pdMS_TO_TICKS(5000));
@@ -139,7 +132,6 @@ static void start_services(void)
139 return; 132 return;
140 } 133 }
141 134
142 esp_netif_get_ip_info(s_ap_netif, &(esp_netif_ip_info_t){0});
143 esp_netif_ip_info_t ap_ip_info; 135 esp_netif_ip_info_t ap_ip_info;
144 esp_netif_get_ip_info(s_ap_netif, &ap_ip_info); 136 esp_netif_get_ip_info(s_ap_netif, &ap_ip_info);
145 137
@@ -147,21 +139,20 @@ static void start_services(void)
147 const ip_addr_t *dns_addr = dns_getserver(0); 139 const ip_addr_t *dns_addr = dns_getserver(0);
148 upstream_dns.addr = dns_addr->addr; 140 upstream_dns.addr = dns_addr->addr;
149 141
150 firewall_init(ap_ip_info.ip);
151 session_manager_init();
152
153 const tollgate_config_t *cfg = tollgate_config_get(); 142 const tollgate_config_t *cfg = tollgate_config_get();
143 const tollgate_platform_t *platform = tollgate_get_platform();
144 tollgate_core_init(platform, ap_ip_info.ip);
145
154 nucula_wallet_init(cfg->mint_url); 146 nucula_wallet_init(cfg->mint_url);
155 lightning_payout_init(&cfg->payout); 147 lightning_payout_init(&cfg->payout);
156 148
157 dns_server_start(ap_ip_info.ip, upstream_dns); 149 tollgate_core_dns_start(upstream_dns);
158 captive_portal_start(cfg->ap_ip_str); 150 captive_portal_start(cfg->ap_ip_str);
159 tollgate_api_start(); 151 tollgate_api_start();
160 152
161 xTaskCreate(publish_wifistr_task, "wifistr_init", 16384, NULL, 3, NULL); 153 xTaskCreate(publish_wifistr_task, "wifistr_init", 16384, NULL, 3, NULL);
162 154
163 const tollgate_config_t *cfg2 = tollgate_config_get(); 155 if (cfg->cvm_enabled) {
164 if (cfg2->cvm_enabled) {
165 cvm_server_init(); 156 cvm_server_init();
166 cvm_server_start(); 157 cvm_server_start();
167 } 158 }
@@ -186,9 +177,8 @@ static void stop_services(void)
186 177
187 captive_portal_stop(); 178 captive_portal_stop();
188 tollgate_api_stop(); 179 tollgate_api_stop();
189 dns_server_stop(); 180 tollgate_core_dns_stop();
190 cvm_server_stop(); 181 cvm_server_stop();
191 firewall_revoke_all();
192 s_services_running = false; 182 s_services_running = false;
193 if (s_services_mutex) xSemaphoreGive(s_services_mutex); 183 if (s_services_mutex) xSemaphoreGive(s_services_mutex);
194 ESP_LOGI(TAG, "=== TollGate services stopped ==="); 184 ESP_LOGI(TAG, "=== TollGate services stopped ===");
@@ -316,7 +306,7 @@ void app_main(void)
316 306
317 while (1) { 307 while (1) {
318 vTaskDelay(pdMS_TO_TICKS(1000)); 308 vTaskDelay(pdMS_TO_TICKS(1000));
319 session_tick(); 309 tollgate_core_tick();
320 tollgate_client_tick(); 310 tollgate_client_tick();
321 lightning_payout_tick(); 311 lightning_payout_tick();
322 } 312 }
diff --git a/main/tollgate_platform.c b/main/tollgate_platform.c
new file mode 100644
index 0000000..c992ea1
--- /dev/null
+++ b/main/tollgate_platform.c
@@ -0,0 +1,64 @@
1#include "tollgate_platform.h"
2#include "tollgate_core.h"
3#include "config.h"
4#include "esp_log.h"
5#include "esp_timer.h"
6#include "freertos/FreeRTOS.h"
7#include "freertos/task.h"
8
9static const char *TAG = "tollgate_platform";
10
11static uint16_t platform_get_price_sats(void)
12{
13 const tollgate_config_t *cfg = tollgate_config_get();
14 return cfg ? (uint16_t)cfg->price_per_step : 21;
15}
16
17static int32_t platform_get_step_ms(void)
18{
19 const tollgate_config_t *cfg = tollgate_config_get();
20 return cfg ? (int32_t)cfg->step_size_ms : 60000;
21}
22
23static const char *platform_get_mint_url(void)
24{
25 const tollgate_config_t *cfg = tollgate_config_get();
26 return cfg ? cfg->mint_url : "https://testnut.cashu.space";
27}
28
29static const char *platform_get_metric(void)
30{
31 const tollgate_config_t *cfg = tollgate_config_get();
32 return cfg ? cfg->metric : "milliseconds";
33}
34
35static int32_t platform_get_step_bytes(void)
36{
37 const tollgate_config_t *cfg = tollgate_config_get();
38 return cfg ? (int32_t)cfg->step_size_bytes : 22020096;
39}
40
41static int64_t platform_get_time_ms(void)
42{
43 return (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS;
44}
45
46static bool platform_spend_proofs(const char *raw_token_json)
47{
48 (void)raw_token_json;
49 return true;
50}
51
52const tollgate_platform_t *tollgate_get_platform(void)
53{
54 static const tollgate_platform_t platform = {
55 .get_price_sats = platform_get_price_sats,
56 .get_step_ms = platform_get_step_ms,
57 .get_mint_url = platform_get_mint_url,
58 .get_metric = platform_get_metric,
59 .get_step_bytes = platform_get_step_bytes,
60 .get_time_ms = platform_get_time_ms,
61 .spend_proofs = platform_spend_proofs,
62 };
63 return &platform;
64}