upleb.uk

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

summaryrefslogtreecommitdiff
path: root/components/wisp_relay/handlers.c
diff options
context:
space:
mode:
Diffstat (limited to 'components/wisp_relay/handlers.c')
-rw-r--r--components/wisp_relay/handlers.c203
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
14static const char *TAG = "handlers";
15
16int 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
98static 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
156void 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
198int 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}