diff options
Diffstat (limited to 'components/wisp_relay/handlers.c')
| -rw-r--r-- | components/wisp_relay/handlers.c | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/components/wisp_relay/handlers.c b/components/wisp_relay/handlers.c new file mode 100644 index 0000000..2164725 --- /dev/null +++ b/components/wisp_relay/handlers.c | |||
| @@ -0,0 +1,203 @@ | |||
| 1 | #include "handlers.h" | ||
| 2 | #include "router.h" | ||
| 3 | #include "storage_engine.h" | ||
| 4 | #include "sub_manager.h" | ||
| 5 | #include "relay_validator.h" | ||
| 6 | #include "broadcaster.h" | ||
| 7 | #include "deletion.h" | ||
| 8 | #include "rate_limiter.h" | ||
| 9 | #include "relay_types.h" | ||
| 10 | #include "cJSON.h" | ||
| 11 | #include "esp_log.h" | ||
| 12 | #include <string.h> | ||
| 13 | |||
| 14 | static const char *TAG = "handlers"; | ||
| 15 | |||
| 16 | int handle_event(relay_ctx_t *ctx, int conn_fd, const char *event_json, size_t event_len) | ||
| 17 | { | ||
| 18 | if (!ctx || !event_json) return -1; | ||
| 19 | |||
| 20 | if (ctx->rate_limiter) { | ||
| 21 | if (!rate_limiter_check(ctx->rate_limiter, conn_fd, RATE_TYPE_EVENT)) { | ||
| 22 | router_send_ok(ctx, conn_fd, "", false, "rate limited"); | ||
| 23 | return -1; | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | cJSON *obj = cJSON_ParseWithLength(event_json, event_len); | ||
| 28 | if (!obj) { | ||
| 29 | router_send_ok(ctx, conn_fd, "", false, "invalid JSON"); | ||
| 30 | return -1; | ||
| 31 | } | ||
| 32 | |||
| 33 | cJSON *id_item = cJSON_GetObjectItem(obj, "id"); | ||
| 34 | cJSON *pubkey_item = cJSON_GetObjectItem(obj, "pubkey"); | ||
| 35 | cJSON *kind_item = cJSON_GetObjectItem(obj, "kind"); | ||
| 36 | cJSON *ca_item = cJSON_GetObjectItem(obj, "created_at"); | ||
| 37 | |||
| 38 | if (!id_item || !pubkey_item || !kind_item || !ca_item) { | ||
| 39 | cJSON_Delete(obj); | ||
| 40 | router_send_ok(ctx, conn_fd, "", false, "missing required fields"); | ||
| 41 | return -1; | ||
| 42 | } | ||
| 43 | |||
| 44 | const char *id_hex = id_item->valuestring; | ||
| 45 | const char *pubkey_hex = pubkey_item->valuestring; | ||
| 46 | int kind = kind_item->valueint; | ||
| 47 | uint64_t created_at = (uint64_t)ca_item->valuedouble; | ||
| 48 | |||
| 49 | if (ctx->config.max_future_sec > 0) { | ||
| 50 | int64_t now = (int64_t)(xTaskGetTickCount() / configTICK_RATE_HZ); | ||
| 51 | if ((int64_t)created_at > now + ctx->config.max_future_sec) { | ||
| 52 | cJSON_Delete(obj); | ||
| 53 | router_send_ok(ctx, conn_fd, id_hex, false, "created_at too far in future"); | ||
| 54 | return -1; | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | uint8_t event_id[32]; | ||
| 59 | if (relay_hex_to_bytes(id_hex, 64, event_id, 32) != 0) { | ||
| 60 | cJSON_Delete(obj); | ||
| 61 | router_send_ok(ctx, conn_fd, "", false, "invalid event id"); | ||
| 62 | return -1; | ||
| 63 | } | ||
| 64 | |||
| 65 | if (storage_event_exists(ctx->storage, event_id)) { | ||
| 66 | cJSON_Delete(obj); | ||
| 67 | router_send_ok(ctx, conn_fd, id_hex, true, "duplicate"); | ||
| 68 | return 0; | ||
| 69 | } | ||
| 70 | |||
| 71 | if (!relay_validator_verify_event(event_json, event_len)) { | ||
| 72 | cJSON_Delete(obj); | ||
| 73 | router_send_ok(ctx, conn_fd, id_hex, false, "invalid signature"); | ||
| 74 | return -1; | ||
| 75 | } | ||
| 76 | |||
| 77 | cJSON_Delete(obj); | ||
| 78 | |||
| 79 | storage_error_t err = storage_save_event_json(ctx->storage, event_json, event_len); | ||
| 80 | if (err != STORAGE_OK) { | ||
| 81 | const char *msg = (err == STORAGE_ERR_FULL) ? "relay full" : | ||
| 82 | (err == STORAGE_ERR_DUPLICATE) ? "duplicate" : "storage error"; | ||
| 83 | router_send_ok(ctx, conn_fd, id_hex, false, msg); | ||
| 84 | return -1; | ||
| 85 | } | ||
| 86 | |||
| 87 | router_send_ok(ctx, conn_fd, id_hex, true, ""); | ||
| 88 | |||
| 89 | if (kind == NOSTR_KIND_DELETION) { | ||
| 90 | deletion_process_json(ctx->storage, event_json, event_len); | ||
| 91 | } | ||
| 92 | |||
| 93 | broadcaster_fanout_json(ctx, event_json, event_len, kind, pubkey_hex, created_at); | ||
| 94 | |||
| 95 | return 0; | ||
| 96 | } | ||
| 97 | |||
| 98 | static void parse_filter_json(const char *json, sub_filter_t *filter) | ||
| 99 | { | ||
| 100 | memset(filter, 0, sizeof(sub_filter_t)); | ||
| 101 | cJSON *obj = cJSON_Parse(json); | ||
| 102 | if (!obj) return; | ||
| 103 | |||
| 104 | cJSON *arr; | ||
| 105 | |||
| 106 | arr = cJSON_GetObjectItem(obj, "ids"); | ||
| 107 | if (arr && cJSON_IsArray(arr)) { | ||
| 108 | filter->ids_count = cJSON_GetArraySize(arr); | ||
| 109 | if (filter->ids_count > SUB_MAX_FILTER_IDS) filter->ids_count = SUB_MAX_FILTER_IDS; | ||
| 110 | for (size_t i = 0; i < filter->ids_count; i++) | ||
| 111 | filter->ids[i] = strdup(cJSON_GetArrayItem(arr, i)->valuestring); | ||
| 112 | } | ||
| 113 | |||
| 114 | arr = cJSON_GetObjectItem(obj, "authors"); | ||
| 115 | if (arr && cJSON_IsArray(arr)) { | ||
| 116 | filter->authors_count = cJSON_GetArraySize(arr); | ||
| 117 | if (filter->authors_count > SUB_MAX_FILTER_AUTHORS) filter->authors_count = SUB_MAX_FILTER_AUTHORS; | ||
| 118 | for (size_t i = 0; i < filter->authors_count; i++) | ||
| 119 | filter->authors[i] = strdup(cJSON_GetArrayItem(arr, i)->valuestring); | ||
| 120 | } | ||
| 121 | |||
| 122 | arr = cJSON_GetObjectItem(obj, "kinds"); | ||
| 123 | if (arr && cJSON_IsArray(arr)) { | ||
| 124 | filter->kinds_count = cJSON_GetArraySize(arr); | ||
| 125 | if (filter->kinds_count > SUB_MAX_FILTER_KINDS) filter->kinds_count = SUB_MAX_FILTER_KINDS; | ||
| 126 | for (size_t i = 0; i < filter->kinds_count; i++) | ||
| 127 | filter->kinds[i] = cJSON_GetArrayItem(arr, i)->valueint; | ||
| 128 | } | ||
| 129 | |||
| 130 | arr = cJSON_GetObjectItem(obj, "#e"); | ||
| 131 | if (arr && cJSON_IsArray(arr)) { | ||
| 132 | filter->e_tags_count = cJSON_GetArraySize(arr); | ||
| 133 | if (filter->e_tags_count > SUB_MAX_FILTER_ETAGS) filter->e_tags_count = SUB_MAX_FILTER_ETAGS; | ||
| 134 | for (size_t i = 0; i < filter->e_tags_count; i++) | ||
| 135 | filter->e_tags[i] = strdup(cJSON_GetArrayItem(arr, i)->valuestring); | ||
| 136 | } | ||
| 137 | |||
| 138 | arr = cJSON_GetObjectItem(obj, "#p"); | ||
| 139 | if (arr && cJSON_IsArray(arr)) { | ||
| 140 | filter->p_tags_count = cJSON_GetArraySize(arr); | ||
| 141 | if (filter->p_tags_count > SUB_MAX_FILTER_PTAGS) filter->p_tags_count = SUB_MAX_FILTER_PTAGS; | ||
| 142 | for (size_t i = 0; i < filter->p_tags_count; i++) | ||
| 143 | filter->p_tags[i] = strdup(cJSON_GetArrayItem(arr, i)->valuestring); | ||
| 144 | } | ||
| 145 | |||
| 146 | cJSON *since = cJSON_GetObjectItem(obj, "since"); | ||
| 147 | if (since) filter->since = (int64_t)since->valuedouble; | ||
| 148 | cJSON *until = cJSON_GetObjectItem(obj, "until"); | ||
| 149 | if (until) filter->until = (int64_t)until->valuedouble; | ||
| 150 | cJSON *limit = cJSON_GetObjectItem(obj, "limit"); | ||
| 151 | if (limit) filter->limit = limit->valueint; | ||
| 152 | |||
| 153 | cJSON_Delete(obj); | ||
| 154 | } | ||
| 155 | |||
| 156 | void handle_req(relay_ctx_t *ctx, int conn_fd, const char *sub_id, const char *filters_json) | ||
| 157 | { | ||
| 158 | if (!ctx || !sub_id) return; | ||
| 159 | |||
| 160 | if (ctx->rate_limiter) { | ||
| 161 | if (!rate_limiter_check(ctx->rate_limiter, conn_fd, RATE_TYPE_REQ)) { | ||
| 162 | router_send_closed(ctx, conn_fd, sub_id, "rate limited"); | ||
| 163 | return; | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | sub_filter_t filter; | ||
| 168 | parse_filter_json(filters_json, &filter); | ||
| 169 | |||
| 170 | int query_kind = -1; | ||
| 171 | const char *query_author = NULL; | ||
| 172 | int query_limit = filter.limit > 0 ? filter.limit : 100; | ||
| 173 | |||
| 174 | if (filter.kinds_count > 0) query_kind = filter.kinds[0]; | ||
| 175 | if (filter.authors_count > 0) query_author = filter.authors[0]; | ||
| 176 | |||
| 177 | char **results = NULL; | ||
| 178 | uint16_t count = 0; | ||
| 179 | storage_query_events_json(ctx->storage, query_kind, query_author, | ||
| 180 | query_limit, &results, &count); | ||
| 181 | |||
| 182 | for (uint16_t i = 0; i < count; i++) { | ||
| 183 | router_send_event(ctx, conn_fd, sub_id, results[i], strlen(results[i])); | ||
| 184 | } | ||
| 185 | storage_free_query_results(results, count); | ||
| 186 | |||
| 187 | router_send_eose(ctx, conn_fd, sub_id); | ||
| 188 | |||
| 189 | sub_manager_add(ctx->sub_manager, conn_fd, sub_id, &filter, 1); | ||
| 190 | |||
| 191 | sub_filter_t *f = &filter; | ||
| 192 | for (size_t i = 0; i < f->ids_count; i++) free(f->ids[i]); | ||
| 193 | for (size_t i = 0; i < f->authors_count; i++) free(f->authors[i]); | ||
| 194 | for (size_t i = 0; i < f->e_tags_count; i++) free(f->e_tags[i]); | ||
| 195 | for (size_t i = 0; i < f->p_tags_count; i++) free(f->p_tags[i]); | ||
| 196 | } | ||
| 197 | |||
| 198 | int handle_close(relay_ctx_t *ctx, int conn_fd, const char *sub_id) | ||
| 199 | { | ||
| 200 | if (!ctx || !sub_id) return -1; | ||
| 201 | sub_manager_remove(ctx->sub_manager, conn_fd, sub_id); | ||
| 202 | return 0; | ||
| 203 | } | ||