diff options
Diffstat (limited to 'main/cvm_server.c')
| -rw-r--r-- | main/cvm_server.c | 238 |
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 | |||
| 15 | static const char *TAG = "cvm_server"; | ||
| 16 | |||
| 17 | static bool g_running = false; | ||
| 18 | static TaskHandle_t g_task = NULL; | ||
| 19 | |||
| 20 | static const char *DEFAULT_RELAY = "wss://relay.damus.io"; | ||
| 21 | |||
| 22 | static 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 | |||
| 31 | static 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 | |||
| 65 | static 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 | |||
| 76 | static 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 | |||
| 85 | static 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 | |||
| 156 | static 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 | |||
| 188 | static 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 | |||
| 218 | esp_err_t cvm_server_init(void) | ||
| 219 | { | ||
| 220 | ESP_LOGI(TAG, "CVM server initialized"); | ||
| 221 | return ESP_OK; | ||
| 222 | } | ||
| 223 | |||
| 224 | void 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 | |||
| 231 | void 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 | } | ||