diff options
| -rw-r--r-- | components/tollgate_core/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | components/tollgate_core/src/tollgate_core_client.c | 146 | ||||
| -rw-r--r-- | components/tollgate_core/src/tollgate_core_client.h | 51 | ||||
| -rw-r--r-- | components/tollgate_core/src/tollgate_core_mint_health.c | 91 | ||||
| -rw-r--r-- | components/tollgate_core/src/tollgate_core_mint_health.h | 50 | ||||
| -rw-r--r-- | components/tollgate_core/src/tollgate_core_portal.c | 149 | ||||
| -rw-r--r-- | components/tollgate_core/src/tollgate_core_portal.h | 34 | ||||
| -rw-r--r-- | components/tollgate_core/src/tollgate_core_stratum_client.c | 91 | ||||
| -rw-r--r-- | components/tollgate_core/src/tollgate_core_stratum_client.h | 50 | ||||
| -rw-r--r-- | main/captive_portal.c | 108 | ||||
| -rw-r--r-- | main/mint_health.c | 94 | ||||
| -rw-r--r-- | main/stratum_client.c | 95 | ||||
| -rw-r--r-- | main/tollgate_client.c | 171 | ||||
| -rw-r--r-- | main/tollgate_client.h | 27 | ||||
| -rw-r--r-- | tests/unit/Makefile | 26 | ||||
| -rw-r--r-- | tests/unit/test_client_core.c | 189 | ||||
| -rw-r--r-- | tests/unit/test_mint_health_core.c | 130 | ||||
| -rw-r--r-- | tests/unit/test_portal.c | 153 | ||||
| -rw-r--r-- | tests/unit/test_stratum_client.c | 125 |
19 files changed, 1393 insertions, 391 deletions
diff --git a/components/tollgate_core/CMakeLists.txt b/components/tollgate_core/CMakeLists.txt index 3cd5e85..502a276 100644 --- a/components/tollgate_core/CMakeLists.txt +++ b/components/tollgate_core/CMakeLists.txt | |||
| @@ -8,6 +8,10 @@ idf_component_register( | |||
| 8 | "src/tollgate_core_stratum_proxy.c" | 8 | "src/tollgate_core_stratum_proxy.c" |
| 9 | "src/tollgate_core_beacon.c" | 9 | "src/tollgate_core_beacon.c" |
| 10 | "src/tollgate_core_market.c" | 10 | "src/tollgate_core_market.c" |
| 11 | "src/tollgate_core_portal.c" | ||
| 12 | "src/tollgate_core_stratum_client.c" | ||
| 13 | "src/tollgate_core_mint_health.c" | ||
| 14 | "src/tollgate_core_client.c" | ||
| 11 | INCLUDE_DIRS "include" "src" | 15 | INCLUDE_DIRS "include" "src" |
| 12 | REQUIRES lwip json esp_http_client mbedtls log esp_netif | 16 | REQUIRES lwip json esp_http_client mbedtls log esp_netif |
| 13 | PRIV_REQUIRES esp_wifi) | 17 | PRIV_REQUIRES esp_wifi) |
diff --git a/components/tollgate_core/src/tollgate_core_client.c b/components/tollgate_core/src/tollgate_core_client.c new file mode 100644 index 0000000..9e8508a --- /dev/null +++ b/components/tollgate_core/src/tollgate_core_client.c | |||
| @@ -0,0 +1,146 @@ | |||
| 1 | #include "tollgate_core_client.h" | ||
| 2 | #include "cJSON.h" | ||
| 3 | #include <string.h> | ||
| 4 | #include <stdlib.h> | ||
| 5 | |||
| 6 | bool tollgate_core_client_parse_discovery(const char *json_str, tollgate_discovery_t *out) | ||
| 7 | { | ||
| 8 | cJSON *root = cJSON_Parse(json_str); | ||
| 9 | if (!root) return false; | ||
| 10 | |||
| 11 | cJSON *kind = cJSON_GetObjectItemCaseSensitive(root, "kind"); | ||
| 12 | if (!kind || !cJSON_IsNumber(kind) || kind->valueint != 10021) { | ||
| 13 | cJSON_Delete(root); | ||
| 14 | return false; | ||
| 15 | } | ||
| 16 | |||
| 17 | memset(out, 0, sizeof(tollgate_discovery_t)); | ||
| 18 | out->is_tollgate = true; | ||
| 19 | |||
| 20 | cJSON *tags = cJSON_GetObjectItemCaseSensitive(root, "tags"); | ||
| 21 | if (!tags || !cJSON_IsArray(tags)) { | ||
| 22 | cJSON_Delete(root); | ||
| 23 | return true; | ||
| 24 | } | ||
| 25 | |||
| 26 | int tag_count = cJSON_GetArraySize(tags); | ||
| 27 | for (int i = 0; i < tag_count; i++) { | ||
| 28 | cJSON *tag = cJSON_GetArrayItem(tags, i); | ||
| 29 | if (!tag || !cJSON_IsArray(tag)) continue; | ||
| 30 | |||
| 31 | int tag_len = cJSON_GetArraySize(tag); | ||
| 32 | if (tag_len < 2) continue; | ||
| 33 | |||
| 34 | cJSON *tag_name = cJSON_GetArrayItem(tag, 0); | ||
| 35 | if (!tag_name || !cJSON_IsString(tag_name)) continue; | ||
| 36 | |||
| 37 | if (strcmp(tag_name->valuestring, "metric") == 0) { | ||
| 38 | cJSON *val = cJSON_GetArrayItem(tag, 1); | ||
| 39 | if (val && cJSON_IsString(val)) { | ||
| 40 | strncpy(out->metric, val->valuestring, sizeof(out->metric) - 1); | ||
| 41 | } | ||
| 42 | } else if (strcmp(tag_name->valuestring, "step_size") == 0) { | ||
| 43 | cJSON *val = cJSON_GetArrayItem(tag, 1); | ||
| 44 | if (val && cJSON_IsString(val)) { | ||
| 45 | out->step_size_ms = atoi(val->valuestring); | ||
| 46 | } | ||
| 47 | } else if (strcmp(tag_name->valuestring, "price_per_step") == 0 && tag_len >= 4) { | ||
| 48 | cJSON *payment_type = cJSON_GetArrayItem(tag, 2); | ||
| 49 | |||
| 50 | if (cJSON_IsString(payment_type) && strcmp(payment_type->valuestring, "mining") == 0 && tag_len >= 5) { | ||
| 51 | out->mining_available = true; | ||
| 52 | cJSON *port_val = cJSON_GetArrayItem(tag, 3); | ||
| 53 | if (port_val && cJSON_IsString(port_val)) { | ||
| 54 | out->mining_port = (uint16_t)atoi(port_val->valuestring); | ||
| 55 | } | ||
| 56 | } else { | ||
| 57 | cJSON *amount = cJSON_GetArrayItem(tag, 2); | ||
| 58 | cJSON *mint = cJSON_GetArrayItem(tag, 4); | ||
| 59 | |||
| 60 | if (amount && cJSON_IsString(amount)) { | ||
| 61 | out->price_per_step = atoi(amount->valuestring); | ||
| 62 | } | ||
| 63 | if (mint && cJSON_IsString(mint)) { | ||
| 64 | strncpy(out->mint_url, mint->valuestring, sizeof(out->mint_url) - 1); | ||
| 65 | } | ||
| 66 | } | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | cJSON_Delete(root); | ||
| 71 | return true; | ||
| 72 | } | ||
| 73 | |||
| 74 | bool tollgate_core_client_parse_session(const char *json_str, int64_t *allotment_ms_out) | ||
| 75 | { | ||
| 76 | cJSON *root = cJSON_Parse(json_str); | ||
| 77 | if (!root) return false; | ||
| 78 | |||
| 79 | cJSON *kind = cJSON_GetObjectItemCaseSensitive(root, "kind"); | ||
| 80 | if (!kind || !cJSON_IsNumber(kind)) { | ||
| 81 | cJSON_Delete(root); | ||
| 82 | return false; | ||
| 83 | } | ||
| 84 | |||
| 85 | if (kind->valueint != 1022) { | ||
| 86 | cJSON_Delete(root); | ||
| 87 | return false; | ||
| 88 | } | ||
| 89 | |||
| 90 | cJSON *tags = cJSON_GetObjectItemCaseSensitive(root, "tags"); | ||
| 91 | if (tags && cJSON_IsArray(tags)) { | ||
| 92 | int tag_count = cJSON_GetArraySize(tags); | ||
| 93 | for (int i = 0; i < tag_count; i++) { | ||
| 94 | cJSON *tag = cJSON_GetArrayItem(tags, i); | ||
| 95 | if (!tag || !cJSON_IsArray(tag)) continue; | ||
| 96 | cJSON *tag_name = cJSON_GetArrayItem(tag, 0); | ||
| 97 | if (tag_name && cJSON_IsString(tag_name) && strcmp(tag_name->valuestring, "allotment") == 0) { | ||
| 98 | cJSON *val = cJSON_GetArrayItem(tag, 1); | ||
| 99 | if (val && cJSON_IsString(val)) { | ||
| 100 | *allotment_ms_out = atoll(val->valuestring); | ||
| 101 | } | ||
| 102 | } | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | cJSON_Delete(root); | ||
| 107 | return true; | ||
| 108 | } | ||
| 109 | |||
| 110 | bool tollgate_core_client_parse_usage(const char *resp, int64_t *remaining_out, int64_t *total_out) | ||
| 111 | { | ||
| 112 | char remaining_str[32] = {0}; | ||
| 113 | char total_str[32] = {0}; | ||
| 114 | const char *slash = strchr(resp, '/'); | ||
| 115 | if (!slash) return false; | ||
| 116 | |||
| 117 | size_t rlen = slash - resp; | ||
| 118 | if (rlen >= sizeof(remaining_str)) return false; | ||
| 119 | memcpy(remaining_str, resp, rlen); | ||
| 120 | strncpy(total_str, slash + 1, sizeof(total_str) - 1); | ||
| 121 | |||
| 122 | *remaining_out = atoll(remaining_str); | ||
| 123 | *total_out = atoll(total_str); | ||
| 124 | return true; | ||
| 125 | } | ||
| 126 | |||
| 127 | bool tollgate_core_client_should_renew(int64_t remaining_ms, int64_t allotment_ms, int threshold_pct) | ||
| 128 | { | ||
| 129 | if (threshold_pct <= 0) threshold_pct = 20; | ||
| 130 | if (allotment_ms <= 0 || remaining_ms < 0) return false; | ||
| 131 | |||
| 132 | int remaining_pct = (int)((remaining_ms * 100) / allotment_ms); | ||
| 133 | return remaining_pct <= threshold_pct; | ||
| 134 | } | ||
| 135 | |||
| 136 | int tollgate_core_client_calc_price_per_min(int price_per_step, int step_size_ms) | ||
| 137 | { | ||
| 138 | if (step_size_ms <= 0) step_size_ms = 1; | ||
| 139 | return price_per_step * 60000 / step_size_ms; | ||
| 140 | } | ||
| 141 | |||
| 142 | int tollgate_core_client_calc_steps(int steps_to_buy, int price_per_step, int discovery_price) | ||
| 143 | { | ||
| 144 | if (steps_to_buy <= 0) steps_to_buy = 1; | ||
| 145 | return steps_to_buy * discovery_price; | ||
| 146 | } | ||
diff --git a/components/tollgate_core/src/tollgate_core_client.h b/components/tollgate_core/src/tollgate_core_client.h new file mode 100644 index 0000000..32b90e6 --- /dev/null +++ b/components/tollgate_core/src/tollgate_core_client.h | |||
| @@ -0,0 +1,51 @@ | |||
| 1 | #ifndef TOLLGATE_CORE_CLIENT_H | ||
| 2 | #define TOLLGATE_CORE_CLIENT_H | ||
| 3 | |||
| 4 | #include <stdint.h> | ||
| 5 | #include <stdbool.h> | ||
| 6 | |||
| 7 | #define TG_CLIENT_MAX_MINT_URL 256 | ||
| 8 | #define TG_CLIENT_MAX_METRIC 32 | ||
| 9 | #define TG_CLIENT_MAX_GW_IP_LEN 16 | ||
| 10 | |||
| 11 | typedef enum { | ||
| 12 | TG_CLIENT_IDLE, | ||
| 13 | TG_CLIENT_DETECTING, | ||
| 14 | TG_CLIENT_NO_TOLLGATE, | ||
| 15 | TG_CLIENT_NEEDS_PAY, | ||
| 16 | TG_CLIENT_PAYING, | ||
| 17 | TG_CLIENT_PAID, | ||
| 18 | TG_CLIENT_RENEWING, | ||
| 19 | TG_CLIENT_MINING, | ||
| 20 | TG_CLIENT_ERROR | ||
| 21 | } tollgate_client_state_t; | ||
| 22 | |||
| 23 | typedef struct { | ||
| 24 | bool is_tollgate; | ||
| 25 | int price_per_step; | ||
| 26 | int step_size_ms; | ||
| 27 | char mint_url[TG_CLIENT_MAX_MINT_URL]; | ||
| 28 | char metric[TG_CLIENT_MAX_METRIC]; | ||
| 29 | bool mining_available; | ||
| 30 | uint16_t mining_port; | ||
| 31 | } tollgate_discovery_t; | ||
| 32 | |||
| 33 | typedef struct { | ||
| 34 | const char *ssid; | ||
| 35 | int price_per_step; | ||
| 36 | uint32_t step_size; | ||
| 37 | } tollgate_client_market_entry_t; | ||
| 38 | |||
| 39 | bool tollgate_core_client_parse_discovery(const char *json_str, tollgate_discovery_t *out); | ||
| 40 | |||
| 41 | bool tollgate_core_client_parse_session(const char *json_str, int64_t *allotment_ms_out); | ||
| 42 | |||
| 43 | bool tollgate_core_client_parse_usage(const char *resp, int64_t *remaining_out, int64_t *total_out); | ||
| 44 | |||
| 45 | bool tollgate_core_client_should_renew(int64_t remaining_ms, int64_t allotment_ms, int threshold_pct); | ||
| 46 | |||
| 47 | int tollgate_core_client_calc_price_per_min(int price_per_step, int step_size_ms); | ||
| 48 | |||
| 49 | int tollgate_core_client_calc_steps(int steps_to_buy, int price_per_step, int discovery_price); | ||
| 50 | |||
| 51 | #endif | ||
diff --git a/components/tollgate_core/src/tollgate_core_mint_health.c b/components/tollgate_core/src/tollgate_core_mint_health.c new file mode 100644 index 0000000..c810003 --- /dev/null +++ b/components/tollgate_core/src/tollgate_core_mint_health.c | |||
| @@ -0,0 +1,91 @@ | |||
| 1 | #include "tollgate_core_mint_health.h" | ||
| 2 | #include <string.h> | ||
| 3 | |||
| 4 | void tollgate_core_mint_health_init(const char urls[][256], int count) | ||
| 5 | { | ||
| 6 | } | ||
| 7 | |||
| 8 | void tollgate_core_mint_health_update(tollgate_mint_health_t *state, | ||
| 9 | int mint_index, | ||
| 10 | bool probe_ok, | ||
| 11 | int http_status, | ||
| 12 | int err_code, | ||
| 13 | int64_t probe_time_ms) | ||
| 14 | { | ||
| 15 | if (!state || mint_index < 0 || mint_index >= state->count) return; | ||
| 16 | |||
| 17 | tollgate_mint_status_t *m = &state->mints[mint_index]; | ||
| 18 | m->last_probe_ms = probe_time_ms; | ||
| 19 | m->last_http_status = probe_ok ? http_status : 0; | ||
| 20 | m->last_err = probe_ok ? 0 : err_code; | ||
| 21 | |||
| 22 | if (probe_ok) { | ||
| 23 | m->consecutive_successes++; | ||
| 24 | if (m->consecutive_successes >= TG_MINT_HEALTH_RECOVERY_THRESHOLD) { | ||
| 25 | m->reachable = true; | ||
| 26 | } | ||
| 27 | } else { | ||
| 28 | m->reachable = false; | ||
| 29 | m->consecutive_successes = 0; | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | void tollgate_core_mint_health_update_initial(tollgate_mint_health_t *state, | ||
| 34 | int mint_index, | ||
| 35 | bool probe_ok, | ||
| 36 | int http_status, | ||
| 37 | int err_code, | ||
| 38 | int64_t probe_time_ms) | ||
| 39 | { | ||
| 40 | if (!state || mint_index < 0 || mint_index >= state->count) return; | ||
| 41 | |||
| 42 | tollgate_mint_status_t *m = &state->mints[mint_index]; | ||
| 43 | m->last_probe_ms = probe_time_ms; | ||
| 44 | m->last_http_status = probe_ok ? http_status : 0; | ||
| 45 | m->last_err = probe_ok ? 0 : err_code; | ||
| 46 | |||
| 47 | if (probe_ok) { | ||
| 48 | m->consecutive_successes = TG_MINT_HEALTH_RECOVERY_THRESHOLD; | ||
| 49 | m->reachable = true; | ||
| 50 | } else { | ||
| 51 | m->consecutive_successes = 0; | ||
| 52 | m->reachable = false; | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | bool tollgate_core_mint_health_is_reachable(const tollgate_mint_health_t *state, | ||
| 57 | const char *url) | ||
| 58 | { | ||
| 59 | if (!state || !url) return false; | ||
| 60 | for (int i = 0; i < state->count; i++) { | ||
| 61 | if (strcmp(state->mints[i].url, url) == 0 || strstr(url, state->mints[i].url) != NULL) { | ||
| 62 | return state->mints[i].reachable; | ||
| 63 | } | ||
| 64 | } | ||
| 65 | return false; | ||
| 66 | } | ||
| 67 | |||
| 68 | void tollgate_core_mint_health_mark_unreachable(tollgate_mint_health_t *state, | ||
| 69 | const char *url) | ||
| 70 | { | ||
| 71 | if (!state || !url) return; | ||
| 72 | for (int i = 0; i < state->count; i++) { | ||
| 73 | if (strcmp(state->mints[i].url, url) == 0 || strstr(url, state->mints[i].url) != NULL) { | ||
| 74 | if (state->mints[i].reachable) { | ||
| 75 | state->mints[i].reachable = false; | ||
| 76 | state->mints[i].consecutive_successes = 0; | ||
| 77 | } | ||
| 78 | break; | ||
| 79 | } | ||
| 80 | } | ||
| 81 | } | ||
| 82 | |||
| 83 | int tollgate_core_mint_health_count_reachable(const tollgate_mint_health_t *state) | ||
| 84 | { | ||
| 85 | if (!state) return 0; | ||
| 86 | int count = 0; | ||
| 87 | for (int i = 0; i < state->count; i++) { | ||
| 88 | if (state->mints[i].reachable) count++; | ||
| 89 | } | ||
| 90 | return count; | ||
| 91 | } | ||
diff --git a/components/tollgate_core/src/tollgate_core_mint_health.h b/components/tollgate_core/src/tollgate_core_mint_health.h new file mode 100644 index 0000000..4435a14 --- /dev/null +++ b/components/tollgate_core/src/tollgate_core_mint_health.h | |||
| @@ -0,0 +1,50 @@ | |||
| 1 | #ifndef TOLLGATE_CORE_MINT_HEALTH_H | ||
| 2 | #define TOLLGATE_CORE_MINT_HEALTH_H | ||
| 3 | |||
| 4 | #include <stdint.h> | ||
| 5 | #include <stdbool.h> | ||
| 6 | |||
| 7 | #define TG_MINT_HEALTH_MAX 8 | ||
| 8 | #define TG_MINT_HEALTH_RECOVERY_THRESHOLD 3 | ||
| 9 | |||
| 10 | typedef struct { | ||
| 11 | char url[256]; | ||
| 12 | bool reachable; | ||
| 13 | uint8_t consecutive_successes; | ||
| 14 | int64_t last_probe_ms; | ||
| 15 | int last_http_status; | ||
| 16 | int last_err; | ||
| 17 | } tollgate_mint_status_t; | ||
| 18 | |||
| 19 | typedef struct { | ||
| 20 | tollgate_mint_status_t mints[TG_MINT_HEALTH_MAX]; | ||
| 21 | int count; | ||
| 22 | } tollgate_mint_health_t; | ||
| 23 | |||
| 24 | typedef void (*tollgate_mint_health_changed_fn)(int old_reachable, int new_reachable); | ||
| 25 | |||
| 26 | void tollgate_core_mint_health_init(const char urls[][256], int count); | ||
| 27 | |||
| 28 | void tollgate_core_mint_health_update(tollgate_mint_health_t *state, | ||
| 29 | int mint_index, | ||
| 30 | bool probe_ok, | ||
| 31 | int http_status, | ||
| 32 | int err_code, | ||
| 33 | int64_t probe_time_ms); | ||
| 34 | |||
| 35 | void tollgate_core_mint_health_update_initial(tollgate_mint_health_t *state, | ||
| 36 | int mint_index, | ||
| 37 | bool probe_ok, | ||
| 38 | int http_status, | ||
| 39 | int err_code, | ||
| 40 | int64_t probe_time_ms); | ||
| 41 | |||
| 42 | bool tollgate_core_mint_health_is_reachable(const tollgate_mint_health_t *state, | ||
| 43 | const char *url); | ||
| 44 | |||
| 45 | void tollgate_core_mint_health_mark_unreachable(tollgate_mint_health_t *state, | ||
| 46 | const char *url); | ||
| 47 | |||
| 48 | int tollgate_core_mint_health_count_reachable(const tollgate_mint_health_t *state); | ||
| 49 | |||
| 50 | #endif | ||
diff --git a/components/tollgate_core/src/tollgate_core_portal.c b/components/tollgate_core/src/tollgate_core_portal.c new file mode 100644 index 0000000..ef2ae1e --- /dev/null +++ b/components/tollgate_core/src/tollgate_core_portal.c | |||
| @@ -0,0 +1,149 @@ | |||
| 1 | #include "tollgate_core_portal.h" | ||
| 2 | #include <string.h> | ||
| 3 | #include <stdio.h> | ||
| 4 | #include <stdlib.h> | ||
| 5 | |||
| 6 | static const char *CAPTIVE_URIS[] = { | ||
| 7 | "/generate_204", | ||
| 8 | "/hotspot-detect.html", | ||
| 9 | "/canonical.html", | ||
| 10 | "/success.txt", | ||
| 11 | "/ncsi.txt", | ||
| 12 | "/connecttest.txt", | ||
| 13 | "/wpad.dat", | ||
| 14 | NULL | ||
| 15 | }; | ||
| 16 | |||
| 17 | char *tollgate_core_portal_template_replace(const char *tpl, const char *key, const char *val) | ||
| 18 | { | ||
| 19 | const char *p; | ||
| 20 | size_t klen = strlen(key); | ||
| 21 | size_t vlen = strlen(val); | ||
| 22 | size_t tlen = strlen(tpl); | ||
| 23 | size_t extra = 0; | ||
| 24 | |||
| 25 | p = tpl; | ||
| 26 | while ((p = strstr(p, key)) != NULL) { | ||
| 27 | extra += vlen - klen; | ||
| 28 | p += klen; | ||
| 29 | } | ||
| 30 | |||
| 31 | size_t out_size = tlen + extra + 1; | ||
| 32 | char *out = malloc(out_size); | ||
| 33 | if (!out) return NULL; | ||
| 34 | |||
| 35 | char *dst = out; | ||
| 36 | p = tpl; | ||
| 37 | while (*p) { | ||
| 38 | const char *found = strstr(p, key); | ||
| 39 | if (found) { | ||
| 40 | memcpy(dst, p, found - p); | ||
| 41 | dst += found - p; | ||
| 42 | memcpy(dst, val, vlen); | ||
| 43 | dst += vlen; | ||
| 44 | p = found + klen; | ||
| 45 | } else { | ||
| 46 | strcpy(dst, p); | ||
| 47 | dst += strlen(p); | ||
| 48 | break; | ||
| 49 | } | ||
| 50 | } | ||
| 51 | *dst = '\0'; | ||
| 52 | return out; | ||
| 53 | } | ||
| 54 | |||
| 55 | char *tollgate_core_portal_render(const char *tpl, | ||
| 56 | const tollgate_portal_sub_t *subs, | ||
| 57 | int nsubs) | ||
| 58 | { | ||
| 59 | size_t tpl_len = strlen(tpl); | ||
| 60 | |||
| 61 | size_t extra = 0; | ||
| 62 | for (int i = 0; i < nsubs; i++) { | ||
| 63 | const char *p = tpl; | ||
| 64 | size_t klen = strlen(subs[i].key); | ||
| 65 | while ((p = strstr(p, subs[i].key)) != NULL) { | ||
| 66 | extra += strlen(subs[i].val) - klen; | ||
| 67 | p += klen; | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | size_t out_size = tpl_len + extra + 1; | ||
| 72 | char *html = malloc(out_size); | ||
| 73 | if (!html) return NULL; | ||
| 74 | |||
| 75 | char *out = html; | ||
| 76 | const char *src = tpl; | ||
| 77 | while (*src) { | ||
| 78 | const char *earliest = NULL; | ||
| 79 | int ei = -1; | ||
| 80 | for (int i = 0; i < nsubs; i++) { | ||
| 81 | const char *found = strstr(src, subs[i].key); | ||
| 82 | if (found && (earliest == NULL || found < earliest)) { | ||
| 83 | earliest = found; | ||
| 84 | ei = i; | ||
| 85 | } | ||
| 86 | } | ||
| 87 | if (earliest) { | ||
| 88 | size_t vlen = strlen(subs[ei].val); | ||
| 89 | memcpy(out, src, earliest - src); | ||
| 90 | out += earliest - src; | ||
| 91 | memcpy(out, subs[ei].val, vlen); | ||
| 92 | out += vlen; | ||
| 93 | src = earliest + strlen(subs[ei].key); | ||
| 94 | } else { | ||
| 95 | strcpy(out, src); | ||
| 96 | out += strlen(src); | ||
| 97 | break; | ||
| 98 | } | ||
| 99 | } | ||
| 100 | *out = '\0'; | ||
| 101 | return html; | ||
| 102 | } | ||
| 103 | |||
| 104 | bool tollgate_core_portal_calc_usage(const tg_session_t *session, | ||
| 105 | const char *metric, | ||
| 106 | int64_t now_ms, | ||
| 107 | int64_t *remaining_out, | ||
| 108 | int64_t *total_out) | ||
| 109 | { | ||
| 110 | if (!session || !session->active) return false; | ||
| 111 | |||
| 112 | bool is_bytes = (strcmp(metric, "bytes") == 0); | ||
| 113 | |||
| 114 | if (is_bytes) { | ||
| 115 | int64_t remaining = (int64_t)session->allotment_bytes - (int64_t)session->bytes_consumed; | ||
| 116 | if (remaining < 0) remaining = 0; | ||
| 117 | *remaining_out = remaining; | ||
| 118 | *total_out = (int64_t)session->allotment_bytes; | ||
| 119 | } else { | ||
| 120 | int64_t elapsed = now_ms - session->start_time_ms; | ||
| 121 | int64_t remaining = (int64_t)session->allotment_ms - elapsed; | ||
| 122 | if (remaining < 0) remaining = 0; | ||
| 123 | *remaining_out = remaining; | ||
| 124 | *total_out = (int64_t)session->allotment_ms; | ||
| 125 | } | ||
| 126 | return true; | ||
| 127 | } | ||
| 128 | |||
| 129 | int tollgate_core_portal_format_usage(const tg_session_t *session, | ||
| 130 | const char *metric, | ||
| 131 | int64_t now_ms, | ||
| 132 | char *buf, | ||
| 133 | size_t buf_size) | ||
| 134 | { | ||
| 135 | int64_t remaining = 0, total = 0; | ||
| 136 | if (!tollgate_core_portal_calc_usage(session, metric, now_ms, &remaining, &total)) { | ||
| 137 | return snprintf(buf, buf_size, "-1/-1"); | ||
| 138 | } | ||
| 139 | return snprintf(buf, buf_size, "%lld/%llu", (long long)remaining, (unsigned long long)total); | ||
| 140 | } | ||
| 141 | |||
| 142 | bool tollgate_core_portal_is_captive_uri(const char *uri) | ||
| 143 | { | ||
| 144 | if (!uri) return false; | ||
| 145 | for (int i = 0; CAPTIVE_URIS[i] != NULL; i++) { | ||
| 146 | if (strcmp(uri, CAPTIVE_URIS[i]) == 0) return true; | ||
| 147 | } | ||
| 148 | return false; | ||
| 149 | } | ||
diff --git a/components/tollgate_core/src/tollgate_core_portal.h b/components/tollgate_core/src/tollgate_core_portal.h new file mode 100644 index 0000000..20c0552 --- /dev/null +++ b/components/tollgate_core/src/tollgate_core_portal.h | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | #ifndef TOLLGATE_CORE_PORTAL_H | ||
| 2 | #define TOLLGATE_CORE_PORTAL_H | ||
| 3 | |||
| 4 | #include "tollgate_core_session.h" | ||
| 5 | #include <stdint.h> | ||
| 6 | #include <stddef.h> | ||
| 7 | #include <stdbool.h> | ||
| 8 | |||
| 9 | typedef struct { | ||
| 10 | const char *key; | ||
| 11 | const char *val; | ||
| 12 | } tollgate_portal_sub_t; | ||
| 13 | |||
| 14 | char *tollgate_core_portal_template_replace(const char *tpl, const char *key, const char *val); | ||
| 15 | |||
| 16 | char *tollgate_core_portal_render(const char *tpl, | ||
| 17 | const tollgate_portal_sub_t *subs, | ||
| 18 | int nsubs); | ||
| 19 | |||
| 20 | bool tollgate_core_portal_calc_usage(const tg_session_t *session, | ||
| 21 | const char *metric, | ||
| 22 | int64_t now_ms, | ||
| 23 | int64_t *remaining_out, | ||
| 24 | int64_t *total_out); | ||
| 25 | |||
| 26 | int tollgate_core_portal_format_usage(const tg_session_t *session, | ||
| 27 | const char *metric, | ||
| 28 | int64_t now_ms, | ||
| 29 | char *buf, | ||
| 30 | size_t buf_size); | ||
| 31 | |||
| 32 | bool tollgate_core_portal_is_captive_uri(const char *uri); | ||
| 33 | |||
| 34 | #endif | ||
diff --git a/components/tollgate_core/src/tollgate_core_stratum_client.c b/components/tollgate_core/src/tollgate_core_stratum_client.c new file mode 100644 index 0000000..8cf22da --- /dev/null +++ b/components/tollgate_core/src/tollgate_core_stratum_client.c | |||
| @@ -0,0 +1,91 @@ | |||
| 1 | #include "tollgate_core_stratum_client.h" | ||
| 2 | #include "cJSON.h" | ||
| 3 | #include <string.h> | ||
| 4 | #include <stdio.h> | ||
| 5 | #include <stdlib.h> | ||
| 6 | |||
| 7 | void tollgate_core_stratum_hex_to_bytes(const char *hex, uint8_t *out, int len) | ||
| 8 | { | ||
| 9 | for (int i = 0; i < len && hex[i * 2] && hex[i * 2 + 1]; i++) { | ||
| 10 | char byte[3] = {hex[i * 2], hex[i * 2 + 1], 0}; | ||
| 11 | out[i] = (uint8_t)strtoul(byte, NULL, 16); | ||
| 12 | } | ||
| 13 | } | ||
| 14 | |||
| 15 | bool tollgate_core_stratum_parse_notify(const void *params_json, | ||
| 16 | tollgate_stratum_job_t *job, | ||
| 17 | uint32_t *out_nbits) | ||
| 18 | { | ||
| 19 | const cJSON *params = (const cJSON *)params_json; | ||
| 20 | if (!params || !cJSON_IsArray(params) || cJSON_GetArraySize(params) < 6) return false; | ||
| 21 | |||
| 22 | const cJSON *p_job_id = cJSON_GetArrayItem(params, 0); | ||
| 23 | const cJSON *p_prevhash = cJSON_GetArrayItem(params, 1); | ||
| 24 | const cJSON *p_version = cJSON_GetArrayItem(params, 5); | ||
| 25 | const cJSON *p_nbits = cJSON_GetArrayItem(params, 6); | ||
| 26 | const cJSON *p_ntime = cJSON_GetArrayItem(params, 7); | ||
| 27 | |||
| 28 | if (!p_job_id || !p_prevhash || !p_nbits) return false; | ||
| 29 | |||
| 30 | memset(job, 0, sizeof(*job)); | ||
| 31 | job->job_id = (uint32_t)atoi(p_job_id->valuestring); | ||
| 32 | job->valid = true; | ||
| 33 | |||
| 34 | tollgate_core_stratum_hex_to_bytes(p_prevhash->valuestring, job->prevhash, 32); | ||
| 35 | |||
| 36 | if (p_version && cJSON_IsString(p_version)) { | ||
| 37 | job->version = (uint32_t)strtoul(p_version->valuestring, NULL, 16); | ||
| 38 | } | ||
| 39 | if (p_nbits && cJSON_IsString(p_nbits)) { | ||
| 40 | job->nbits = (uint32_t)strtoul(p_nbits->valuestring, NULL, 16); | ||
| 41 | if (out_nbits) *out_nbits = job->nbits; | ||
| 42 | } | ||
| 43 | if (p_ntime && cJSON_IsString(p_ntime)) { | ||
| 44 | job->ntime = (uint32_t)strtoul(p_ntime->valuestring, NULL, 16); | ||
| 45 | } | ||
| 46 | |||
| 47 | memset(job->target, 0xFF, 32); | ||
| 48 | job->target_len = 32; | ||
| 49 | |||
| 50 | return true; | ||
| 51 | } | ||
| 52 | |||
| 53 | bool tollgate_core_stratum_parse_difficulty(const void *params_json, | ||
| 54 | uint64_t *difficulty_out) | ||
| 55 | { | ||
| 56 | const cJSON *params = (const cJSON *)params_json; | ||
| 57 | if (!params || !cJSON_IsArray(params) || cJSON_GetArraySize(params) < 1) return false; | ||
| 58 | |||
| 59 | const cJSON *diff = cJSON_GetArrayItem(params, 0); | ||
| 60 | if (diff && cJSON_IsNumber(diff)) { | ||
| 61 | *difficulty_out = (uint64_t)diff->valuedouble; | ||
| 62 | return true; | ||
| 63 | } | ||
| 64 | return false; | ||
| 65 | } | ||
| 66 | |||
| 67 | int tollgate_core_stratum_build_subscribe(char *buf, size_t buf_size, uint32_t req_id) | ||
| 68 | { | ||
| 69 | return snprintf(buf, buf_size, | ||
| 70 | "{\"id\":%lu,\"method\":\"mining.subscribe\",\"params\":[\"TollGate/1.0\"]}\n", | ||
| 71 | (unsigned long)req_id); | ||
| 72 | } | ||
| 73 | |||
| 74 | int tollgate_core_stratum_build_authorize(char *buf, size_t buf_size, uint32_t req_id, | ||
| 75 | const char *user, const char *pass) | ||
| 76 | { | ||
| 77 | return snprintf(buf, buf_size, | ||
| 78 | "{\"id\":%lu,\"method\":\"mining.authorize\",\"params\":[\"%s\",\"%s\"]}\n", | ||
| 79 | (unsigned long)req_id, user, pass); | ||
| 80 | } | ||
| 81 | |||
| 82 | int tollgate_core_stratum_build_submit(char *buf, size_t buf_size, uint32_t req_id, | ||
| 83 | const char *user, uint32_t job_id, | ||
| 84 | uint32_t ntime, uint32_t nonce, uint32_t version) | ||
| 85 | { | ||
| 86 | return snprintf(buf, buf_size, | ||
| 87 | "{\"id\":%lu,\"method\":\"mining.submit\",\"params\":[\"%s\",\"%lu\",\"%08lx\",\"%08lx\",\"%08lx\"]}\n", | ||
| 88 | (unsigned long)req_id, user, | ||
| 89 | (unsigned long)job_id, (unsigned long)ntime, | ||
| 90 | (unsigned long)nonce, (unsigned long)version); | ||
| 91 | } | ||
diff --git a/components/tollgate_core/src/tollgate_core_stratum_client.h b/components/tollgate_core/src/tollgate_core_stratum_client.h new file mode 100644 index 0000000..fdcce1c --- /dev/null +++ b/components/tollgate_core/src/tollgate_core_stratum_client.h | |||
| @@ -0,0 +1,50 @@ | |||
| 1 | #ifndef TOLLGATE_CORE_STRATUM_CLIENT_H | ||
| 2 | #define TOLLGATE_CORE_STRATUM_CLIENT_H | ||
| 3 | |||
| 4 | #include <stdint.h> | ||
| 5 | #include <stddef.h> | ||
| 6 | #include <stdbool.h> | ||
| 7 | |||
| 8 | #define TG_STRATUM_MAX_JOB_ID_LEN 32 | ||
| 9 | |||
| 10 | typedef struct { | ||
| 11 | uint32_t job_id; | ||
| 12 | uint8_t prevhash[32]; | ||
| 13 | uint8_t merkle_root[32]; | ||
| 14 | uint32_t ntime; | ||
| 15 | uint32_t nbits; | ||
| 16 | uint32_t version; | ||
| 17 | uint8_t target[32]; | ||
| 18 | int target_len; | ||
| 19 | bool valid; | ||
| 20 | } tollgate_stratum_job_t; | ||
| 21 | |||
| 22 | typedef struct { | ||
| 23 | bool connected; | ||
| 24 | char pool_host[128]; | ||
| 25 | uint16_t pool_port; | ||
| 26 | uint32_t nbits; | ||
| 27 | uint64_t difficulty; | ||
| 28 | uint64_t shares_accepted; | ||
| 29 | uint64_t shares_rejected; | ||
| 30 | } tollgate_stratum_client_state_t; | ||
| 31 | |||
| 32 | void tollgate_core_stratum_hex_to_bytes(const char *hex, uint8_t *out, int len); | ||
| 33 | |||
| 34 | bool tollgate_core_stratum_parse_notify(const void *params_json, | ||
| 35 | tollgate_stratum_job_t *job, | ||
| 36 | uint32_t *out_nbits); | ||
| 37 | |||
| 38 | bool tollgate_core_stratum_parse_difficulty(const void *params_json, | ||
| 39 | uint64_t *difficulty_out); | ||
| 40 | |||
| 41 | int tollgate_core_stratum_build_subscribe(char *buf, size_t buf_size, uint32_t req_id); | ||
| 42 | |||
| 43 | int tollgate_core_stratum_build_authorize(char *buf, size_t buf_size, uint32_t req_id, | ||
| 44 | const char *user, const char *pass); | ||
| 45 | |||
| 46 | int tollgate_core_stratum_build_submit(char *buf, size_t buf_size, uint32_t req_id, | ||
| 47 | const char *user, uint32_t job_id, | ||
| 48 | uint32_t ntime, uint32_t nonce, uint32_t version); | ||
| 49 | |||
| 50 | #endif | ||
diff --git a/main/captive_portal.c b/main/captive_portal.c index f95a791..8113856 100644 --- a/main/captive_portal.c +++ b/main/captive_portal.c | |||
| @@ -2,6 +2,7 @@ | |||
| 2 | #include "tollgate_core.h" | 2 | #include "tollgate_core.h" |
| 3 | #include "tollgate_core_firewall.h" | 3 | #include "tollgate_core_firewall.h" |
| 4 | #include "tollgate_core_session.h" | 4 | #include "tollgate_core_session.h" |
| 5 | #include "tollgate_core_portal.h" | ||
| 5 | #include "config.h" | 6 | #include "config.h" |
| 6 | #include "stratum_proxy.h" | 7 | #include "stratum_proxy.h" |
| 7 | #include "esp_log.h" | 8 | #include "esp_log.h" |
| @@ -176,10 +177,12 @@ static esp_err_t portal_handler(httpd_req_t *req) | |||
| 176 | char price_str[16]; | 177 | char price_str[16]; |
| 177 | snprintf(price_str, sizeof(price_str), "%d", cfg->price_per_step); | 178 | snprintf(price_str, sizeof(price_str), "%d", cfg->price_per_step); |
| 178 | 179 | ||
| 179 | const char *tpl = PORTAL_HTML_TEMPLATE; | 180 | char mining_port_buf[8] = "3333"; |
| 180 | size_t tpl_len = strlen(tpl); | 181 | if (cfg->mining_enabled) { |
| 182 | snprintf(mining_port_buf, sizeof(mining_port_buf), "%d", cfg->mining_port); | ||
| 183 | } | ||
| 181 | 184 | ||
| 182 | struct { const char *key; const char *val; } subs[] = { | 185 | tollgate_portal_sub_t subs[] = { |
| 183 | { "__AP_IP__", s_ap_ip_str }, | 186 | { "__AP_IP__", s_ap_ip_str }, |
| 184 | { "__PRICE__", price_str }, | 187 | { "__PRICE__", price_str }, |
| 185 | { "__MINT_URL__", cfg->mint_url }, | 188 | { "__MINT_URL__", cfg->mint_url }, |
| @@ -188,63 +191,19 @@ static esp_err_t portal_handler(httpd_req_t *req) | |||
| 188 | "<button class='tab active' onclick=\"switchTab('cashu')\">Cashu</button>" | 191 | "<button class='tab active' onclick=\"switchTab('cashu')\">Cashu</button>" |
| 189 | "<button class='tab' onclick=\"switchTab('mining')\">Mine</button>" | 192 | "<button class='tab' onclick=\"switchTab('mining')\">Mine</button>" |
| 190 | "</div>" : "" }, | 193 | "</div>" : "" }, |
| 191 | { "__MINING_PORT__", cfg->mining_enabled ? | 194 | { "__MINING_PORT__", mining_port_buf }, |
| 192 | (char[]){ [0 ... 7] = 0 } : "3333" }, | ||
| 193 | { "__CASHU_ACTIVE__", "active" }, | 195 | { "__CASHU_ACTIVE__", "active" }, |
| 194 | { "__MINING_ACTIVE__", "" }, | 196 | { "__MINING_ACTIVE__", "" }, |
| 195 | }; | 197 | }; |
| 196 | char mining_port_buf[8] = "3333"; | ||
| 197 | if (cfg->mining_enabled) { | ||
| 198 | snprintf(mining_port_buf, sizeof(mining_port_buf), "%d", cfg->mining_port); | ||
| 199 | subs[4].val = mining_port_buf; | ||
| 200 | } | ||
| 201 | int nsubs = sizeof(subs) / sizeof(subs[0]); | 198 | int nsubs = sizeof(subs) / sizeof(subs[0]); |
| 202 | 199 | ||
| 203 | size_t extra = 0; | 200 | char *html = tollgate_core_portal_render(PORTAL_HTML_TEMPLATE, subs, nsubs); |
| 204 | for (int i = 0; i < nsubs; i++) { | ||
| 205 | const char *p = tpl; | ||
| 206 | size_t klen = strlen(subs[i].key); | ||
| 207 | while ((p = strstr(p, subs[i].key)) != NULL) { | ||
| 208 | extra += strlen(subs[i].val) - klen; | ||
| 209 | p += klen; | ||
| 210 | } | ||
| 211 | } | ||
| 212 | |||
| 213 | size_t out_size = tpl_len + extra + 1; | ||
| 214 | char *html = malloc(out_size); | ||
| 215 | if (!html) { | 201 | if (!html) { |
| 216 | httpd_resp_send_500(req); | 202 | httpd_resp_send_500(req); |
| 217 | return ESP_OK; | 203 | return ESP_OK; |
| 218 | } | 204 | } |
| 219 | 205 | ||
| 220 | char *out = html; | 206 | httpd_resp_send(req, html, strlen(html)); |
| 221 | const char *src = tpl; | ||
| 222 | while (*src) { | ||
| 223 | const char *earliest = NULL; | ||
| 224 | int ei = -1; | ||
| 225 | for (int i = 0; i < nsubs; i++) { | ||
| 226 | const char *found = strstr(src, subs[i].key); | ||
| 227 | if (found && (earliest == NULL || found < earliest)) { | ||
| 228 | earliest = found; | ||
| 229 | ei = i; | ||
| 230 | } | ||
| 231 | } | ||
| 232 | if (earliest) { | ||
| 233 | size_t vlen = strlen(subs[ei].val); | ||
| 234 | memcpy(out, src, earliest - src); | ||
| 235 | out += earliest - src; | ||
| 236 | memcpy(out, subs[ei].val, vlen); | ||
| 237 | out += vlen; | ||
| 238 | src = earliest + strlen(subs[ei].key); | ||
| 239 | } else { | ||
| 240 | strcpy(out, src); | ||
| 241 | out += strlen(src); | ||
| 242 | break; | ||
| 243 | } | ||
| 244 | } | ||
| 245 | *out = '\0'; | ||
| 246 | |||
| 247 | httpd_resp_send(req, html, out - html); | ||
| 248 | free(html); | 207 | free(html); |
| 249 | return ESP_OK; | 208 | return ESP_OK; |
| 250 | } | 209 | } |
| @@ -312,19 +271,10 @@ static esp_err_t usage_handler(httpd_req_t *req) | |||
| 312 | } | 271 | } |
| 313 | 272 | ||
| 314 | const tollgate_config_t *cfg = tollgate_config_get(); | 273 | const tollgate_config_t *cfg = tollgate_config_get(); |
| 315 | bool is_bytes = (strcmp(cfg->metric, "bytes") == 0); | 274 | int64_t now_ms = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; |
| 316 | 275 | ||
| 317 | char resp[64]; | 276 | char resp[64]; |
| 318 | if (is_bytes) { | 277 | tollgate_core_portal_format_usage(session, cfg->metric, now_ms, resp, sizeof(resp)); |
| 319 | int64_t remaining = (int64_t)session->allotment_bytes - (int64_t)session->bytes_consumed; | ||
| 320 | if (remaining < 0) remaining = 0; | ||
| 321 | snprintf(resp, sizeof(resp), "%lld/%llu", (long long)remaining, (unsigned long long)session->allotment_bytes); | ||
| 322 | } else { | ||
| 323 | int64_t elapsed = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS - session->start_time_ms; | ||
| 324 | int64_t remaining = session->allotment_ms - elapsed; | ||
| 325 | if (remaining < 0) remaining = 0; | ||
| 326 | snprintf(resp, sizeof(resp), "%lld/%llu", (long long)remaining, (unsigned long long)session->allotment_ms); | ||
| 327 | } | ||
| 328 | httpd_resp_set_type(req, "text/plain"); | 278 | httpd_resp_set_type(req, "text/plain"); |
| 329 | httpd_resp_send(req, resp, strlen(resp)); | 279 | httpd_resp_send(req, resp, strlen(resp)); |
| 330 | return ESP_OK; | 280 | return ESP_OK; |
| @@ -342,7 +292,9 @@ static esp_err_t reset_auth_handler(httpd_req_t *req) | |||
| 342 | 292 | ||
| 343 | static esp_err_t redirect_to_portal_handler(httpd_req_t *req) | 293 | static esp_err_t redirect_to_portal_handler(httpd_req_t *req) |
| 344 | { | 294 | { |
| 345 | ESP_LOGI(TAG, "Captive detect: GET %s → 200 portal HTML", req->uri); | 295 | if (tollgate_core_portal_is_captive_uri(req->uri)) { |
| 296 | ESP_LOGI(TAG, "Captive detect: GET %s → 200 portal HTML", req->uri); | ||
| 297 | } | ||
| 346 | return portal_handler(req); | 298 | return portal_handler(req); |
| 347 | } | 299 | } |
| 348 | 300 | ||
| @@ -493,37 +445,7 @@ static const char SETUP_HTML_TEMPLATE[] = \ | |||
| 493 | "</body></html>"; | 445 | "</body></html>"; |
| 494 | 446 | ||
| 495 | static char *template_replace(const char *tpl, const char *key, const char *val) { | 447 | static char *template_replace(const char *tpl, const char *key, const char *val) { |
| 496 | const char *p; | 448 | return tollgate_core_portal_template_replace(tpl, key, val); |
| 497 | size_t klen = strlen(key); | ||
| 498 | size_t vlen = strlen(val); | ||
| 499 | size_t tlen = strlen(tpl); | ||
| 500 | size_t extra = 0; | ||
| 501 | p = tpl; | ||
| 502 | while ((p = strstr(p, key)) != NULL) { | ||
| 503 | extra += vlen - klen; | ||
| 504 | p += klen; | ||
| 505 | } | ||
| 506 | size_t out_size = tlen + extra + 1; | ||
| 507 | char *out = malloc(out_size); | ||
| 508 | if (!out) return NULL; | ||
| 509 | char *dst = out; | ||
| 510 | p = tpl; | ||
| 511 | while (*p) { | ||
| 512 | const char *found = strstr(p, key); | ||
| 513 | if (found) { | ||
| 514 | memcpy(dst, p, found - p); | ||
| 515 | dst += found - p; | ||
| 516 | memcpy(dst, val, vlen); | ||
| 517 | dst += vlen; | ||
| 518 | p = found + klen; | ||
| 519 | } else { | ||
| 520 | strcpy(dst, p); | ||
| 521 | dst += strlen(p); | ||
| 522 | break; | ||
| 523 | } | ||
| 524 | } | ||
| 525 | *dst = '\0'; | ||
| 526 | return out; | ||
| 527 | } | 449 | } |
| 528 | 450 | ||
| 529 | static bool is_setup_available(void) { | 451 | static bool is_setup_available(void) { |
diff --git a/main/mint_health.c b/main/mint_health.c index d309731..48a4333 100644 --- a/main/mint_health.c +++ b/main/mint_health.c | |||
| @@ -1,5 +1,6 @@ | |||
| 1 | #include "mint_health.h" | 1 | #include "mint_health.h" |
| 2 | #include "tls_worker.h" | 2 | #include "tls_worker.h" |
| 3 | #include "tollgate_core_mint_health.h" | ||
| 3 | #include "esp_log.h" | 4 | #include "esp_log.h" |
| 4 | #include "esp_http_client.h" | 5 | #include "esp_http_client.h" |
| 5 | #include "esp_crt_bundle.h" | 6 | #include "esp_crt_bundle.h" |
| @@ -18,8 +19,7 @@ static QueueHandle_t s_wallet_queue = NULL; | |||
| 18 | 19 | ||
| 19 | static int s_last_probe_err = 0; | 20 | static int s_last_probe_err = 0; |
| 20 | 21 | ||
| 21 | static mint_status_t s_mints[MINT_HEALTH_MAX]; | 22 | static tollgate_mint_health_t s_health_state; |
| 22 | static int s_mint_count = 0; | ||
| 23 | static bool s_running = false; | 23 | static bool s_running = false; |
| 24 | static TaskHandle_t s_task_handle = NULL; | 24 | static TaskHandle_t s_task_handle = NULL; |
| 25 | static SemaphoreHandle_t s_mutex = NULL; | 25 | static SemaphoreHandle_t s_mutex = NULL; |
| @@ -38,18 +38,18 @@ static void fire_callbacks(void) | |||
| 38 | esp_err_t mint_health_init(const char urls[][256], int count) | 38 | esp_err_t mint_health_init(const char urls[][256], int count) |
| 39 | { | 39 | { |
| 40 | if (count > MINT_HEALTH_MAX) count = MINT_HEALTH_MAX; | 40 | if (count > MINT_HEALTH_MAX) count = MINT_HEALTH_MAX; |
| 41 | s_mint_count = count; | 41 | s_health_state.count = count; |
| 42 | s_callback_count = 0; | 42 | s_callback_count = 0; |
| 43 | 43 | ||
| 44 | if (!s_mutex) s_mutex = xSemaphoreCreateMutex(); | 44 | if (!s_mutex) s_mutex = xSemaphoreCreateMutex(); |
| 45 | 45 | ||
| 46 | memset(s_mints, 0, sizeof(s_mints)); | 46 | memset(s_health_state.mints, 0, sizeof(s_health_state.mints)); |
| 47 | for (int i = 0; i < count; i++) { | 47 | for (int i = 0; i < count; i++) { |
| 48 | strncpy(s_mints[i].url, urls[i], sizeof(s_mints[i].url) - 1); | 48 | strncpy(s_health_state.mints[i].url, urls[i], sizeof(s_health_state.mints[i].url) - 1); |
| 49 | s_mints[i].reachable = false; | 49 | s_health_state.mints[i].reachable = false; |
| 50 | s_mints[i].consecutive_successes = 0; | 50 | s_health_state.mints[i].consecutive_successes = 0; |
| 51 | s_mints[i].last_probe_ms = 0; | 51 | s_health_state.mints[i].last_probe_ms = 0; |
| 52 | s_mints[i].last_http_status = 0; | 52 | s_health_state.mints[i].last_http_status = 0; |
| 53 | } | 53 | } |
| 54 | 54 | ||
| 55 | ESP_LOGI(TAG, "Initialized with %d mints", count); | 55 | ESP_LOGI(TAG, "Initialized with %d mints", count); |
| @@ -106,35 +106,17 @@ static void run_probes(void) | |||
| 106 | 106 | ||
| 107 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return; | 107 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return; |
| 108 | 108 | ||
| 109 | for (int i = 0; i < s_mint_count; i++) { | 109 | old_reachable = tollgate_core_mint_health_count_reachable(&s_health_state); |
| 110 | if (s_mints[i].reachable) old_reachable++; | ||
| 111 | } | ||
| 112 | |||
| 113 | for (int i = 0; i < s_mint_count; i++) { | ||
| 114 | bool ok = probe_mint(s_mints[i].url); | ||
| 115 | s_mints[i].last_probe_ms = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; | ||
| 116 | s_mints[i].last_http_status = ok ? 200 : 0; | ||
| 117 | s_mints[i].last_err = ok ? 0 : s_last_probe_err; | ||
| 118 | 110 | ||
| 119 | if (ok) { | 111 | for (int i = 0; i < s_health_state.count; i++) { |
| 120 | s_mints[i].consecutive_successes++; | 112 | bool ok = probe_mint(s_health_state.mints[i].url); |
| 121 | if (s_mints[i].consecutive_successes >= MINT_HEALTH_RECOVERY_THRESHOLD) { | 113 | int64_t probe_time = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; |
| 122 | if (!s_mints[i].reachable) { | ||
| 123 | ESP_LOGI(TAG, "Mint RECOVERED: %s", s_mints[i].url); | ||
| 124 | } | ||
| 125 | s_mints[i].reachable = true; | ||
| 126 | } | ||
| 127 | } else { | ||
| 128 | if (s_mints[i].reachable) { | ||
| 129 | ESP_LOGW(TAG, "Mint UNREACHABLE: %s err=0x%x", s_mints[i].url, s_last_probe_err); | ||
| 130 | } | ||
| 131 | s_mints[i].reachable = false; | ||
| 132 | s_mints[i].consecutive_successes = 0; | ||
| 133 | } | ||
| 134 | 114 | ||
| 135 | if (s_mints[i].reachable) new_reachable++; | 115 | tollgate_core_mint_health_update(&s_health_state, i, ok, ok ? 200 : 0, |
| 116 | ok ? 0 : s_last_probe_err, probe_time); | ||
| 136 | } | 117 | } |
| 137 | 118 | ||
| 119 | new_reachable = tollgate_core_mint_health_count_reachable(&s_health_state); | ||
| 138 | bool changed = (old_reachable != new_reachable); | 120 | bool changed = (old_reachable != new_reachable); |
| 139 | xSemaphoreGive(s_mutex); | 121 | xSemaphoreGive(s_mutex); |
| 140 | 122 | ||
| @@ -148,20 +130,17 @@ static void run_initial_probes(void) | |||
| 148 | { | 130 | { |
| 149 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return; | 131 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return; |
| 150 | 132 | ||
| 151 | for (int i = 0; i < s_mint_count; i++) { | 133 | for (int i = 0; i < s_health_state.count; i++) { |
| 152 | bool ok = probe_mint(s_mints[i].url); | 134 | bool ok = probe_mint(s_health_state.mints[i].url); |
| 153 | s_mints[i].last_probe_ms = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; | 135 | int64_t probe_time = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; |
| 154 | s_mints[i].last_http_status = ok ? 200 : 0; | 136 | |
| 155 | s_mints[i].last_err = ok ? 0 : s_last_probe_err; | 137 | tollgate_core_mint_health_update_initial(&s_health_state, i, ok, ok ? 200 : 0, |
| 138 | ok ? 0 : s_last_probe_err, probe_time); | ||
| 156 | 139 | ||
| 157 | if (ok) { | 140 | if (ok) { |
| 158 | s_mints[i].consecutive_successes = MINT_HEALTH_RECOVERY_THRESHOLD; | 141 | ESP_LOGI(TAG, "Initial probe OK: %s (reachable)", s_health_state.mints[i].url); |
| 159 | s_mints[i].reachable = true; | ||
| 160 | ESP_LOGI(TAG, "Initial probe OK: %s (reachable)", s_mints[i].url); | ||
| 161 | } else { | 142 | } else { |
| 162 | s_mints[i].consecutive_successes = 0; | 143 | ESP_LOGW(TAG, "Initial probe FAIL: %s (unreachable)", s_health_state.mints[i].url); |
| 163 | s_mints[i].reachable = false; | ||
| 164 | ESP_LOGW(TAG, "Initial probe FAIL: %s (unreachable)", s_mints[i].url); | ||
| 165 | } | 144 | } |
| 166 | } | 145 | } |
| 167 | 146 | ||
| @@ -246,24 +225,18 @@ const mint_status_t *mint_health_get_all(int *out_count) | |||
| 246 | { | 225 | { |
| 247 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) { | 226 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) { |
| 248 | *out_count = 0; | 227 | *out_count = 0; |
| 249 | return s_mints; | 228 | return (const mint_status_t *)s_health_state.mints; |
| 250 | } | 229 | } |
| 251 | *out_count = s_mint_count; | 230 | *out_count = s_health_state.count; |
| 252 | xSemaphoreGive(s_mutex); | 231 | xSemaphoreGive(s_mutex); |
| 253 | return s_mints; | 232 | return (const mint_status_t *)s_health_state.mints; |
| 254 | } | 233 | } |
| 255 | 234 | ||
| 256 | bool mint_health_is_reachable(const char *url) | 235 | bool mint_health_is_reachable(const char *url) |
| 257 | { | 236 | { |
| 258 | if (!url) return false; | 237 | if (!url) return false; |
| 259 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) return false; | 238 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) return false; |
| 260 | bool result = false; | 239 | bool result = tollgate_core_mint_health_is_reachable(&s_health_state, url); |
| 261 | for (int i = 0; i < s_mint_count; i++) { | ||
| 262 | if (strcmp(s_mints[i].url, url) == 0 || strstr(url, s_mints[i].url) != NULL) { | ||
| 263 | result = s_mints[i].reachable; | ||
| 264 | break; | ||
| 265 | } | ||
| 266 | } | ||
| 267 | xSemaphoreGive(s_mutex); | 240 | xSemaphoreGive(s_mutex); |
| 268 | return result; | 241 | return result; |
| 269 | } | 242 | } |
| @@ -272,16 +245,7 @@ void mint_health_mark_unreachable(const char *url) | |||
| 272 | { | 245 | { |
| 273 | if (!url) return; | 246 | if (!url) return; |
| 274 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) return; | 247 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) return; |
| 275 | for (int i = 0; i < s_mint_count; i++) { | 248 | tollgate_core_mint_health_mark_unreachable(&s_health_state, url); |
| 276 | if (strcmp(s_mints[i].url, url) == 0 || strstr(url, s_mints[i].url) != NULL) { | ||
| 277 | if (s_mints[i].reachable) { | ||
| 278 | s_mints[i].reachable = false; | ||
| 279 | s_mints[i].consecutive_successes = 0; | ||
| 280 | ESP_LOGW(TAG, "Reactively marked unreachable: %s", url); | ||
| 281 | } | ||
| 282 | break; | ||
| 283 | } | ||
| 284 | } | ||
| 285 | xSemaphoreGive(s_mutex); | 249 | xSemaphoreGive(s_mutex); |
| 286 | } | 250 | } |
| 287 | 251 | ||
diff --git a/main/stratum_client.c b/main/stratum_client.c index a308a5b..14f0880 100644 --- a/main/stratum_client.c +++ b/main/stratum_client.c | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | #include "stratum_client.h" | 1 | #include "stratum_client.h" |
| 2 | #include "stratum_proxy.h" | 2 | #include "stratum_proxy.h" |
| 3 | #include "tollgate_core_mining.h" | 3 | #include "tollgate_core_mining.h" |
| 4 | #include "tollgate_core_stratum_client.h" | ||
| 4 | #include "config.h" | 5 | #include "config.h" |
| 5 | #include "esp_log.h" | 6 | #include "esp_log.h" |
| 6 | #include "esp_transport.h" | 7 | #include "esp_transport.h" |
| @@ -66,76 +67,53 @@ static esp_err_t stratum_connect(const char *host, uint16_t port) | |||
| 66 | static void send_subscribe(void) | 67 | static void send_subscribe(void) |
| 67 | { | 68 | { |
| 68 | char subscribe[256]; | 69 | char subscribe[256]; |
| 69 | snprintf(subscribe, sizeof(subscribe), | 70 | int len = tollgate_core_stratum_build_subscribe(subscribe, sizeof(subscribe), s_req_id++); |
| 70 | "{\"id\":%lu,\"method\":\"mining.subscribe\",\"params\":[\"TollGate/1.0\"]}\n", | 71 | if (len > 0) { |
| 71 | (unsigned long)s_req_id++); | 72 | esp_transport_write(s_transport, subscribe, len, 5000); |
| 72 | esp_transport_write(s_transport, subscribe, strlen(subscribe), 5000); | 73 | ESP_LOGI(TAG, "Sent mining.subscribe"); |
| 73 | ESP_LOGI(TAG, "Sent mining.subscribe"); | 74 | } |
| 74 | } | 75 | } |
| 75 | 76 | ||
| 76 | static void send_authorize(void) | 77 | static void send_authorize(void) |
| 77 | { | 78 | { |
| 78 | const tollgate_config_t *cfg = tollgate_config_get(); | 79 | const tollgate_config_t *cfg = tollgate_config_get(); |
| 79 | char authorize[512]; | 80 | char authorize[512]; |
| 80 | snprintf(authorize, sizeof(authorize), | 81 | int len = tollgate_core_stratum_build_authorize(authorize, sizeof(authorize), |
| 81 | "{\"id\":%lu,\"method\":\"mining.authorize\",\"params\":[\"%s\",\"%s\"]}\n", | 82 | s_req_id++, cfg->stratum_user, cfg->stratum_pass); |
| 82 | (unsigned long)s_req_id++, cfg->stratum_user, cfg->stratum_pass); | 83 | if (len > 0) { |
| 83 | esp_transport_write(s_transport, authorize, strlen(authorize), 5000); | 84 | esp_transport_write(s_transport, authorize, len, 5000); |
| 84 | ESP_LOGI(TAG, "Sent mining.authorize for user=%s", cfg->stratum_user); | 85 | ESP_LOGI(TAG, "Sent mining.authorize for user=%s", cfg->stratum_user); |
| 85 | } | ||
| 86 | |||
| 87 | static void hex_to_bytes(const char *hex, uint8_t *out, int len) | ||
| 88 | { | ||
| 89 | for (int i = 0; i < len && hex[i * 2] && hex[i * 2 + 1]; i++) { | ||
| 90 | char byte[3] = {hex[i * 2], hex[i * 2 + 1], 0}; | ||
| 91 | out[i] = (uint8_t)strtoul(byte, NULL, 16); | ||
| 92 | } | 86 | } |
| 93 | } | 87 | } |
| 94 | 88 | ||
| 95 | static void handle_mining_notify(cJSON *params) | 89 | static void handle_mining_notify(cJSON *params) |
| 96 | { | 90 | { |
| 97 | if (!params || !cJSON_IsArray(params) || cJSON_GetArraySize(params) < 6) return; | 91 | tollgate_stratum_job_t job = {0}; |
| 98 | 92 | uint32_t nbits = 0; | |
| 99 | cJSON *p_job_id = cJSON_GetArrayItem(params, 0); | 93 | if (!tollgate_core_stratum_parse_notify(params, &job, &nbits)) return; |
| 100 | cJSON *p_prevhash = cJSON_GetArrayItem(params, 1); | 94 | |
| 101 | cJSON *p_version = cJSON_GetArrayItem(params, 5); | 95 | stratum_job_t proxy_job = {0}; |
| 102 | cJSON *p_nbits = cJSON_GetArrayItem(params, 6); | 96 | proxy_job.job_id = job.job_id; |
| 103 | cJSON *p_ntime = cJSON_GetArrayItem(params, 7); | 97 | memcpy(proxy_job.prevhash, job.prevhash, 32); |
| 104 | 98 | proxy_job.version = job.version; | |
| 105 | if (!p_job_id || !p_prevhash || !p_nbits) return; | 99 | proxy_job.nbits = job.nbits; |
| 106 | 100 | proxy_job.ntime = job.ntime; | |
| 107 | stratum_job_t job = {0}; | 101 | memcpy(proxy_job.target, job.target, 32); |
| 108 | job.job_id = (uint32_t)atoi(p_job_id->valuestring); | 102 | proxy_job.target_len = job.target_len; |
| 109 | job.valid = true; | 103 | proxy_job.valid = job.valid; |
| 110 | 104 | ||
| 111 | hex_to_bytes(p_prevhash->valuestring, job.prevhash, 32); | 105 | tollgate_core_mining_set_current_nbits(proxy_job.nbits); |
| 112 | 106 | stratum_proxy_set_job(&proxy_job); | |
| 113 | if (p_version && cJSON_IsString(p_version)) { | 107 | |
| 114 | job.version = (uint32_t)strtoul(p_version->valuestring, NULL, 16); | 108 | if (nbits) s_state.nbits = nbits; |
| 115 | } | ||
| 116 | if (p_nbits && cJSON_IsString(p_nbits)) { | ||
| 117 | job.nbits = (uint32_t)strtoul(p_nbits->valuestring, NULL, 16); | ||
| 118 | s_state.nbits = job.nbits; | ||
| 119 | } | ||
| 120 | if (p_ntime && cJSON_IsString(p_ntime)) { | ||
| 121 | job.ntime = (uint32_t)strtoul(p_ntime->valuestring, NULL, 16); | ||
| 122 | } | ||
| 123 | |||
| 124 | memset(job.target, 0xFF, 32); | ||
| 125 | job.target_len = 32; | ||
| 126 | |||
| 127 | tollgate_core_mining_set_current_nbits(job.nbits); | ||
| 128 | stratum_proxy_set_job(&job); | ||
| 129 | |||
| 130 | ESP_LOGI(TAG, "New mining job: id=%lu, nbits=0x%08lx", (unsigned long)job.job_id, (unsigned long)job.nbits); | 109 | ESP_LOGI(TAG, "New mining job: id=%lu, nbits=0x%08lx", (unsigned long)job.job_id, (unsigned long)job.nbits); |
| 131 | } | 110 | } |
| 132 | 111 | ||
| 133 | static void handle_mining_set_difficulty(cJSON *params) | 112 | static void handle_mining_set_difficulty(cJSON *params) |
| 134 | { | 113 | { |
| 135 | if (!params || !cJSON_IsArray(params) || cJSON_GetArraySize(params) < 1) return; | 114 | uint64_t diff = 0; |
| 136 | cJSON *diff = cJSON_GetArrayItem(params, 0); | 115 | if (tollgate_core_stratum_parse_difficulty(params, &diff)) { |
| 137 | if (diff && cJSON_IsNumber(diff)) { | 116 | s_state.difficulty = diff; |
| 138 | s_state.difficulty = (uint64_t)diff->valuedouble; | ||
| 139 | ESP_LOGI(TAG, "Pool set difficulty: %llu", (unsigned long long)s_state.difficulty); | 117 | ESP_LOGI(TAG, "Pool set difficulty: %llu", (unsigned long long)s_state.difficulty); |
| 140 | } | 118 | } |
| 141 | } | 119 | } |
| @@ -243,12 +221,11 @@ esp_err_t stratum_client_submit_share(uint32_t job_id, uint32_t nonce, uint32_t | |||
| 243 | const tollgate_config_t *cfg = tollgate_config_get(); | 221 | const tollgate_config_t *cfg = tollgate_config_get(); |
| 244 | 222 | ||
| 245 | char submit[512]; | 223 | char submit[512]; |
| 246 | snprintf(submit, sizeof(submit), | 224 | int len = tollgate_core_stratum_build_submit(submit, sizeof(submit), s_req_id++, |
| 247 | "{\"id\":%lu,\"method\":\"mining.submit\",\"params\":[\"%s\",\"%lu\",\"%08lx\",\"%08lx\",\"%08lx\"]}\n", | 225 | cfg->stratum_user, job_id, ntime, nonce, version); |
| 248 | (unsigned long)s_req_id++, cfg->stratum_user, | 226 | if (len <= 0) return ESP_FAIL; |
| 249 | (unsigned long)job_id, (unsigned long)ntime, (unsigned long)nonce, (unsigned long)version); | ||
| 250 | 227 | ||
| 251 | int written = esp_transport_write(s_transport, submit, strlen(submit), 5000); | 228 | int written = esp_transport_write(s_transport, submit, len, 5000); |
| 252 | if (written < 0) { | 229 | if (written < 0) { |
| 253 | ESP_LOGW(TAG, "Failed to submit share"); | 230 | ESP_LOGW(TAG, "Failed to submit share"); |
| 254 | s_state.shares_rejected++; | 231 | s_state.shares_rejected++; |
diff --git a/main/tollgate_client.c b/main/tollgate_client.c index 73c8370..70a25c2 100644 --- a/main/tollgate_client.c +++ b/main/tollgate_client.c | |||
| @@ -88,123 +88,17 @@ static esp_err_t http_post_text(const char *url, const char *body, char *resp_bu | |||
| 88 | 88 | ||
| 89 | static bool parse_discovery_response(const char *json_str, tollgate_discovery_t *out) | 89 | static bool parse_discovery_response(const char *json_str, tollgate_discovery_t *out) |
| 90 | { | 90 | { |
| 91 | cJSON *root = cJSON_Parse(json_str); | 91 | return tollgate_core_client_parse_discovery(json_str, out); |
| 92 | if (!root) return false; | ||
| 93 | |||
| 94 | cJSON *kind = cJSON_GetObjectItemCaseSensitive(root, "kind"); | ||
| 95 | if (!kind || !cJSON_IsNumber(kind) || kind->valueint != 10021) { | ||
| 96 | cJSON_Delete(root); | ||
| 97 | return false; | ||
| 98 | } | ||
| 99 | |||
| 100 | memset(out, 0, sizeof(tollgate_discovery_t)); | ||
| 101 | out->is_tollgate = true; | ||
| 102 | |||
| 103 | cJSON *tags = cJSON_GetObjectItemCaseSensitive(root, "tags"); | ||
| 104 | if (!tags || !cJSON_IsArray(tags)) { | ||
| 105 | cJSON_Delete(root); | ||
| 106 | return true; | ||
| 107 | } | ||
| 108 | |||
| 109 | int tag_count = cJSON_GetArraySize(tags); | ||
| 110 | for (int i = 0; i < tag_count; i++) { | ||
| 111 | cJSON *tag = cJSON_GetArrayItem(tags, i); | ||
| 112 | if (!tag || !cJSON_IsArray(tag)) continue; | ||
| 113 | |||
| 114 | int tag_len = cJSON_GetArraySize(tag); | ||
| 115 | if (tag_len < 2) continue; | ||
| 116 | |||
| 117 | cJSON *tag_name = cJSON_GetArrayItem(tag, 0); | ||
| 118 | if (!tag_name || !cJSON_IsString(tag_name)) continue; | ||
| 119 | |||
| 120 | if (strcmp(tag_name->valuestring, "metric") == 0) { | ||
| 121 | cJSON *val = cJSON_GetArrayItem(tag, 1); | ||
| 122 | if (val && cJSON_IsString(val)) { | ||
| 123 | strncpy(out->metric, val->valuestring, sizeof(out->metric) - 1); | ||
| 124 | } | ||
| 125 | } else if (strcmp(tag_name->valuestring, "step_size") == 0) { | ||
| 126 | cJSON *val = cJSON_GetArrayItem(tag, 1); | ||
| 127 | if (val && cJSON_IsString(val)) { | ||
| 128 | out->step_size_ms = atoi(val->valuestring); | ||
| 129 | } | ||
| 130 | } else if (strcmp(tag_name->valuestring, "price_per_step") == 0 && tag_len >= 4) { | ||
| 131 | cJSON *payment_type = cJSON_GetArrayItem(tag, 2); | ||
| 132 | |||
| 133 | if (cJSON_IsString(payment_type) && strcmp(payment_type->valuestring, "mining") == 0 && tag_len >= 5) { | ||
| 134 | out->mining_available = true; | ||
| 135 | cJSON *port_val = cJSON_GetArrayItem(tag, 3); | ||
| 136 | if (port_val && cJSON_IsString(port_val)) { | ||
| 137 | out->mining_port = (uint16_t)atoi(port_val->valuestring); | ||
| 138 | } | ||
| 139 | } else { | ||
| 140 | cJSON *amount = cJSON_GetArrayItem(tag, 2); | ||
| 141 | cJSON *mint = cJSON_GetArrayItem(tag, 4); | ||
| 142 | |||
| 143 | if (amount && cJSON_IsString(amount)) { | ||
| 144 | out->price_per_step = atoi(amount->valuestring); | ||
| 145 | } | ||
| 146 | if (mint && cJSON_IsString(mint)) { | ||
| 147 | strncpy(out->mint_url, mint->valuestring, sizeof(out->mint_url) - 1); | ||
| 148 | } | ||
| 149 | } | ||
| 150 | } | ||
| 151 | } | ||
| 152 | |||
| 153 | cJSON_Delete(root); | ||
| 154 | return true; | ||
| 155 | } | 92 | } |
| 156 | 93 | ||
| 157 | static bool parse_session_response(const char *json_str, int64_t *allotment_ms_out) | 94 | static bool parse_session_response(const char *json_str, int64_t *allotment_ms_out) |
| 158 | { | 95 | { |
| 159 | cJSON *root = cJSON_Parse(json_str); | 96 | return tollgate_core_client_parse_session(json_str, allotment_ms_out); |
| 160 | if (!root) return false; | ||
| 161 | |||
| 162 | cJSON *kind = cJSON_GetObjectItemCaseSensitive(root, "kind"); | ||
| 163 | if (!kind || !cJSON_IsNumber(kind)) { | ||
| 164 | cJSON_Delete(root); | ||
| 165 | return false; | ||
| 166 | } | ||
| 167 | |||
| 168 | if (kind->valueint != 1022) { | ||
| 169 | cJSON_Delete(root); | ||
| 170 | return false; | ||
| 171 | } | ||
| 172 | |||
| 173 | cJSON *tags = cJSON_GetObjectItemCaseSensitive(root, "tags"); | ||
| 174 | if (tags && cJSON_IsArray(tags)) { | ||
| 175 | int tag_count = cJSON_GetArraySize(tags); | ||
| 176 | for (int i = 0; i < tag_count; i++) { | ||
| 177 | cJSON *tag = cJSON_GetArrayItem(tags, i); | ||
| 178 | if (!tag || !cJSON_IsArray(tag)) continue; | ||
| 179 | cJSON *tag_name = cJSON_GetArrayItem(tag, 0); | ||
| 180 | if (tag_name && cJSON_IsString(tag_name) && strcmp(tag_name->valuestring, "allotment") == 0) { | ||
| 181 | cJSON *val = cJSON_GetArrayItem(tag, 1); | ||
| 182 | if (val && cJSON_IsString(val)) { | ||
| 183 | *allotment_ms_out = atoll(val->valuestring); | ||
| 184 | } | ||
| 185 | } | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | cJSON_Delete(root); | ||
| 190 | return true; | ||
| 191 | } | 97 | } |
| 192 | 98 | ||
| 193 | static bool parse_usage_response(const char *resp, int64_t *remaining_out, int64_t *total_out) | 99 | static bool parse_usage_response(const char *resp, int64_t *remaining_out, int64_t *total_out) |
| 194 | { | 100 | { |
| 195 | char remaining_str[32] = {0}; | 101 | return tollgate_core_client_parse_usage(resp, remaining_out, total_out); |
| 196 | char total_str[32] = {0}; | ||
| 197 | const char *slash = strchr(resp, '/'); | ||
| 198 | if (!slash) return false; | ||
| 199 | |||
| 200 | size_t rlen = slash - resp; | ||
| 201 | if (rlen >= sizeof(remaining_str)) return false; | ||
| 202 | memcpy(remaining_str, resp, rlen); | ||
| 203 | strncpy(total_str, slash + 1, sizeof(total_str) - 1); | ||
| 204 | |||
| 205 | *remaining_out = atoll(remaining_str); | ||
| 206 | *total_out = atoll(total_str); | ||
| 207 | return true; | ||
| 208 | } | 102 | } |
| 209 | 103 | ||
| 210 | esp_err_t tollgate_client_detect(const char *gw_ip, tollgate_discovery_t *discovery) | 104 | esp_err_t tollgate_client_detect(const char *gw_ip, tollgate_discovery_t *discovery) |
| @@ -358,13 +252,11 @@ esp_err_t tollgate_client_on_sta_connected(const char *gw_ip_str) | |||
| 358 | const market_t *mkt = market_get(); | 252 | const market_t *mkt = market_get(); |
| 359 | int cheapest = market_find_cheapest(); | 253 | int cheapest = market_find_cheapest(); |
| 360 | if (cheapest >= 0 && mkt->entries[cheapest].valid && mkt->entries[cheapest].ssid[0] != '\0') { | 254 | if (cheapest >= 0 && mkt->entries[cheapest].valid && mkt->entries[cheapest].ssid[0] != '\0') { |
| 361 | uint32_t upstream_step = s_discovery.step_size_ms > 0 ? s_discovery.step_size_ms : 1; | 255 | int upstream_eff = tollgate_core_client_calc_price_per_min(s_discovery.price_per_step, s_discovery.step_size_ms); |
| 362 | uint32_t upstream_eff = (uint32_t)s_discovery.price_per_step * 60000 / upstream_step; | 256 | int cheap_eff = tollgate_core_client_calc_price_per_min(mkt->entries[cheapest].price_per_step, mkt->entries[cheapest].step_size); |
| 363 | uint32_t cheap_step = mkt->entries[cheapest].step_size > 0 ? mkt->entries[cheapest].step_size : 1; | ||
| 364 | uint32_t cheap_eff = (uint32_t)mkt->entries[cheapest].price_per_step * 60000 / cheap_step; | ||
| 365 | if (cheap_eff < upstream_eff) { | 257 | if (cheap_eff < upstream_eff) { |
| 366 | ESP_LOGW(TAG, "CHEAPER TOLLGATE AVAILABLE: %s at %lu sats/min vs upstream %lu sats/min", | 258 | ESP_LOGW(TAG, "CHEAPER TOLLGATE AVAILABLE: %s at %d sats/min vs upstream %d sats/min", |
| 367 | mkt->entries[cheapest].ssid, (unsigned long)cheap_eff, (unsigned long)upstream_eff); | 259 | mkt->entries[cheapest].ssid, cheap_eff, upstream_eff); |
| 368 | } | 260 | } |
| 369 | } | 261 | } |
| 370 | return ESP_OK; | 262 | return ESP_OK; |
| @@ -430,32 +322,29 @@ void tollgate_client_tick(void) | |||
| 430 | 322 | ||
| 431 | const tollgate_config_t *cfg = tollgate_config_get(); | 323 | const tollgate_config_t *cfg = tollgate_config_get(); |
| 432 | int threshold_pct = cfg->client_renewal_threshold_pct; | 324 | int threshold_pct = cfg->client_renewal_threshold_pct; |
| 433 | if (threshold_pct <= 0) threshold_pct = 20; | 325 | |
| 434 | 326 | if (tollgate_core_client_should_renew(s_remaining_ms, s_allotment_ms, threshold_pct)) { | |
| 435 | if (s_allotment_ms > 0 && s_remaining_ms >= 0) { | 327 | ESP_LOGI(TAG, "session nearing expiry (%lld/%lldms, %d%%), renewing", |
| 436 | int remaining_pct = (int)((s_remaining_ms * 100) / s_allotment_ms); | 328 | (long long)s_remaining_ms, (long long)s_allotment_ms, |
| 437 | if (remaining_pct <= threshold_pct) { | 329 | (int)((s_remaining_ms * 100) / s_allotment_ms)); |
| 438 | ESP_LOGI(TAG, "session nearing expiry (%lld/%lldms, %d%%), renewing", | 330 | |
| 439 | (long long)s_remaining_ms, (long long)s_allotment_ms, remaining_pct); | 331 | s_state = TG_CLIENT_RENEWING; |
| 440 | 332 | int steps = cfg->client_steps_to_buy; | |
| 441 | s_state = TG_CLIENT_RENEWING; | 333 | if (steps <= 0) steps = 1; |
| 442 | int steps = cfg->client_steps_to_buy; | 334 | int amount_sats = steps * s_discovery.price_per_step; |
| 443 | if (steps <= 0) steps = 1; | 335 | |
| 444 | int amount_sats = steps * s_discovery.price_per_step; | 336 | int64_t allotment = 0; |
| 445 | 337 | err = tollgate_client_pay(s_gw_ip, amount_sats, &allotment); | |
| 446 | int64_t allotment = 0; | 338 | if (err == ESP_OK) { |
| 447 | err = tollgate_client_pay(s_gw_ip, amount_sats, &allotment); | 339 | s_allotment_ms = allotment; |
| 448 | if (err == ESP_OK) { | 340 | s_remaining_ms = allotment; |
| 449 | s_allotment_ms = allotment; | 341 | s_last_pay_time_ms = get_time_ms(); |
| 450 | s_remaining_ms = allotment; | 342 | s_state = TG_CLIENT_PAID; |
| 451 | s_last_pay_time_ms = get_time_ms(); | 343 | ESP_LOGI(TAG, "renewal succeeded: %lldms", (long long)allotment); |
| 452 | s_state = TG_CLIENT_PAID; | 344 | } else { |
| 453 | ESP_LOGI(TAG, "renewal succeeded: %lldms", (long long)allotment); | 345 | s_state = TG_CLIENT_ERROR; |
| 454 | } else { | 346 | s_last_pay_time_ms = get_time_ms(); |
| 455 | s_state = TG_CLIENT_ERROR; | 347 | ESP_LOGE(TAG, "renewal payment failed"); |
| 456 | s_last_pay_time_ms = get_time_ms(); | ||
| 457 | ESP_LOGE(TAG, "renewal payment failed"); | ||
| 458 | } | ||
| 459 | } | 348 | } |
| 460 | } | 349 | } |
| 461 | } | 350 | } |
diff --git a/main/tollgate_client.h b/main/tollgate_client.h index ccee624..463cf63 100644 --- a/main/tollgate_client.h +++ b/main/tollgate_client.h | |||
| @@ -1,36 +1,11 @@ | |||
| 1 | #ifndef TOLLGATE_CLIENT_H | 1 | #ifndef TOLLGATE_CLIENT_H |
| 2 | #define TOLLGATE_CLIENT_H | 2 | #define TOLLGATE_CLIENT_H |
| 3 | 3 | ||
| 4 | #include "tollgate_core_client.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 TG_CLIENT_MAX_GW_IP_LEN 16 | ||
| 9 | #define TG_CLIENT_MAX_MINT_URL 256 | ||
| 10 | #define TG_CLIENT_MAX_METRIC 32 | ||
| 11 | |||
| 12 | typedef enum { | ||
| 13 | TG_CLIENT_IDLE, | ||
| 14 | TG_CLIENT_DETECTING, | ||
| 15 | TG_CLIENT_NO_TOLLGATE, | ||
| 16 | TG_CLIENT_NEEDS_PAY, | ||
| 17 | TG_CLIENT_PAYING, | ||
| 18 | TG_CLIENT_PAID, | ||
| 19 | TG_CLIENT_RENEWING, | ||
| 20 | TG_CLIENT_MINING, | ||
| 21 | TG_CLIENT_ERROR | ||
| 22 | } tollgate_client_state_t; | ||
| 23 | |||
| 24 | typedef struct { | ||
| 25 | bool is_tollgate; | ||
| 26 | int price_per_step; | ||
| 27 | int step_size_ms; | ||
| 28 | char mint_url[TG_CLIENT_MAX_MINT_URL]; | ||
| 29 | char metric[TG_CLIENT_MAX_METRIC]; | ||
| 30 | bool mining_available; | ||
| 31 | uint16_t mining_port; | ||
| 32 | } tollgate_discovery_t; | ||
| 33 | |||
| 34 | esp_err_t tollgate_client_init(void); | 9 | esp_err_t tollgate_client_init(void); |
| 35 | 10 | ||
| 36 | esp_err_t tollgate_client_on_sta_connected(const char *gw_ip_str); | 11 | esp_err_t tollgate_client_on_sta_connected(const char *gw_ip_str); |
diff --git a/tests/unit/Makefile b/tests/unit/Makefile index 9347516..591ec0f 100644 --- a/tests/unit/Makefile +++ b/tests/unit/Makefile | |||
| @@ -26,7 +26,7 @@ LDFLAGS := -lmbedcrypto -lcjson -lm | |||
| 26 | 26 | ||
| 27 | SECP256K1_OBJ := secp256k1.o precomputed_ecmult.o precomputed_ecmult_gen.o | 27 | SECP256K1_OBJ := secp256k1.o precomputed_ecmult.o precomputed_ecmult_gen.o |
| 28 | 28 | ||
| 29 | TESTS := test_geohash test_identity test_nostr_event test_cashu test_session test_tollgate_client test_lnurl_pay test_lightning_payout test_mcp_handler test_nip04 test_cvm_server test_beacon_price test_market test_mint_health test_mining_payment test_stratum_proxy test_session_payment_method test_tollgate_client_mining test_firewall_sandbox test_relay_selector test_relay_validator | 29 | TESTS := test_geohash test_identity test_nostr_event test_cashu test_session test_tollgate_client test_lnurl_pay test_lightning_payout test_mcp_handler test_nip04 test_cvm_server test_beacon_price test_market test_mint_health test_mining_payment test_stratum_proxy test_session_payment_method test_tollgate_client_mining test_firewall_sandbox test_relay_selector test_relay_validator test_portal test_stratum_client test_mint_health_core test_client_core |
| 30 | 30 | ||
| 31 | .PHONY: all test clean $(TESTS) | 31 | .PHONY: all test clean $(TESTS) |
| 32 | 32 | ||
| @@ -69,8 +69,8 @@ TG_CORE_OBJS_CFLAGS := -include stubs/mint_health.h -include stubs/esp_netif.h - | |||
| 69 | test_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 | 69 | test_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 | ||
| 72 | test_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 | 72 | test_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 $(TG_CORE_SRC)/tollgate_core_client.c |
| 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) | 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 $(TG_CORE_SRC)/tollgate_core_client.c -o $@ $(LDFLAGS) |
| 74 | 74 | ||
| 75 | test_lnurl_pay: test_lnurl_pay.c | 75 | test_lnurl_pay: test_lnurl_pay.c |
| 76 | $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) | 76 | $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) |
| @@ -87,8 +87,8 @@ test_nip04: test_nip04.c $(REPO_ROOT)/main/nip04.c $(SECP256K1_OBJ) | |||
| 87 | test_cvm_server: test_cvm_server.c | 87 | test_cvm_server: test_cvm_server.c |
| 88 | $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) | 88 | $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) |
| 89 | 89 | ||
| 90 | test_mint_health: test_mint_health.c $(REPO_ROOT)/main/mint_health.c | 90 | test_mint_health: test_mint_health.c $(REPO_ROOT)/main/mint_health.c $(TG_CORE_SRC)/tollgate_core_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 -I $(TG_CORE_SRC) -I $(TG_CORE_INC) $^ -o $@ $(LDFLAGS) |
| 92 | 92 | ||
| 93 | test_beacon_price: test_beacon_price.c $(REPO_ROOT)/main/beacon_price.c $(TG_CORE_SRC)/tollgate_core_beacon.c | 93 | test_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 $(TG_CORE_SRC)/tollgate_core_beacon.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) |
| @@ -105,8 +105,8 @@ test_stratum_proxy: test_stratum_proxy.c $(REPO_ROOT)/main/stratum_proxy.c $(TG_ | |||
| 105 | test_session_payment_method: test_session_payment_method.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 | 105 | test_session_payment_method: test_session_payment_method.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 |
| 106 | $(CC) $(CFLAGS) $(TG_CORE_OBJS_CFLAGS) $^ -o $@ $(LDFLAGS) | 106 | $(CC) $(CFLAGS) $(TG_CORE_OBJS_CFLAGS) $^ -o $@ $(LDFLAGS) |
| 107 | 107 | ||
| 108 | test_tollgate_client_mining: test_tollgate_client_mining.c | 108 | test_tollgate_client_mining: test_tollgate_client_mining.c $(TG_CORE_SRC)/tollgate_core_client.c |
| 109 | $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) | 109 | $(CC) $(CFLAGS) -I $(REPO_ROOT)/main $< $(TG_CORE_SRC)/tollgate_core_client.c -o $@ $(LDFLAGS) |
| 110 | 110 | ||
| 111 | test_firewall_sandbox: test_firewall_sandbox.c $(TG_CORE_SRC)/tollgate_core_firewall.c $(TG_CORE_SRC)/tollgate_core.c $(TG_CORE_SRC)/tollgate_core_session.c $(TG_CORE_SRC)/tollgate_core_cashu.c $(TG_CORE_SRC)/tollgate_core_mining.c $(TG_CORE_SRC)/tollgate_core_dns.c $(TG_CORE_SRC)/tollgate_core_stratum_proxy.c | 111 | test_firewall_sandbox: test_firewall_sandbox.c $(TG_CORE_SRC)/tollgate_core_firewall.c $(TG_CORE_SRC)/tollgate_core.c $(TG_CORE_SRC)/tollgate_core_session.c $(TG_CORE_SRC)/tollgate_core_cashu.c $(TG_CORE_SRC)/tollgate_core_mining.c $(TG_CORE_SRC)/tollgate_core_dns.c $(TG_CORE_SRC)/tollgate_core_stratum_proxy.c |
| 112 | $(CC) $(CFLAGS) -include stubs/dns_server.h $(TG_CORE_OBJS_CFLAGS) $^ -o $@ $(LDFLAGS) | 112 | $(CC) $(CFLAGS) -include stubs/dns_server.h $(TG_CORE_OBJS_CFLAGS) $^ -o $@ $(LDFLAGS) |
| @@ -117,5 +117,17 @@ test_relay_selector: test_relay_selector.c | |||
| 117 | test_relay_validator: test_relay_validator.c $(REPO_ROOT)/main/nostr_event.c $(REPO_ROOT)/main/identity.c $(REPO_ROOT)/components/wisp_relay/relay_validator.c $(SECP256K1_OBJ) | 117 | test_relay_validator: test_relay_validator.c $(REPO_ROOT)/main/nostr_event.c $(REPO_ROOT)/main/identity.c $(REPO_ROOT)/components/wisp_relay/relay_validator.c $(SECP256K1_OBJ) |
| 118 | $(CC) $(CFLAGS) -I $(SECP256K1_PRIV_INC) -I $(REPO_ROOT)/main -I $(REPO_ROOT)/components/wisp_relay $< $(REPO_ROOT)/main/nostr_event.c $(REPO_ROOT)/main/identity.c $(REPO_ROOT)/components/wisp_relay/relay_validator.c $(SECP256K1_OBJ) -o $@ $(LDFLAGS) | 118 | $(CC) $(CFLAGS) -I $(SECP256K1_PRIV_INC) -I $(REPO_ROOT)/main -I $(REPO_ROOT)/components/wisp_relay $< $(REPO_ROOT)/main/nostr_event.c $(REPO_ROOT)/main/identity.c $(REPO_ROOT)/components/wisp_relay/relay_validator.c $(SECP256K1_OBJ) -o $@ $(LDFLAGS) |
| 119 | 119 | ||
| 120 | test_portal: test_portal.c $(TG_CORE_SRC)/tollgate_core_portal.c | ||
| 121 | $(CC) $(CFLAGS) $(TG_CORE_OBJS_CFLAGS) $^ -o $@ $(LDFLAGS) | ||
| 122 | |||
| 123 | test_stratum_client: test_stratum_client.c $(TG_CORE_SRC)/tollgate_core_stratum_client.c | ||
| 124 | $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) | ||
| 125 | |||
| 126 | test_mint_health_core: test_mint_health_core.c $(TG_CORE_SRC)/tollgate_core_mint_health.c | ||
| 127 | $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) | ||
| 128 | |||
| 129 | test_client_core: test_client_core.c $(TG_CORE_SRC)/tollgate_core_client.c | ||
| 130 | $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) | ||
| 131 | |||
| 120 | clean: | 132 | clean: |
| 121 | rm -f $(TESTS) $(SECP256K1_OBJ) | 133 | rm -f $(TESTS) $(SECP256K1_OBJ) |
diff --git a/tests/unit/test_client_core.c b/tests/unit/test_client_core.c new file mode 100644 index 0000000..7cc6cc7 --- /dev/null +++ b/tests/unit/test_client_core.c | |||
| @@ -0,0 +1,189 @@ | |||
| 1 | #include "test_framework.h" | ||
| 2 | #include "../../components/tollgate_core/src/tollgate_core_client.h" | ||
| 3 | #include <cjson/cJSON.h> | ||
| 4 | #include <string.h> | ||
| 5 | #include <stdio.h> | ||
| 6 | |||
| 7 | int main(void) | ||
| 8 | { | ||
| 9 | printf("=== test_client_core ===\n"); | ||
| 10 | |||
| 11 | printf("\n--- parse_discovery valid ---\n"); | ||
| 12 | { | ||
| 13 | const char *json = "{\"kind\":10021,\"content\":\"discovery\"," | ||
| 14 | "\"tags\":[" | ||
| 15 | "[\"metric\",\"milliseconds\"]," | ||
| 16 | "[\"step_size\",\"60000\"]," | ||
| 17 | "[\"price_per_step\",\"21\",\"21\",\"sha256\",\"https://test.mint\"]" | ||
| 18 | "]}"; | ||
| 19 | tollgate_discovery_t disc = {0}; | ||
| 20 | bool ok = tollgate_core_client_parse_discovery(json, &disc); | ||
| 21 | ASSERT(ok, "parse_discovery returns true"); | ||
| 22 | ASSERT(disc.is_tollgate, "is_tollgate set"); | ||
| 23 | ASSERT_EQ_INT(21, disc.price_per_step, "price_per_step"); | ||
| 24 | ASSERT_EQ_INT(60000, disc.step_size_ms, "step_size_ms"); | ||
| 25 | ASSERT(strcmp(disc.metric, "milliseconds") == 0, "metric"); | ||
| 26 | ASSERT(strcmp(disc.mint_url, "https://test.mint") == 0, "mint_url"); | ||
| 27 | } | ||
| 28 | |||
| 29 | printf("\n--- parse_discovery wrong kind ---\n"); | ||
| 30 | { | ||
| 31 | const char *json = "{\"kind\":9999}"; | ||
| 32 | tollgate_discovery_t disc = {0}; | ||
| 33 | bool ok = tollgate_core_client_parse_discovery(json, &disc); | ||
| 34 | ASSERT(!ok, "wrong kind returns false"); | ||
| 35 | } | ||
| 36 | |||
| 37 | printf("\n--- parse_discovery mining payment ---\n"); | ||
| 38 | { | ||
| 39 | const char *json = "{\"kind\":10021,\"content\":\"\"," | ||
| 40 | "\"tags\":[" | ||
| 41 | "[\"metric\",\"milliseconds\"]," | ||
| 42 | "[\"step_size\",\"60000\"]," | ||
| 43 | "[\"price_per_step\",\"0\",\"mining\",\"3333\",\"extra\"]" | ||
| 44 | "]}"; | ||
| 45 | tollgate_discovery_t disc = {0}; | ||
| 46 | bool ok = tollgate_core_client_parse_discovery(json, &disc); | ||
| 47 | ASSERT(ok, "parse_discovery mining returns true"); | ||
| 48 | ASSERT(disc.mining_available, "mining_available set"); | ||
| 49 | ASSERT_EQ_INT(3333, (int)disc.mining_port, "mining_port"); | ||
| 50 | } | ||
| 51 | |||
| 52 | printf("\n--- parse_discovery empty tags ---\n"); | ||
| 53 | { | ||
| 54 | const char *json = "{\"kind\":10021,\"tags\":[]}"; | ||
| 55 | tollgate_discovery_t disc = {0}; | ||
| 56 | bool ok = tollgate_core_client_parse_discovery(json, &disc); | ||
| 57 | ASSERT(ok, "empty tags returns true"); | ||
| 58 | ASSERT(disc.is_tollgate, "is_tollgate set"); | ||
| 59 | } | ||
| 60 | |||
| 61 | printf("\n--- parse_discovery no tags ---\n"); | ||
| 62 | { | ||
| 63 | const char *json = "{\"kind\":10021}"; | ||
| 64 | tollgate_discovery_t disc = {0}; | ||
| 65 | bool ok = tollgate_core_client_parse_discovery(json, &disc); | ||
| 66 | ASSERT(ok, "no tags returns true"); | ||
| 67 | } | ||
| 68 | |||
| 69 | printf("\n--- parse_discovery invalid JSON ---\n"); | ||
| 70 | { | ||
| 71 | tollgate_discovery_t disc = {0}; | ||
| 72 | bool ok = tollgate_core_client_parse_discovery("not json", &disc); | ||
| 73 | ASSERT(!ok, "invalid JSON returns false"); | ||
| 74 | } | ||
| 75 | |||
| 76 | printf("\n--- parse_session valid ---\n"); | ||
| 77 | { | ||
| 78 | const char *json = "{\"kind\":1022,\"content\":\"session\"," | ||
| 79 | "\"tags\":[[\"allotment\",\"120000\"]]}"; | ||
| 80 | int64_t allotment = 0; | ||
| 81 | bool ok = tollgate_core_client_parse_session(json, &allotment); | ||
| 82 | ASSERT(ok, "parse_session returns true"); | ||
| 83 | ASSERT_EQ_UINT64(120000, (unsigned long long)allotment, "allotment_ms"); | ||
| 84 | } | ||
| 85 | |||
| 86 | printf("\n--- parse_session wrong kind ---\n"); | ||
| 87 | { | ||
| 88 | const char *json = "{\"kind\":10021}"; | ||
| 89 | int64_t allotment = 0; | ||
| 90 | bool ok = tollgate_core_client_parse_session(json, &allotment); | ||
| 91 | ASSERT(!ok, "wrong kind returns false"); | ||
| 92 | } | ||
| 93 | |||
| 94 | printf("\n--- parse_session no allotment tag ---\n"); | ||
| 95 | { | ||
| 96 | const char *json = "{\"kind\":1022,\"tags\":[]}"; | ||
| 97 | int64_t allotment = 999; | ||
| 98 | bool ok = tollgate_core_client_parse_session(json, &allotment); | ||
| 99 | ASSERT(ok, "returns true"); | ||
| 100 | ASSERT_EQ_UINT64(999, (unsigned long long)allotment, "allotment unchanged when no tag"); | ||
| 101 | } | ||
| 102 | |||
| 103 | printf("\n--- parse_usage valid ---\n"); | ||
| 104 | { | ||
| 105 | int64_t remaining = 0, total = 0; | ||
| 106 | bool ok = tollgate_core_client_parse_usage("45000/60000", &remaining, &total); | ||
| 107 | ASSERT(ok, "parse_usage returns true"); | ||
| 108 | ASSERT_EQ_UINT64(45000, (unsigned long long)remaining, "remaining"); | ||
| 109 | ASSERT_EQ_UINT64(60000, (unsigned long long)total, "total"); | ||
| 110 | } | ||
| 111 | |||
| 112 | printf("\n--- parse_usage no slash ---\n"); | ||
| 113 | { | ||
| 114 | int64_t remaining = 0, total = 0; | ||
| 115 | bool ok = tollgate_core_client_parse_usage("45000", &remaining, &total); | ||
| 116 | ASSERT(!ok, "no slash returns false"); | ||
| 117 | } | ||
| 118 | |||
| 119 | printf("\n--- parse_usage zero values ---\n"); | ||
| 120 | { | ||
| 121 | int64_t remaining = 0, total = 0; | ||
| 122 | bool ok = tollgate_core_client_parse_usage("0/0", &remaining, &total); | ||
| 123 | ASSERT(ok, "parse_usage zero values"); | ||
| 124 | ASSERT_EQ_UINT64(0, (unsigned long long)remaining, "remaining is 0"); | ||
| 125 | ASSERT_EQ_UINT64(0, (unsigned long long)total, "total is 0"); | ||
| 126 | } | ||
| 127 | |||
| 128 | printf("\n--- should_renew ---\n"); | ||
| 129 | { | ||
| 130 | ASSERT(tollgate_core_client_should_renew(1000, 60000, 20), | ||
| 131 | "1.6% remaining → should renew"); | ||
| 132 | ASSERT(tollgate_core_client_should_renew(0, 60000, 20), | ||
| 133 | "0% remaining → should renew"); | ||
| 134 | ASSERT(!tollgate_core_client_should_renew(30000, 60000, 20), | ||
| 135 | "50% remaining → should not renew"); | ||
| 136 | ASSERT(!tollgate_core_client_should_renew(60000, 60000, 20), | ||
| 137 | "100% remaining → should not renew"); | ||
| 138 | ASSERT(tollgate_core_client_should_renew(11999, 60000, 20), | ||
| 139 | "19.99% → should renew"); | ||
| 140 | ASSERT(tollgate_core_client_should_renew(12000, 60000, 20), | ||
| 141 | "20% exactly at threshold → should renew"); | ||
| 142 | } | ||
| 143 | |||
| 144 | printf("\n--- should_renew default threshold ---\n"); | ||
| 145 | { | ||
| 146 | ASSERT(tollgate_core_client_should_renew(1000, 60000, 0), | ||
| 147 | "threshold=0 defaults to 20%"); | ||
| 148 | ASSERT(tollgate_core_client_should_renew(1000, 60000, -1), | ||
| 149 | "negative threshold defaults to 20%"); | ||
| 150 | } | ||
| 151 | |||
| 152 | printf("\n--- should_renew edge cases ---\n"); | ||
| 153 | { | ||
| 154 | ASSERT(!tollgate_core_client_should_renew(-1, 60000, 20), | ||
| 155 | "negative remaining → false"); | ||
| 156 | ASSERT(!tollgate_core_client_should_renew(1000, 0, 20), | ||
| 157 | "zero allotment → false"); | ||
| 158 | } | ||
| 159 | |||
| 160 | printf("\n--- calc_price_per_min ---\n"); | ||
| 161 | { | ||
| 162 | ASSERT_EQ_INT(21, tollgate_core_client_calc_price_per_min(21, 60000), | ||
| 163 | "21 sats/60s = 21 sats/min"); | ||
| 164 | ASSERT_EQ_INT(42, tollgate_core_client_calc_price_per_min(21, 30000), | ||
| 165 | "21 sats/30s = 42 sats/min"); | ||
| 166 | ASSERT_EQ_INT(21000, tollgate_core_client_calc_price_per_min(21, 60), | ||
| 167 | "21 sats/60ms effective"); | ||
| 168 | } | ||
| 169 | |||
| 170 | printf("\n--- calc_price_per_min zero step ---\n"); | ||
| 171 | { | ||
| 172 | int result = tollgate_core_client_calc_price_per_min(21, 0); | ||
| 173 | ASSERT(result > 0, "zero step_size_ms doesn't crash"); | ||
| 174 | } | ||
| 175 | |||
| 176 | printf("\n--- calc_steps ---\n"); | ||
| 177 | { | ||
| 178 | ASSERT_EQ_INT(21, tollgate_core_client_calc_steps(1, 21, 21), | ||
| 179 | "1 step * 21 = 21"); | ||
| 180 | ASSERT_EQ_INT(42, tollgate_core_client_calc_steps(2, 21, 21), | ||
| 181 | "2 steps * 21 = 42"); | ||
| 182 | ASSERT_EQ_INT(21, tollgate_core_client_calc_steps(0, 21, 21), | ||
| 183 | "0 steps defaults to 1"); | ||
| 184 | ASSERT_EQ_INT(21, tollgate_core_client_calc_steps(-1, 21, 21), | ||
| 185 | "negative steps defaults to 1"); | ||
| 186 | } | ||
| 187 | |||
| 188 | TEST_SUMMARY(); | ||
| 189 | } | ||
diff --git a/tests/unit/test_mint_health_core.c b/tests/unit/test_mint_health_core.c new file mode 100644 index 0000000..9727edb --- /dev/null +++ b/tests/unit/test_mint_health_core.c | |||
| @@ -0,0 +1,130 @@ | |||
| 1 | #include "test_framework.h" | ||
| 2 | #include "../../components/tollgate_core/src/tollgate_core_mint_health.h" | ||
| 3 | #include <string.h> | ||
| 4 | #include <stdio.h> | ||
| 5 | |||
| 6 | int main(void) | ||
| 7 | { | ||
| 8 | printf("=== test_mint_health_core ===\n"); | ||
| 9 | |||
| 10 | printf("\n--- update: probe success ---\n"); | ||
| 11 | { | ||
| 12 | tollgate_mint_health_t state = {0}; | ||
| 13 | strncpy(state.mints[0].url, "https://test.mint", sizeof(state.mints[0].url) - 1); | ||
| 14 | state.count = 1; | ||
| 15 | |||
| 16 | tollgate_core_mint_health_update(&state, 0, true, 200, 0, 1000); | ||
| 17 | ASSERT_EQ_INT(1, state.mints[0].consecutive_successes, "1st success"); | ||
| 18 | ASSERT(!state.mints[0].reachable, "not yet reachable (threshold=3)"); | ||
| 19 | |||
| 20 | tollgate_core_mint_health_update(&state, 0, true, 200, 0, 2000); | ||
| 21 | ASSERT_EQ_INT(2, state.mints[0].consecutive_successes, "2nd success"); | ||
| 22 | ASSERT(!state.mints[0].reachable, "still not reachable"); | ||
| 23 | |||
| 24 | tollgate_core_mint_health_update(&state, 0, true, 200, 0, 3000); | ||
| 25 | ASSERT_EQ_INT(3, state.mints[0].consecutive_successes, "3rd success"); | ||
| 26 | ASSERT(state.mints[0].reachable, "now reachable after threshold"); | ||
| 27 | } | ||
| 28 | |||
| 29 | printf("\n--- update: failure resets ---\n"); | ||
| 30 | { | ||
| 31 | tollgate_mint_health_t state = {0}; | ||
| 32 | strncpy(state.mints[0].url, "https://test.mint", sizeof(state.mints[0].url) - 1); | ||
| 33 | state.count = 1; | ||
| 34 | state.mints[0].reachable = true; | ||
| 35 | state.mints[0].consecutive_successes = 5; | ||
| 36 | |||
| 37 | tollgate_core_mint_health_update(&state, 0, false, 0, -1, 1000); | ||
| 38 | ASSERT(!state.mints[0].reachable, "failure marks unreachable"); | ||
| 39 | ASSERT_EQ_INT(0, state.mints[0].consecutive_successes, "successes reset to 0"); | ||
| 40 | ASSERT_EQ_INT(-1, state.mints[0].last_err, "error code recorded"); | ||
| 41 | } | ||
| 42 | |||
| 43 | printf("\n--- update_initial: success ---\n"); | ||
| 44 | { | ||
| 45 | tollgate_mint_health_t state = {0}; | ||
| 46 | strncpy(state.mints[0].url, "https://test.mint", sizeof(state.mints[0].url) - 1); | ||
| 47 | state.count = 1; | ||
| 48 | |||
| 49 | tollgate_core_mint_health_update_initial(&state, 0, true, 200, 0, 1000); | ||
| 50 | ASSERT(state.mints[0].reachable, "immediately reachable on initial success"); | ||
| 51 | ASSERT_EQ_INT(TG_MINT_HEALTH_RECOVERY_THRESHOLD, | ||
| 52 | state.mints[0].consecutive_successes, "successes set to threshold"); | ||
| 53 | } | ||
| 54 | |||
| 55 | printf("\n--- update_initial: failure ---\n"); | ||
| 56 | { | ||
| 57 | tollgate_mint_health_t state = {0}; | ||
| 58 | strncpy(state.mints[0].url, "https://test.mint", sizeof(state.mints[0].url) - 1); | ||
| 59 | state.count = 1; | ||
| 60 | |||
| 61 | tollgate_core_mint_health_update_initial(&state, 0, false, 0, -2, 1000); | ||
| 62 | ASSERT(!state.mints[0].reachable, "not reachable on initial failure"); | ||
| 63 | ASSERT_EQ_INT(0, state.mints[0].consecutive_successes, "successes is 0"); | ||
| 64 | } | ||
| 65 | |||
| 66 | printf("\n--- is_reachable ---\n"); | ||
| 67 | { | ||
| 68 | tollgate_mint_health_t state = {0}; | ||
| 69 | strncpy(state.mints[0].url, "https://test.mint", sizeof(state.mints[0].url) - 1); | ||
| 70 | state.count = 1; | ||
| 71 | state.mints[0].reachable = true; | ||
| 72 | |||
| 73 | ASSERT(tollgate_core_mint_health_is_reachable(&state, "https://test.mint"), | ||
| 74 | "exact URL match"); | ||
| 75 | ASSERT(tollgate_core_mint_health_is_reachable(&state, "https://test.mint/v1/info"), | ||
| 76 | "substring match"); | ||
| 77 | ASSERT(!tollgate_core_mint_health_is_reachable(&state, "https://other.mint"), | ||
| 78 | "no match for other URL"); | ||
| 79 | ASSERT(!tollgate_core_mint_health_is_reachable(NULL, "https://test.mint"), | ||
| 80 | "NULL state"); | ||
| 81 | ASSERT(!tollgate_core_mint_health_is_reachable(&state, NULL), | ||
| 82 | "NULL url"); | ||
| 83 | } | ||
| 84 | |||
| 85 | printf("\n--- mark_unreachable ---\n"); | ||
| 86 | { | ||
| 87 | tollgate_mint_health_t state = {0}; | ||
| 88 | strncpy(state.mints[0].url, "https://test.mint", sizeof(state.mints[0].url) - 1); | ||
| 89 | state.count = 1; | ||
| 90 | state.mints[0].reachable = true; | ||
| 91 | state.mints[0].consecutive_successes = 5; | ||
| 92 | |||
| 93 | tollgate_core_mint_health_mark_unreachable(&state, "https://test.mint"); | ||
| 94 | ASSERT(!state.mints[0].reachable, "marked unreachable"); | ||
| 95 | ASSERT_EQ_INT(0, state.mints[0].consecutive_successes, "successes cleared"); | ||
| 96 | |||
| 97 | tollgate_core_mint_health_mark_unreachable(&state, "https://test.mint"); | ||
| 98 | ASSERT(!state.mints[0].reachable, "idempotent"); | ||
| 99 | } | ||
| 100 | |||
| 101 | printf("\n--- count_reachable ---\n"); | ||
| 102 | { | ||
| 103 | tollgate_mint_health_t state = {0}; | ||
| 104 | state.count = 3; | ||
| 105 | strncpy(state.mints[0].url, "https://a.mint", sizeof(state.mints[0].url) - 1); | ||
| 106 | strncpy(state.mints[1].url, "https://b.mint", sizeof(state.mints[1].url) - 1); | ||
| 107 | strncpy(state.mints[2].url, "https://c.mint", sizeof(state.mints[2].url) - 1); | ||
| 108 | state.mints[0].reachable = true; | ||
| 109 | state.mints[1].reachable = false; | ||
| 110 | state.mints[2].reachable = true; | ||
| 111 | |||
| 112 | ASSERT_EQ_INT(2, tollgate_core_mint_health_count_reachable(&state), | ||
| 113 | "2 out of 3 reachable"); | ||
| 114 | ASSERT_EQ_INT(0, tollgate_core_mint_health_count_reachable(NULL), | ||
| 115 | "NULL returns 0"); | ||
| 116 | } | ||
| 117 | |||
| 118 | printf("\n--- update with invalid index ---\n"); | ||
| 119 | { | ||
| 120 | tollgate_mint_health_t state = {0}; | ||
| 121 | state.count = 1; | ||
| 122 | |||
| 123 | tollgate_core_mint_health_update(&state, -1, true, 200, 0, 1000); | ||
| 124 | tollgate_core_mint_health_update(&state, 5, true, 200, 0, 1000); | ||
| 125 | tollgate_core_mint_health_update(NULL, 0, true, 200, 0, 1000); | ||
| 126 | ASSERT(true, "no crash on invalid inputs"); | ||
| 127 | } | ||
| 128 | |||
| 129 | TEST_SUMMARY(); | ||
| 130 | } | ||
diff --git a/tests/unit/test_portal.c b/tests/unit/test_portal.c new file mode 100644 index 0000000..575921e --- /dev/null +++ b/tests/unit/test_portal.c | |||
| @@ -0,0 +1,153 @@ | |||
| 1 | #include "test_framework.h" | ||
| 2 | #include "../../components/tollgate_core/src/tollgate_core_portal.h" | ||
| 3 | #include "../../components/tollgate_core/src/tollgate_core_session.h" | ||
| 4 | #include <string.h> | ||
| 5 | #include <stdio.h> | ||
| 6 | #include <stdlib.h> | ||
| 7 | |||
| 8 | int main(void) | ||
| 9 | { | ||
| 10 | printf("=== test_portal ===\n"); | ||
| 11 | |||
| 12 | printf("\n--- template_replace single key ---\n"); | ||
| 13 | { | ||
| 14 | char *result = tollgate_core_portal_template_replace( | ||
| 15 | "Hello __NAME__!", "__NAME__", "World"); | ||
| 16 | ASSERT(result != NULL, "result not NULL"); | ||
| 17 | ASSERT(strcmp(result, "Hello World!") == 0, "single substitution works"); | ||
| 18 | free(result); | ||
| 19 | } | ||
| 20 | |||
| 21 | printf("\n--- template_replace no match ---\n"); | ||
| 22 | { | ||
| 23 | char *result = tollgate_core_portal_template_replace( | ||
| 24 | "Hello World!", "__NAME__", "Test"); | ||
| 25 | ASSERT(result != NULL, "result not NULL"); | ||
| 26 | ASSERT(strcmp(result, "Hello World!") == 0, "no match returns original"); | ||
| 27 | free(result); | ||
| 28 | } | ||
| 29 | |||
| 30 | printf("\n--- template_replace multiple occurrences ---\n"); | ||
| 31 | { | ||
| 32 | char *result = tollgate_core_portal_template_replace( | ||
| 33 | "__IP__:2121 and __IP__:80", "__IP__", "10.0.0.1"); | ||
| 34 | ASSERT(result != NULL, "result not NULL"); | ||
| 35 | ASSERT(strcmp(result, "10.0.0.1:2121 and 10.0.0.1:80") == 0, | ||
| 36 | "multiple occurrences replaced"); | ||
| 37 | free(result); | ||
| 38 | } | ||
| 39 | |||
| 40 | printf("\n--- template_replace key longer than value ---\n"); | ||
| 41 | { | ||
| 42 | char *result = tollgate_core_portal_template_replace( | ||
| 43 | "A __LONGKEY__ B", "__LONGKEY__", "X"); | ||
| 44 | ASSERT(result != NULL, "result not NULL"); | ||
| 45 | ASSERT(strcmp(result, "A X B") == 0, "shrinks correctly"); | ||
| 46 | free(result); | ||
| 47 | } | ||
| 48 | |||
| 49 | printf("\n--- render multi-key substitution ---\n"); | ||
| 50 | { | ||
| 51 | tollgate_portal_sub_t subs[] = { | ||
| 52 | { "__AP_IP__", "10.0.0.1" }, | ||
| 53 | { "__PRICE__", "21" }, | ||
| 54 | { "__MINT_URL__", "https://test.mint" }, | ||
| 55 | }; | ||
| 56 | char *result = tollgate_core_portal_render( | ||
| 57 | "IP=__AP_IP__ price=__PRICE__ mint=__MINT_URL__", subs, 3); | ||
| 58 | ASSERT(result != NULL, "result not NULL"); | ||
| 59 | ASSERT(strcmp(result, "IP=10.0.0.1 price=21 mint=https://test.mint") == 0, | ||
| 60 | "multi-key render works"); | ||
| 61 | free(result); | ||
| 62 | } | ||
| 63 | |||
| 64 | printf("\n--- render with no matches ---\n"); | ||
| 65 | { | ||
| 66 | tollgate_portal_sub_t subs[] = { | ||
| 67 | { "__AP_IP__", "10.0.0.1" }, | ||
| 68 | }; | ||
| 69 | char *result = tollgate_core_portal_render("no placeholders", subs, 1); | ||
| 70 | ASSERT(result != NULL, "result not NULL"); | ||
| 71 | ASSERT(strcmp(result, "no placeholders") == 0, "no matches returns original"); | ||
| 72 | free(result); | ||
| 73 | } | ||
| 74 | |||
| 75 | printf("\n--- calc_usage time metric ---\n"); | ||
| 76 | { | ||
| 77 | tg_session_t session = {0}; | ||
| 78 | session.active = true; | ||
| 79 | session.allotment_ms = 60000; | ||
| 80 | session.start_time_ms = 10000; | ||
| 81 | |||
| 82 | int64_t remaining = 0, total = 0; | ||
| 83 | bool ok = tollgate_core_portal_calc_usage(&session, "milliseconds", 30000, &remaining, &total); | ||
| 84 | ASSERT(ok, "calc_usage returns true for active session"); | ||
| 85 | ASSERT_EQ_UINT64(40000, (unsigned long long)remaining, "remaining = 60000 - 20000"); | ||
| 86 | ASSERT_EQ_UINT64(60000, (unsigned long long)total, "total = allotment_ms"); | ||
| 87 | } | ||
| 88 | |||
| 89 | printf("\n--- calc_usage bytes metric ---\n"); | ||
| 90 | { | ||
| 91 | tg_session_t session = {0}; | ||
| 92 | session.active = true; | ||
| 93 | session.allotment_bytes = 1000000; | ||
| 94 | session.bytes_consumed = 300000; | ||
| 95 | |||
| 96 | int64_t remaining = 0, total = 0; | ||
| 97 | bool ok = tollgate_core_portal_calc_usage(&session, "bytes", 0, &remaining, &total); | ||
| 98 | ASSERT(ok, "calc_usage returns true for bytes"); | ||
| 99 | ASSERT_EQ_UINT64(700000, (unsigned long long)remaining, "remaining = 1000000 - 300000"); | ||
| 100 | ASSERT_EQ_UINT64(1000000, (unsigned long long)total, "total = allotment_bytes"); | ||
| 101 | } | ||
| 102 | |||
| 103 | printf("\n--- calc_usage expired time ---\n"); | ||
| 104 | { | ||
| 105 | tg_session_t session = {0}; | ||
| 106 | session.active = true; | ||
| 107 | session.allotment_ms = 60000; | ||
| 108 | session.start_time_ms = 0; | ||
| 109 | |||
| 110 | int64_t remaining = 0, total = 0; | ||
| 111 | tollgate_core_portal_calc_usage(&session, "milliseconds", 120000, &remaining, &total); | ||
| 112 | ASSERT_EQ_UINT64(0, (unsigned long long)remaining, "remaining clamped to 0 when expired"); | ||
| 113 | } | ||
| 114 | |||
| 115 | printf("\n--- calc_usage inactive session ---\n"); | ||
| 116 | { | ||
| 117 | tg_session_t session = {0}; | ||
| 118 | session.active = false; | ||
| 119 | |||
| 120 | int64_t remaining = 0, total = 0; | ||
| 121 | bool ok = tollgate_core_portal_calc_usage(&session, "milliseconds", 0, &remaining, &total); | ||
| 122 | ASSERT(!ok, "inactive session returns false"); | ||
| 123 | } | ||
| 124 | |||
| 125 | printf("\n--- format_usage ---\n"); | ||
| 126 | { | ||
| 127 | tg_session_t session = {0}; | ||
| 128 | session.active = true; | ||
| 129 | session.allotment_ms = 60000; | ||
| 130 | session.start_time_ms = 10000; | ||
| 131 | |||
| 132 | char buf[64]; | ||
| 133 | int len = tollgate_core_portal_format_usage(&session, "milliseconds", 30000, buf, sizeof(buf)); | ||
| 134 | ASSERT(len > 0, "format_usage returns positive length"); | ||
| 135 | ASSERT(strstr(buf, "/") != NULL, "contains separator"); | ||
| 136 | ASSERT(strstr(buf, "60000") != NULL, "contains total"); | ||
| 137 | } | ||
| 138 | |||
| 139 | printf("\n--- is_captive_uri ---\n"); | ||
| 140 | { | ||
| 141 | ASSERT(tollgate_core_portal_is_captive_uri("/generate_204"), "generate_204 is captive"); | ||
| 142 | ASSERT(tollgate_core_portal_is_captive_uri("/hotspot-detect.html"), "hotspot-detect is captive"); | ||
| 143 | ASSERT(tollgate_core_portal_is_captive_uri("/success.txt"), "success.txt is captive"); | ||
| 144 | ASSERT(tollgate_core_portal_is_captive_uri("/ncsi.txt"), "ncsi.txt is captive"); | ||
| 145 | ASSERT(tollgate_core_portal_is_captive_uri("/connecttest.txt"), "connecttest is captive"); | ||
| 146 | ASSERT(tollgate_core_portal_is_captive_uri("/wpad.dat"), "wpad.dat is captive"); | ||
| 147 | ASSERT(!tollgate_core_portal_is_captive_uri("/"), "root is not captive"); | ||
| 148 | ASSERT(!tollgate_core_portal_is_captive_uri("/setup"), "setup is not captive"); | ||
| 149 | ASSERT(!tollgate_core_portal_is_captive_uri(NULL), "NULL is not captive"); | ||
| 150 | } | ||
| 151 | |||
| 152 | TEST_SUMMARY(); | ||
| 153 | } | ||
diff --git a/tests/unit/test_stratum_client.c b/tests/unit/test_stratum_client.c new file mode 100644 index 0000000..302fcba --- /dev/null +++ b/tests/unit/test_stratum_client.c | |||
| @@ -0,0 +1,125 @@ | |||
| 1 | #include "test_framework.h" | ||
| 2 | #include "../../components/tollgate_core/src/tollgate_core_stratum_client.h" | ||
| 3 | #include <cjson/cJSON.h> | ||
| 4 | #include <string.h> | ||
| 5 | #include <stdio.h> | ||
| 6 | #include <stdlib.h> | ||
| 7 | |||
| 8 | int main(void) | ||
| 9 | { | ||
| 10 | printf("=== test_stratum_client ===\n"); | ||
| 11 | |||
| 12 | printf("\n--- hex_to_bytes ---\n"); | ||
| 13 | { | ||
| 14 | const char *hex = "0a1b2c3d"; | ||
| 15 | uint8_t out[4] = {0}; | ||
| 16 | tollgate_core_stratum_hex_to_bytes(hex, out, 4); | ||
| 17 | ASSERT_EQ_INT(0x0a, (int)out[0], "byte 0"); | ||
| 18 | ASSERT_EQ_INT(0x1b, (int)out[1], "byte 1"); | ||
| 19 | ASSERT_EQ_INT(0x2c, (int)out[2], "byte 2"); | ||
| 20 | ASSERT_EQ_INT(0x3d, (int)out[3], "byte 3"); | ||
| 21 | } | ||
| 22 | |||
| 23 | printf("\n--- hex_to_bytes zeros ---\n"); | ||
| 24 | { | ||
| 25 | const char *hex = "00000000"; | ||
| 26 | uint8_t out[4] = {0xff, 0xff, 0xff, 0xff}; | ||
| 27 | tollgate_core_stratum_hex_to_bytes(hex, out, 4); | ||
| 28 | ASSERT_EQ_INT(0, (int)out[0], "zero byte 0"); | ||
| 29 | ASSERT_EQ_INT(0, (int)out[1], "zero byte 1"); | ||
| 30 | } | ||
| 31 | |||
| 32 | printf("\n--- parse_notify valid ---\n"); | ||
| 33 | { | ||
| 34 | cJSON *params = cJSON_Parse("[\"123\",\"0000000000000000000000000000000000000000000000000000000000000000\"," | ||
| 35 | "\"\"" | ||
| 36 | ",\"\"" | ||
| 37 | ",\"\"" | ||
| 38 | ",\"20000000\"" | ||
| 39 | ",\"1705e3c4\"" | ||
| 40 | ",\"65a3b2c1\"]"); | ||
| 41 | tollgate_stratum_job_t job = {0}; | ||
| 42 | uint32_t nbits = 0; | ||
| 43 | bool ok = tollgate_core_stratum_parse_notify(params, &job, &nbits); | ||
| 44 | ASSERT(ok, "parse_notify returns true"); | ||
| 45 | ASSERT_EQ_INT(123, (int)job.job_id, "job_id parsed"); | ||
| 46 | ASSERT(job.valid, "job is valid"); | ||
| 47 | ASSERT_EQ_INT(0x20000000, (int)job.version, "version parsed"); | ||
| 48 | ASSERT_EQ_INT(0x1705e3c4, (int)job.nbits, "nbits parsed"); | ||
| 49 | ASSERT_EQ_INT(0x65a3b2c1, (int)job.ntime, "ntime parsed"); | ||
| 50 | ASSERT_EQ_INT(0x1705e3c4, (int)nbits, "out_nbits set"); | ||
| 51 | ASSERT_EQ_INT(32, job.target_len, "target_len is 32"); | ||
| 52 | cJSON_Delete(params); | ||
| 53 | } | ||
| 54 | |||
| 55 | printf("\n--- parse_notify too few params ---\n"); | ||
| 56 | { | ||
| 57 | cJSON *params = cJSON_Parse("[\"123\",\"abcd\"]"); | ||
| 58 | tollgate_stratum_job_t job = {0}; | ||
| 59 | bool ok = tollgate_core_stratum_parse_notify(params, &job, NULL); | ||
| 60 | ASSERT(!ok, "parse_notify returns false for insufficient params"); | ||
| 61 | cJSON_Delete(params); | ||
| 62 | } | ||
| 63 | |||
| 64 | printf("\n--- parse_notify NULL ---\n"); | ||
| 65 | { | ||
| 66 | tollgate_stratum_job_t job = {0}; | ||
| 67 | bool ok = tollgate_core_stratum_parse_notify(NULL, &job, NULL); | ||
| 68 | ASSERT(!ok, "parse_notify returns false for NULL"); | ||
| 69 | } | ||
| 70 | |||
| 71 | printf("\n--- parse_difficulty valid ---\n"); | ||
| 72 | { | ||
| 73 | cJSON *params = cJSON_Parse("[512]"); | ||
| 74 | uint64_t diff = 0; | ||
| 75 | bool ok = tollgate_core_stratum_parse_difficulty(params, &diff); | ||
| 76 | ASSERT(ok, "parse_difficulty returns true"); | ||
| 77 | ASSERT_EQ_UINT64(512, (unsigned long long)diff, "difficulty parsed"); | ||
| 78 | cJSON_Delete(params); | ||
| 79 | } | ||
| 80 | |||
| 81 | printf("\n--- parse_difficulty invalid ---\n"); | ||
| 82 | { | ||
| 83 | cJSON *params = cJSON_Parse("[]"); | ||
| 84 | uint64_t diff = 0; | ||
| 85 | bool ok = tollgate_core_stratum_parse_difficulty(params, &diff); | ||
| 86 | ASSERT(!ok, "parse_difficulty returns false for empty array"); | ||
| 87 | cJSON_Delete(params); | ||
| 88 | } | ||
| 89 | |||
| 90 | printf("\n--- build_subscribe ---\n"); | ||
| 91 | { | ||
| 92 | char buf[256]; | ||
| 93 | int len = tollgate_core_stratum_build_subscribe(buf, sizeof(buf), 1); | ||
| 94 | ASSERT(len > 0, "build_subscribe returns positive length"); | ||
| 95 | ASSERT(strstr(buf, "mining.subscribe") != NULL, "contains method"); | ||
| 96 | ASSERT(strstr(buf, "\"id\":1") != NULL, "contains id"); | ||
| 97 | ASSERT(strstr(buf, "TollGate/1.0") != NULL, "contains user agent"); | ||
| 98 | ASSERT(buf[len - 1] == '\n', "ends with newline"); | ||
| 99 | } | ||
| 100 | |||
| 101 | printf("\n--- build_authorize ---\n"); | ||
| 102 | { | ||
| 103 | char buf[512]; | ||
| 104 | int len = tollgate_core_stratum_build_authorize(buf, sizeof(buf), 2, "user1", "pass1"); | ||
| 105 | ASSERT(len > 0, "build_authorize returns positive length"); | ||
| 106 | ASSERT(strstr(buf, "mining.authorize") != NULL, "contains method"); | ||
| 107 | ASSERT(strstr(buf, "user1") != NULL, "contains user"); | ||
| 108 | ASSERT(strstr(buf, "pass1") != NULL, "contains pass"); | ||
| 109 | } | ||
| 110 | |||
| 111 | printf("\n--- build_submit ---\n"); | ||
| 112 | { | ||
| 113 | char buf[512]; | ||
| 114 | int len = tollgate_core_stratum_build_submit(buf, sizeof(buf), 3, "worker1", | ||
| 115 | 100, 0x65a3b2c1, 0x12345678, 0x20000000); | ||
| 116 | ASSERT(len > 0, "build_submit returns positive length"); | ||
| 117 | ASSERT(strstr(buf, "mining.submit") != NULL, "contains method"); | ||
| 118 | ASSERT(strstr(buf, "worker1") != NULL, "contains worker"); | ||
| 119 | ASSERT(strstr(buf, "100") != NULL, "contains job_id"); | ||
| 120 | ASSERT(strstr(buf, "65a3b2c1") != NULL, "contains ntime"); | ||
| 121 | ASSERT(strstr(buf, "12345678") != NULL, "contains nonce"); | ||
| 122 | } | ||
| 123 | |||
| 124 | TEST_SUMMARY(); | ||
| 125 | } | ||