upleb.uk

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

summaryrefslogtreecommitdiff
path: root/components/wisp_relay/rate_limiter.c
diff options
context:
space:
mode:
authorYour Name <you@example.com>2026-05-19 02:31:19 +0530
committerYour Name <you@example.com>2026-05-19 02:32:41 +0530
commit81f2dc52dc42d01c89dff45a5407ec40b8863052 (patch)
tree15018c2438639ca89dc6d33a5144c10d0b1c2af0 /components/wisp_relay/rate_limiter.c
parent75688d55b3c8d13c8c9a50da9668ec408f684cb3 (diff)
feat: local Nostr relay with relay selection, sync, and integration tests
Local Nostr relay (NIP-01) on port 4869 with LittleFS 4MB storage. All events published locally first, then synced to public relays via REQ-diff. Relay selection via NIP-11 HTTP probing with NIP-77 scoring and auto-failover. Components: - wisp_relay: 16-file local relay (ws_server, storage_engine, sub_manager, broadcaster, relay_validator, router, handlers, rate_limiter, nip11, deletion, flash_monitor, relay_types) - esp_littlefs: LittleFS VFS integration (git submodule) - negentropy: for future NIP-77 binary sync (git submodule) New source files: - local_relay.c/h: thin wrapper for relay init/start/publish - relay_selector.c/h: NIP-11 probe + scoring + auto-failover - sync_manager.c/h: REQ-diff sync (primary 30min, fallback 6h) Bug fixes: - config.c: use-after-free (cJSON_Delete before seed_relays/sync parsing) - local_relay: moved init to app_main for boot-time start (not gated on STA IP) Flash layout: 4MB LittleFS partition at 0x500000 for relay_store Test results (Board B, live hardware): - Smoke: ping + HTTP 4869 + NIP-11: PASS - NIP-11 info document: 10/11 PASS - WS pub/sub (connect, REQ/EOSE, EVENT/OK, CLOSE, concurrent): 6/6 PASS - Unit tests (relay_validator + relay_selector): 13/13 PASS Hardware test make targets in physical-router-test-automation/: - make relay-build, relay-flash-b, relay-test-smoke/nip11/pubsub/sync/full
Diffstat (limited to 'components/wisp_relay/rate_limiter.c')
-rw-r--r--components/wisp_relay/rate_limiter.c98
1 files changed, 98 insertions, 0 deletions
diff --git a/components/wisp_relay/rate_limiter.c b/components/wisp_relay/rate_limiter.c
new file mode 100644
index 0000000..7734e03
--- /dev/null
+++ b/components/wisp_relay/rate_limiter.c
@@ -0,0 +1,98 @@
1#include "rate_limiter.h"
2#include "esp_timer.h"
3#include "esp_log.h"
4#include <string.h>
5
6static const char *TAG = "rate_limiter";
7
8void rate_limiter_init(rate_limiter_t *rl, const rate_config_t *config)
9{
10 memset(rl, 0, sizeof(rate_limiter_t));
11 rl->lock = xSemaphoreCreateMutex();
12 if (config) {
13 memcpy(&rl->config, config, sizeof(rate_config_t));
14 } else {
15 rl->config.events_per_minute = 30;
16 rl->config.reqs_per_minute = 60;
17 }
18}
19
20void rate_limiter_destroy(rate_limiter_t *rl)
21{
22 if (!rl) return;
23 if (rl->lock) {
24 vSemaphoreDelete(rl->lock);
25 rl->lock = NULL;
26 }
27}
28
29static rate_bucket_t* get_bucket(rate_limiter_t *rl, int fd)
30{
31 for (int i = 0; i < RATE_LIMITER_MAX_BUCKETS; i++) {
32 if (rl->buckets[i].active && rl->buckets[i].fd == fd) {
33 return &rl->buckets[i];
34 }
35 }
36 for (int i = 0; i < RATE_LIMITER_MAX_BUCKETS; i++) {
37 if (!rl->buckets[i].active) {
38 rl->buckets[i].fd = fd;
39 rl->buckets[i].active = true;
40 rl->buckets[i].event_count = 0;
41 rl->buckets[i].req_count = 0;
42 rl->buckets[i].window_start = esp_timer_get_time() / 1000000;
43 return &rl->buckets[i];
44 }
45 }
46 return NULL;
47}
48
49bool rate_limiter_check(rate_limiter_t *rl, int fd, rate_type_t type)
50{
51 xSemaphoreTake(rl->lock, portMAX_DELAY);
52
53 rate_bucket_t *bucket = get_bucket(rl, fd);
54 if (!bucket) {
55 xSemaphoreGive(rl->lock);
56 return false;
57 }
58
59 uint32_t now = esp_timer_get_time() / 1000000;
60
61 if (now - bucket->window_start >= 60) {
62 bucket->event_count = 0;
63 bucket->req_count = 0;
64 bucket->window_start = now;
65 }
66
67 bool allowed = true;
68 if (type == RATE_TYPE_EVENT) {
69 if (bucket->event_count >= rl->config.events_per_minute) {
70 ESP_LOGW(TAG, "Rate limited: fd=%d events=%d", fd, bucket->event_count);
71 allowed = false;
72 } else {
73 bucket->event_count++;
74 }
75 } else {
76 if (bucket->req_count >= rl->config.reqs_per_minute) {
77 ESP_LOGW(TAG, "Rate limited: fd=%d reqs=%d", fd, bucket->req_count);
78 allowed = false;
79 } else {
80 bucket->req_count++;
81 }
82 }
83
84 xSemaphoreGive(rl->lock);
85 return allowed;
86}
87
88void rate_limiter_reset(rate_limiter_t *rl, int fd)
89{
90 xSemaphoreTake(rl->lock, portMAX_DELAY);
91 for (int i = 0; i < RATE_LIMITER_MAX_BUCKETS; i++) {
92 if (rl->buckets[i].active && rl->buckets[i].fd == fd) {
93 rl->buckets[i].active = false;
94 break;
95 }
96 }
97 xSemaphoreGive(rl->lock);
98}