upleb.uk

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

summaryrefslogtreecommitdiff
path: root/main/cvm_server.c
diff options
context:
space:
mode:
Diffstat (limited to 'main/cvm_server.c')
-rw-r--r--main/cvm_server.c238
1 files changed, 238 insertions, 0 deletions
diff --git a/main/cvm_server.c b/main/cvm_server.c
new file mode 100644
index 0000000..5addd88
--- /dev/null
+++ b/main/cvm_server.c
@@ -0,0 +1,238 @@
1#include "cvm_server.h"
2#include "mcp_handler.h"
3#include "nip04.h"
4#include "identity.h"
5#include "config.h"
6#include "nucula_wallet.h"
7#include "cJSON.h"
8#include "esp_log.h"
9#include "esp_http_client.h"
10#include "freertos/FreeRTOS.h"
11#include "freertos/task.h"
12#include <string.h>
13#include <stdio.h>
14
15static const char *TAG = "cvm_server";
16
17static bool g_running = false;
18static TaskHandle_t g_task = NULL;
19
20static const char *DEFAULT_RELAY = "wss://relay.damus.io";
21
22static char *fetch_relays(void)
23{
24 const tollgate_config_t *cfg = tollgate_config_get();
25 if (cfg && cfg->cvm_relays[0]) {
26 return cfg->cvm_relays;
27 }
28 return (char *)DEFAULT_RELAY;
29}
30
31static char *http_get(const char *url, int timeout_ms)
32{
33 char *buf = malloc(8192);
34 if (!buf) return NULL;
35 int total = 0;
36
37 esp_http_client_config_t config = {
38 .url = url,
39 .method = HTTP_METHOD_GET,
40 .timeout_ms = timeout_ms,
41 };
42 esp_http_client_handle_t client = esp_http_client_init(&config);
43 if (!client) { free(buf); return NULL; }
44
45 esp_err_t err = esp_http_client_open(client, 0);
46 if (err != ESP_OK) {
47 esp_http_client_cleanup(client);
48 free(buf);
49 return NULL;
50 }
51
52 int content_length = esp_http_client_fetch_headers(client);
53 int max_read = content_length > 0 ? content_length : 8191;
54
55 while (total < max_read) {
56 int n = esp_http_client_read(client, buf + total, max_read - total);
57 if (n <= 0) break;
58 total += n;
59 }
60 buf[total] = '\0';
61 esp_http_client_cleanup(client);
62 return buf;
63}
64
65static cJSON *build_filter(const char *npub)
66{
67 cJSON *filter = cJSON_CreateObject();
68 cJSON *kinds = cJSON_CreateArray();
69 cJSON_AddItemToArray(kinds, cJSON_CreateNumber(4));
70 cJSON_AddItemToObject(filter, "kinds", kinds);
71 cJSON_AddStringToObject(filter, "#p", npub);
72 cJSON_AddNumberToObject(filter, "limit", 10);
73 return filter;
74}
75
76static cJSON *build_subscription(const char *npub)
77{
78 cJSON *sub = cJSON_CreateArray();
79 cJSON_AddItemToArray(sub, cJSON_CreateString("REQ"));
80 cJSON_AddItemToArray(sub, cJSON_CreateString("cvm_sub_01"));
81 cJSON_AddItemToArray(sub, build_filter(npub));
82 return sub;
83}
84
85static void process_dm(const char *sender_pubkey, const char *encrypted_content)
86{
87 const tollgate_identity_t *id = identity_get();
88 if (!id || !id->initialized) {
89 ESP_LOGE(TAG, "Identity not initialized");
90 return;
91 }
92
93 uint8_t sender_pk[64];
94 for (int i = 0; i < 64; i++) {
95 char hex[3] = { sender_pubkey[i*2], sender_pubkey[i*2+1], 0 };
96 sender_pk[i] = (uint8_t)strtol(hex, NULL, 16);
97 }
98
99 char plaintext[2048];
100 int pt_len = nip04_decrypt(id->nsec, sender_pk, encrypted_content, plaintext, sizeof(plaintext));
101 if (pt_len < 0) {
102 ESP_LOGE(TAG, "Failed to decrypt DM from %.8s", sender_pubkey);
103 return;
104 }
105
106 ESP_LOGI(TAG, "Decrypted DM from %.8s: %s", sender_pubkey, plaintext);
107
108 cJSON *msg = cJSON_Parse(plaintext);
109 if (!msg) {
110 ESP_LOGE(TAG, "Invalid JSON in DM");
111 return;
112 }
113
114 cJSON *method = cJSON_GetObjectItem(msg, "method");
115 cJSON *params = cJSON_GetObjectItem(msg, "params");
116 if (!method || !cJSON_IsString(method)) {
117 cJSON_Delete(msg);
118 ESP_LOGE(TAG, "Missing 'method' in CVM request");
119 return;
120 }
121
122 mcp_request_t req = {0};
123 req.tool = mcp_parse_tool(method->valuestring);
124 strncpy(req.method, method->valuestring, sizeof(req.method) - 1);
125 if (params && cJSON_IsString(params)) {
126 strncpy(req.params_json, params->valuestring, sizeof(req.params_json) - 1);
127 } else if (params) {
128 char *pjson = cJSON_PrintUnformatted(params);
129 strncpy(req.params_json, pjson, sizeof(req.params_json) - 1);
130 cJSON_free(pjson);
131 }
132
133 mcp_response_t resp = mcp_dispatch(&req);
134 cJSON_Delete(msg);
135
136 cJSON *response_msg = cJSON_CreateObject();
137 if (resp.success) {
138 cJSON_AddStringToObject(response_msg, "status", "ok");
139 cJSON_AddItemToObject(response_msg, "result", cJSON_Parse(resp.result_json));
140 } else {
141 cJSON_AddStringToObject(response_msg, "status", "error");
142 cJSON_AddStringToObject(response_msg, "error", resp.error);
143 }
144
145 char *response_str = cJSON_PrintUnformatted(response_msg);
146 cJSON_Delete(response_msg);
147
148 uint8_t response_ct[4096];
149 size_t ct_len = 0;
150 nip04_encrypt(id->nsec, sender_pk, response_str, response_ct, &ct_len);
151 free(response_str);
152
153 ESP_LOGI(TAG, "CVM response prepared (%zu bytes encrypted), would send to %.8s", ct_len, sender_pubkey);
154}
155
156static void parse_nostr_events(const char *data)
157{
158 cJSON *arr = cJSON_Parse(data);
159 if (!arr || !cJSON_IsArray(arr)) {
160 if (arr) cJSON_Delete(arr);
161 return;
162 }
163
164 cJSON *item = NULL;
165 cJSON_ArrayForEach(item, arr) {
166 if (!cJSON_IsArray(item)) continue;
167 int arr_size = cJSON_GetArraySize(item);
168 if (arr_size < 3) continue;
169
170 cJSON *cmd = cJSON_GetArrayItem(item, 0);
171 if (!cmd || !cJSON_IsString(cmd) || strcmp(cmd->valuestring, "EVENT") != 0) continue;
172
173 cJSON *event = cJSON_GetArrayItem(item, 2);
174 if (!event) continue;
175
176 cJSON *kind = cJSON_GetObjectItem(event, "kind");
177 if (!kind || kind->valueint != 4) continue;
178
179 cJSON *pubkey = cJSON_GetObjectItem(event, "pubkey");
180 cJSON *content = cJSON_GetObjectItem(event, "content");
181 if (pubkey && content) {
182 process_dm(pubkey->valuestring, content->valuestring);
183 }
184 }
185 cJSON_Delete(arr);
186}
187
188static void cvm_task(void *arg)
189{
190 const tollgate_identity_t *id = identity_get();
191 if (!id || !id->initialized) {
192 ESP_LOGE(TAG, "Cannot start: identity not initialized");
193 vTaskDelete(NULL);
194 return;
195 }
196
197 char *relays = fetch_relays();
198 ESP_LOGI(TAG, "CVM server started, relays: %s", relays);
199
200 while (g_running) {
201 ESP_LOGI(TAG, "Polling for DMs...");
202
203 cJSON *sub = build_subscription(id->npub_hex);
204 char *sub_json = cJSON_PrintUnformatted(sub);
205 cJSON_Delete(sub);
206
207 char url[256];
208 snprintf(url, sizeof(url), "%s/cvm_poll", relays);
209 free(sub_json);
210
211 vTaskDelay(pdMS_TO_TICKS(30000));
212 }
213
214 ESP_LOGI(TAG, "CVM server stopped");
215 vTaskDelete(NULL);
216}
217
218esp_err_t cvm_server_init(void)
219{
220 ESP_LOGI(TAG, "CVM server initialized");
221 return ESP_OK;
222}
223
224void cvm_server_start(void)
225{
226 if (g_running) return;
227 g_running = true;
228 xTaskCreate(cvm_task, "cvm_server", 8192, NULL, 5, &g_task);
229}
230
231void cvm_server_stop(void)
232{
233 g_running = false;
234 if (g_task) {
235 vTaskDelay(pdMS_TO_TICKS(500));
236 g_task = NULL;
237 }
238}