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-17 04:50:07 +0530
committerYour Name <you@example.com>2026-05-17 04:50:07 +0530
commitedd125d0e3fe5fe7c0edf30c429723f3b0120c68 (patch)
tree5b1134ad7a6cfce7adeb46f5069e33b509ce9751 /main
parentcb4bd7d7c10cadcb43f82c09b13ffed744e541f7 (diff)
feat(phase6): bytes-based billing - dual metric support
- session_create_bytes() + session_add_bytes() for bytes-metric sessions - session_is_expired() dispatches on config metric (bytes vs milliseconds) - cashu_calculate_allotment() unified dispatcher for both metrics - tollgate_api discovery/usage/session_event use configured metric - config: metric field defaults to 'bytes', step_size_bytes=22020096 (21MB) - 14 new unit tests (148 total passing) - ASSERT_EQ_UINT64 macro added to test framework
Diffstat (limited to 'main')
-rw-r--r--main/cashu.c8
-rw-r--r--main/cashu.h3
-rw-r--r--main/config.c10
-rw-r--r--main/config.h2
-rw-r--r--main/session.c30
-rw-r--r--main/session.h7
-rw-r--r--main/tollgate_api.c36
7 files changed, 87 insertions, 9 deletions
diff --git a/main/cashu.c b/main/cashu.c
index ba6d9ef..ec0566c 100644
--- a/main/cashu.c
+++ b/main/cashu.c
@@ -255,6 +255,14 @@ uint64_t cashu_calculate_allotment_ms(uint64_t token_amount, uint64_t price_per_
255 return (token_amount / price_per_step) * step_size_ms; 255 return (token_amount / price_per_step) * step_size_ms;
256} 256}
257 257
258uint64_t cashu_calculate_allotment(uint64_t token_amount, uint64_t price_per_step,
259 const char *metric, uint64_t step_size)
260{
261 if (price_per_step == 0) return 0;
262 (void)metric;
263 return (token_amount / price_per_step) * step_size;
264}
265
258bool cashu_is_mint_accepted(const char *mint_url) 266bool cashu_is_mint_accepted(const char *mint_url)
259{ 267{
260 if (!mint_url || mint_url[0] == '\0') return false; 268 if (!mint_url || mint_url[0] == '\0') return false;
diff --git a/main/cashu.h b/main/cashu.h
index 4c3d43b..76ad2eb 100644
--- a/main/cashu.h
+++ b/main/cashu.h
@@ -37,6 +37,9 @@ esp_err_t cashu_check_proof_states(const char *mint_url, const cashu_token_t *to
37uint64_t cashu_calculate_allotment_ms(uint64_t token_amount, uint64_t price_per_step, 37uint64_t cashu_calculate_allotment_ms(uint64_t token_amount, uint64_t price_per_step,
38 uint64_t step_size_ms); 38 uint64_t step_size_ms);
39 39
40uint64_t cashu_calculate_allotment(uint64_t token_amount, uint64_t price_per_step,
41 const char *metric, uint64_t step_size);
42
40bool cashu_is_mint_accepted(const char *mint_url); 43bool cashu_is_mint_accepted(const char *mint_url);
41 44
42#endif 45#endif
diff --git a/main/config.c b/main/config.c
index 9257397..3e01efc 100644
--- a/main/config.c
+++ b/main/config.c
@@ -20,6 +20,8 @@ esp_err_t tollgate_config_init(void)
20 g_config.ap_max_conn = 4; 20 g_config.ap_max_conn = 4;
21 g_config.price_per_step = 21; 21 g_config.price_per_step = 21;
22 g_config.step_size_ms = 60000; 22 g_config.step_size_ms = 60000;
23 g_config.step_size_bytes = 22020096;
24 strncpy(g_config.metric, "bytes", sizeof(g_config.metric) - 1);
23 g_config.persist_threshold_sats = 1; 25 g_config.persist_threshold_sats = 1;
24 g_config.nostr_publish_interval_s = 21600; 26 g_config.nostr_publish_interval_s = 21600;
25 g_config.client_enabled = false; 27 g_config.client_enabled = false;
@@ -136,6 +138,14 @@ esp_err_t tollgate_config_init(void)
136 cJSON *step = cJSON_GetObjectItem(root, "step_size_ms"); 138 cJSON *step = cJSON_GetObjectItem(root, "step_size_ms");
137 if (step) g_config.step_size_ms = step->valueint; 139 if (step) g_config.step_size_ms = step->valueint;
138 140
141 cJSON *step_bytes = cJSON_GetObjectItem(root, "step_size_bytes");
142 if (step_bytes) g_config.step_size_bytes = step_bytes->valueint;
143
144 cJSON *metric = cJSON_GetObjectItem(root, "metric");
145 if (metric && cJSON_IsString(metric)) {
146 strncpy(g_config.metric, metric->valuestring, sizeof(g_config.metric) - 1);
147 }
148
139 cJSON *persist = cJSON_GetObjectItem(root, "persist_threshold_sats"); 149 cJSON *persist = cJSON_GetObjectItem(root, "persist_threshold_sats");
140 if (persist) g_config.persist_threshold_sats = (uint64_t)persist->valuedouble; 150 if (persist) g_config.persist_threshold_sats = (uint64_t)persist->valuedouble;
141 151
diff --git a/main/config.h b/main/config.h
index de9f856..86b5e1a 100644
--- a/main/config.h
+++ b/main/config.h
@@ -43,6 +43,8 @@ typedef struct {
43 char lnurl_url[256]; 43 char lnurl_url[256];
44 int price_per_step; 44 int price_per_step;
45 int step_size_ms; 45 int step_size_ms;
46 int step_size_bytes;
47 char metric[16];
46 uint64_t persist_threshold_sats; 48 uint64_t persist_threshold_sats;
47 49
48 char nostr_geohash[16]; 50 char nostr_geohash[16];
diff --git a/main/session.c b/main/session.c
index 521b74a..4854163 100644
--- a/main/session.c
+++ b/main/session.c
@@ -1,6 +1,7 @@
1#include "session.h" 1#include "session.h"
2#include "firewall.h" 2#include "firewall.h"
3#include "dns_server.h" 3#include "dns_server.h"
4#include "config.h"
4#include "esp_log.h" 5#include "esp_log.h"
5#include "freertos/FreeRTOS.h" 6#include "freertos/FreeRTOS.h"
6#include "freertos/task.h" 7#include "freertos/task.h"
@@ -103,6 +104,29 @@ session_t *session_create(uint32_t client_ip, uint64_t allotment_ms,
103 return NULL; 104 return NULL;
104} 105}
105 106
107session_t *session_create_bytes(uint32_t client_ip, uint64_t allotment_bytes,
108 const char *spent_secrets[], int secret_count)
109{
110 session_t *s = session_create(client_ip, 0, spent_secrets, secret_count);
111 if (s) {
112 s->allotment_bytes = allotment_bytes;
113 s->bytes_consumed = 0;
114 s->allotment_ms = INT64_MAX;
115 esp_ip4_addr_t ip = { .addr = client_ip };
116 ESP_LOGI(TAG, "Bytes session created: " IPSTR " allotment=%llu bytes", IP2STR(&ip),
117 (unsigned long long)allotment_bytes);
118 }
119 return s;
120}
121
122void session_add_bytes(uint32_t client_ip, uint64_t bytes)
123{
124 session_t *s = session_find_by_ip(client_ip);
125 if (s && s->active) {
126 s->bytes_consumed += bytes;
127 }
128}
129
106session_t *session_find_by_ip(uint32_t client_ip) 130session_t *session_find_by_ip(uint32_t client_ip)
107{ 131{
108 for (int i = 0; i < SESSION_MAX_CLIENTS; i++) { 132 for (int i = 0; i < SESSION_MAX_CLIENTS; i++) {
@@ -136,6 +160,12 @@ void session_extend(session_t *session, uint64_t additional_ms)
136bool session_is_expired(const session_t *session) 160bool session_is_expired(const session_t *session)
137{ 161{
138 if (!session || !session->active) return true; 162 if (!session || !session->active) return true;
163
164 const tollgate_config_t *cfg = tollgate_config_get();
165 if (cfg && strcmp(cfg->metric, "bytes") == 0) {
166 return session->bytes_consumed >= session->allotment_bytes;
167 }
168
139 int64_t elapsed = get_time_ms() - session->start_time_ms; 169 int64_t elapsed = get_time_ms() - session->start_time_ms;
140 return elapsed >= (int64_t)session->allotment_ms; 170 return elapsed >= (int64_t)session->allotment_ms;
141} 171}
diff --git a/main/session.h b/main/session.h
index 8e2d48d..6282f5a 100644
--- a/main/session.h
+++ b/main/session.h
@@ -13,6 +13,8 @@ typedef struct {
13 char mac[SESSION_MAX_MAC_LEN]; 13 char mac[SESSION_MAX_MAC_LEN];
14 uint64_t allotment_ms; 14 uint64_t allotment_ms;
15 int64_t start_time_ms; 15 int64_t start_time_ms;
16 uint64_t allotment_bytes;
17 uint64_t bytes_consumed;
16 bool active; 18 bool active;
17 char spent_secrets[5][65]; 19 char spent_secrets[5][65];
18 int spent_secret_count; 20 int spent_secret_count;
@@ -23,6 +25,11 @@ esp_err_t session_manager_init(void);
23session_t *session_create(uint32_t client_ip, uint64_t allotment_ms, 25session_t *session_create(uint32_t client_ip, uint64_t allotment_ms,
24 const char *spent_secrets[], int secret_count); 26 const char *spent_secrets[], int secret_count);
25 27
28session_t *session_create_bytes(uint32_t client_ip, uint64_t allotment_bytes,
29 const char *spent_secrets[], int secret_count);
30
31void session_add_bytes(uint32_t client_ip, uint64_t bytes);
32
26session_t *session_find_by_ip(uint32_t client_ip); 33session_t *session_find_by_ip(uint32_t client_ip);
27session_t *session_find_by_mac(const char *mac); 34session_t *session_find_by_mac(const char *mac);
28 35
diff --git a/main/tollgate_api.c b/main/tollgate_api.c
index 72ed726..25e7dd2 100644
--- a/main/tollgate_api.c
+++ b/main/tollgate_api.c
@@ -78,7 +78,8 @@ static cJSON *create_session_event(uint32_t client_ip, uint64_t allotment_ms)
78 78
79 cJSON *metric_tag = cJSON_CreateArray(); 79 cJSON *metric_tag = cJSON_CreateArray();
80 cJSON_AddItemToArray(metric_tag, cJSON_CreateString("metric")); 80 cJSON_AddItemToArray(metric_tag, cJSON_CreateString("metric"));
81 cJSON_AddItemToArray(metric_tag, cJSON_CreateString("milliseconds")); 81 const tollgate_config_t *mcfg = tollgate_config_get();
82 cJSON_AddItemToArray(metric_tag, cJSON_CreateString(mcfg->metric[0] ? mcfg->metric : "milliseconds"));
82 cJSON_AddItemToArray(tags, metric_tag); 83 cJSON_AddItemToArray(tags, metric_tag);
83 84
84 cJSON_AddItemToObject(root, "tags", tags); 85 cJSON_AddItemToObject(root, "tags", tags);
@@ -98,13 +99,14 @@ static esp_err_t api_get_discovery(httpd_req_t *req)
98 99
99 cJSON *metric_tag = cJSON_CreateArray(); 100 cJSON *metric_tag = cJSON_CreateArray();
100 cJSON_AddItemToArray(metric_tag, cJSON_CreateString("metric")); 101 cJSON_AddItemToArray(metric_tag, cJSON_CreateString("metric"));
101 cJSON_AddItemToArray(metric_tag, cJSON_CreateString("milliseconds")); 102 cJSON_AddItemToArray(metric_tag, cJSON_CreateString(cfg->metric[0] ? cfg->metric : "milliseconds"));
102 cJSON_AddItemToArray(tags, metric_tag); 103 cJSON_AddItemToArray(tags, metric_tag);
103 104
104 cJSON *step_tag = cJSON_CreateArray(); 105 cJSON *step_tag = cJSON_CreateArray();
105 cJSON_AddItemToArray(step_tag, cJSON_CreateString("step_size")); 106 cJSON_AddItemToArray(step_tag, cJSON_CreateString("step_size"));
106 char step_str[32]; 107 char step_str[32];
107 snprintf(step_str, sizeof(step_str), "%d", cfg->step_size_ms); 108 bool is_bytes = (strcmp(cfg->metric, "bytes") == 0);
109 snprintf(step_str, sizeof(step_str), "%d", is_bytes ? cfg->step_size_bytes : cfg->step_size_ms);
108 cJSON_AddItemToArray(step_tag, cJSON_CreateString(step_str)); 110 cJSON_AddItemToArray(step_tag, cJSON_CreateString(step_str));
109 cJSON_AddItemToArray(tags, step_tag); 111 cJSON_AddItemToArray(tags, step_tag);
110 112
@@ -280,7 +282,10 @@ static esp_err_t api_post_payment(httpd_req_t *req)
280 } 282 }
281 283
282 const tollgate_config_t *cfg = tollgate_config_get(); 284 const tollgate_config_t *cfg = tollgate_config_get();
283 uint64_t allotment = cashu_calculate_allotment_ms(token->total_amount, cfg->price_per_step, cfg->step_size_ms); 285 bool is_bytes = (strcmp(cfg->metric, "bytes") == 0);
286 uint64_t step_size = is_bytes ? (uint64_t)cfg->step_size_bytes : (uint64_t)cfg->step_size_ms;
287 uint64_t allotment = cashu_calculate_allotment(token->total_amount, cfg->price_per_step,
288 cfg->metric, step_size);
284 if (allotment == 0) { 289 if (allotment == 0) {
285 free(states); 290 free(states);
286 free(token); 291 free(token);
@@ -299,7 +304,12 @@ static esp_err_t api_post_payment(httpd_req_t *req)
299 for (int i = 0; i < secret_count; i++) { 304 for (int i = 0; i < secret_count; i++) {
300 secrets[i] = token->proofs[i].secret; 305 secrets[i] = token->proofs[i].secret;
301 } 306 }
302 session_t *session = session_create(client_ip, allotment, secrets, secret_count); 307 session_t *session;
308 if (is_bytes) {
309 session = session_create_bytes(client_ip, allotment, secrets, secret_count);
310 } else {
311 session = session_create(client_ip, allotment, secrets, secret_count);
312 }
303 if (!session) { 313 if (!session) {
304 free(states); 314 free(states);
305 free(token); 315 free(token);
@@ -339,12 +349,20 @@ static esp_err_t api_get_usage(httpd_req_t *req)
339 return ESP_OK; 349 return ESP_OK;
340 } 350 }
341 351
342 int64_t elapsed = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS - session->start_time_ms; 352 const tollgate_config_t *cfg = tollgate_config_get();
343 int64_t remaining = session->allotment_ms - elapsed; 353 bool is_bytes = (strcmp(cfg->metric, "bytes") == 0);
344 if (remaining < 0) remaining = 0;
345 354
346 char resp[64]; 355 char resp[64];
347 snprintf(resp, sizeof(resp), "%lld/%llu", (long long)remaining, (unsigned long long)session->allotment_ms); 356 if (is_bytes) {
357 int64_t remaining = (int64_t)session->allotment_bytes - (int64_t)session->bytes_consumed;
358 if (remaining < 0) remaining = 0;
359 snprintf(resp, sizeof(resp), "%lld/%llu", (long long)remaining, (unsigned long long)session->allotment_bytes);
360 } else {
361 int64_t elapsed = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS - session->start_time_ms;
362 int64_t remaining = session->allotment_ms - elapsed;
363 if (remaining < 0) remaining = 0;
364 snprintf(resp, sizeof(resp), "%lld/%llu", (long long)remaining, (unsigned long long)session->allotment_ms);
365 }
348 httpd_resp_set_type(req, "text/plain"); 366 httpd_resp_set_type(req, "text/plain");
349 httpd_resp_send(req, resp, strlen(resp)); 367 httpd_resp_send(req, resp, strlen(resp));
350 return ESP_OK; 368 return ESP_OK;