upleb.uk

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

summaryrefslogtreecommitdiff
path: root/main/wifistr.c
diff options
context:
space:
mode:
Diffstat (limited to 'main/wifistr.c')
-rw-r--r--main/wifistr.c252
1 files changed, 252 insertions, 0 deletions
diff --git a/main/wifistr.c b/main/wifistr.c
new file mode 100644
index 0000000..bf03b4d
--- /dev/null
+++ b/main/wifistr.c
@@ -0,0 +1,252 @@
1#include "wifistr.h"
2#include "identity.h"
3#include "nostr_event.h"
4#include "config.h"
5#include "esp_log.h"
6#include "esp_tls.h"
7#include "esp_crt_bundle.h"
8#include "cJSON.h"
9#include "freertos/task.h"
10#include "freertos/timers.h"
11#include <string.h>
12#include <stdio.h>
13#include <stdlib.h>
14
15static const char *TAG = "wifistr";
16static TimerHandle_t s_publish_timer = NULL;
17
18static esp_err_t ws_send_to_relay(const char *relay_url, const char *event_json)
19{
20 char host[128] = {0};
21 int port = 443;
22 char path[128] = "/";
23
24 if (strncmp(relay_url, "wss://", 6) != 0) {
25 ESP_LOGW(TAG, "Unsupported relay URL: %s", relay_url);
26 return ESP_ERR_INVALID_ARG;
27 }
28
29 const char *url_start = relay_url + 6;
30 const char *path_ptr = strchr(url_start, '/');
31 if (path_ptr) {
32 size_t host_len = path_ptr - url_start;
33 if (host_len >= sizeof(host)) host_len = sizeof(host) - 1;
34 memcpy(host, url_start, host_len);
35 host[host_len] = '\0';
36 strncpy(path, path_ptr, sizeof(path) - 1);
37 } else {
38 strncpy(host, url_start, sizeof(host) - 1);
39 }
40
41 char *colon = strchr(host, ':');
42 if (colon) {
43 *colon = '\0';
44 port = atoi(colon + 1);
45 }
46
47 ESP_LOGI(TAG, "Connecting to %s:%d%s", host, port, path);
48
49 esp_tls_cfg_t tls_cfg = {
50 .crt_bundle_attach = esp_crt_bundle_attach,
51 };
52
53 esp_tls_t *tls = esp_tls_init();
54 if (!tls) {
55 ESP_LOGE(TAG, "Failed to allocate TLS handle");
56 return ESP_ERR_NO_MEM;
57 }
58
59 int ret = esp_tls_conn_new_sync(host, strlen(host), port, &tls_cfg, tls);
60 if (ret < 0) {
61 ESP_LOGE(TAG, "TLS connect failed to %s", host);
62 esp_tls_conn_destroy(tls);
63 return ESP_FAIL;
64 }
65
66 char upgrade[512];
67 snprintf(upgrade, sizeof(upgrade),
68 "GET %s HTTP/1.1\r\n"
69 "Host: %s\r\n"
70 "Upgrade: websocket\r\n"
71 "Connection: Upgrade\r\n"
72 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
73 "Sec-WebSocket-Version: 13\r\n"
74 "\r\n",
75 path, host);
76
77 int written = esp_tls_conn_write(tls, (const unsigned char *)upgrade, strlen(upgrade));
78 if (written < 0) {
79 ESP_LOGE(TAG, "Failed to send upgrade request");
80 esp_tls_conn_destroy(tls);
81 return ESP_FAIL;
82 }
83
84 char resp[1024];
85 int rlen = esp_tls_conn_read(tls, (unsigned char *)resp, sizeof(resp) - 1);
86 if (rlen <= 0 || !strstr(resp, "101")) {
87 ESP_LOGE(TAG, "WebSocket upgrade failed (read %d bytes)", rlen);
88 esp_tls_conn_destroy(tls);
89 return ESP_FAIL;
90 }
91
92 cJSON *arr = cJSON_CreateArray();
93 cJSON_AddItemToArray(arr, cJSON_CreateString("EVENT"));
94 cJSON_AddItemToArray(arr, cJSON_Parse(event_json));
95 char *msg = cJSON_PrintUnformatted(arr);
96 cJSON_Delete(arr);
97
98 size_t msg_len = strlen(msg);
99 uint8_t ws_header[10];
100 int header_len = 0;
101 ws_header[0] = 0x81;
102 if (msg_len <= 125) {
103 ws_header[1] = (uint8_t)msg_len;
104 header_len = 2;
105 } else if (msg_len <= 65535) {
106 ws_header[1] = 126;
107 ws_header[2] = (uint8_t)((msg_len >> 8) & 0xff);
108 ws_header[3] = (uint8_t)(msg_len & 0xff);
109 header_len = 4;
110 } else {
111 ws_header[1] = 127;
112 for (int i = 0; i < 8; i++)
113 ws_header[2 + i] = (uint8_t)((msg_len >> (56 - i * 8)) & 0xff);
114 header_len = 10;
115 }
116
117 esp_tls_conn_write(tls, ws_header, header_len);
118 esp_tls_conn_write(tls, (const unsigned char *)msg, msg_len);
119
120 free(msg);
121
122 uint8_t resp_buf[256];
123 int resp_len = esp_tls_conn_read(tls, resp_buf, sizeof(resp_buf) - 1);
124 if (resp_len > 0) {
125 resp_buf[resp_len] = '\0';
126 int mask_len = (resp_buf[1] & 0x80) ? 4 : 0;
127 int payload_offset = 2 + mask_len;
128 if (resp_len > payload_offset) {
129 ESP_LOGI(TAG, "Relay response: %.*s", resp_len - payload_offset,
130 (char *)resp_buf + payload_offset);
131 }
132 }
133
134 uint8_t close_frame[2] = {0x88, 0x00};
135 esp_tls_conn_write(tls, close_frame, 2);
136 esp_tls_conn_destroy(tls);
137
138 ESP_LOGI(TAG, "Published to %s", host);
139 return ESP_OK;
140}
141
142static char *build_wifistr_event(void)
143{
144 const tollgate_identity_t *id = identity_get();
145 if (!id || !id->initialized) {
146 ESP_LOGE(TAG, "Identity not initialized");
147 return NULL;
148 }
149
150 const tollgate_config_t *cfg = tollgate_config_get();
151
152 cJSON *tags = cJSON_CreateArray();
153
154 cJSON *d_tag = cJSON_CreateArray();
155 cJSON_AddItemToArray(d_tag, cJSON_CreateString("d"));
156 cJSON_AddItemToArray(d_tag, cJSON_CreateString(id->npub_hex));
157 cJSON_AddItemToArray(tags, d_tag);
158
159 cJSON *ssid_tag = cJSON_CreateArray();
160 cJSON_AddItemToArray(ssid_tag, cJSON_CreateString("ssid"));
161 cJSON_AddItemToArray(ssid_tag, cJSON_CreateString(id->ap_ssid));
162 cJSON_AddItemToArray(tags, ssid_tag);
163
164 cJSON *h_tag = cJSON_CreateArray();
165 cJSON_AddItemToArray(h_tag, cJSON_CreateString("h"));
166 cJSON_AddItemToArray(h_tag, cJSON_CreateString("cashu-testnut"));
167 cJSON_AddItemToArray(tags, h_tag);
168
169 cJSON *sec_tag = cJSON_CreateArray();
170 cJSON_AddItemToArray(sec_tag, cJSON_CreateString("security"));
171 cJSON_AddItemToArray(sec_tag, cJSON_CreateString("open"));
172 cJSON_AddItemToArray(tags, sec_tag);
173
174 cJSON *g_tag = cJSON_CreateArray();
175 cJSON_AddItemToArray(g_tag, cJSON_CreateString("g"));
176 cJSON_AddItemToArray(g_tag, cJSON_CreateString(cfg->nostr_geohash));
177 cJSON_AddItemToArray(tags, g_tag);
178
179 cJSON *c_tag = cJSON_CreateArray();
180 cJSON_AddItemToArray(c_tag, cJSON_CreateString("c"));
181 cJSON_AddItemToArray(c_tag, cJSON_CreateString("cashu"));
182 cJSON_AddItemToArray(tags, c_tag);
183
184 char content[512];
185 snprintf(content, sizeof(content),
186 "TollGate WiFi hotspot: %s | Price: %d sats/%dms | Mint: %s",
187 id->ap_ssid, cfg->price_per_step, cfg->step_size_ms, cfg->mint_url);
188
189 char *tags_str = cJSON_PrintUnformatted(tags);
190 cJSON_Delete(tags);
191
192 nostr_event_t event;
193 nostr_event_init(&event, id->npub_hex, 38787, tags_str, content);
194 nostr_event_sign(&event, id->nsec);
195 free(tags_str);
196
197 char *event_json = malloc(2048);
198 if (!event_json) return NULL;
199
200 esp_err_t ret = nostr_event_to_json(&event, event_json, 2048);
201 if (ret != ESP_OK) {
202 free(event_json);
203 return NULL;
204 }
205
206 return event_json;
207}
208
209esp_err_t wifistr_publish(void)
210{
211 char *event_json = build_wifistr_event();
212 if (!event_json) {
213 ESP_LOGE(TAG, "Failed to build wifistr event");
214 return ESP_FAIL;
215 }
216
217 ESP_LOGI(TAG, "Wifistr event: %s", event_json);
218
219 const tollgate_config_t *cfg = tollgate_config_get();
220 esp_err_t last_err = ESP_FAIL;
221
222 for (int i = 0; i < cfg->nostr_relay_count; i++) {
223 esp_err_t err = ws_send_to_relay(cfg->nostr_relays[i], event_json);
224 if (err == ESP_OK) last_err = ESP_OK;
225 vTaskDelay(pdMS_TO_TICKS(500));
226 }
227
228 free(event_json);
229 return last_err;
230}
231
232static void publish_task(void *pvParameters)
233{
234 wifistr_publish();
235 vTaskDelete(NULL);
236}
237
238static void timer_callback(TimerHandle_t timer)
239{
240 xTaskCreate(publish_task, "wifistr_pub", 16384, NULL, 3, NULL);
241}
242
243void wifistr_start_periodic(int interval_s)
244{
245 if (s_publish_timer) return;
246 s_publish_timer = xTimerCreate("wifistr", pdMS_TO_TICKS(interval_s * 1000),
247 pdTRUE, NULL, timer_callback);
248 if (s_publish_timer) {
249 xTimerStart(s_publish_timer, 0);
250 ESP_LOGI(TAG, "Periodic publish every %ds", interval_s);
251 }
252}