ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
rate_limit.c
Go to the documentation of this file.
1
11#include <stdlib.h>
12#include <string.h>
13#include <time.h>
14
15// Event type strings for logging
16static const char *event_type_strings[RATE_EVENT_MAX] = {
17 [RATE_EVENT_SESSION_CREATE] = "session_create",
18 [RATE_EVENT_SESSION_LOOKUP] = "session_lookup",
19 [RATE_EVENT_SESSION_JOIN] = "session_join",
20 [RATE_EVENT_CONNECTION] = "connection",
21 [RATE_EVENT_IMAGE_FRAME] = "image_frame",
22 [RATE_EVENT_AUDIO] = "audio",
23 [RATE_EVENT_PING] = "ping",
24 [RATE_EVENT_CLIENT_JOIN] = "client_join",
25 [RATE_EVENT_CONTROL] = "control",
26};
27
28// Default rate limits: conservative defaults to prevent abuse
29// Debug builds have relaxed limits for development/testing
31// ACDS discovery server limits
32#ifdef NDEBUG
33 // Release mode: production limits (144 FPS video, 172 FPS audio)
34 [RATE_EVENT_SESSION_CREATE] = {.max_events = 10, .window_secs = 60}, // 10 creates per minute
35 [RATE_EVENT_SESSION_LOOKUP] = {.max_events = 30, .window_secs = 60}, // 30 lookups per minute
36 [RATE_EVENT_SESSION_JOIN] = {.max_events = 20, .window_secs = 60}, // 20 joins per minute
37 [RATE_EVENT_CONNECTION] = {.max_events = 50, .window_secs = 60}, // 50 connections per minute
38 [RATE_EVENT_IMAGE_FRAME] = {.max_events = 8640, .window_secs = 60}, // 8640 frames/min = 144 FPS
39 [RATE_EVENT_AUDIO] = {.max_events = 10320, .window_secs = 60}, // 10320 packets/min = 172 FPS
40 [RATE_EVENT_PING] = {.max_events = 120, .window_secs = 60}, // 120 pings/min = 2 Hz max
41 [RATE_EVENT_CLIENT_JOIN] = {.max_events = 10, .window_secs = 60}, // 10 joins per minute
42 [RATE_EVENT_CONTROL] = {.max_events = 100, .window_secs = 60}, // 100 control packets/min
43#else
44 // Debug mode: slightly relaxed limits for development/testing (1.5x production limits)
45 [RATE_EVENT_SESSION_CREATE] = {.max_events = 15, .window_secs = 60}, // 15 creates per minute
46 [RATE_EVENT_SESSION_LOOKUP] = {.max_events = 45, .window_secs = 60}, // 45 lookups per minute
47 [RATE_EVENT_SESSION_JOIN] = {.max_events = 30, .window_secs = 60}, // 30 joins per minute
48 [RATE_EVENT_CONNECTION] = {.max_events = 75, .window_secs = 60}, // 75 connections per minute
49 [RATE_EVENT_IMAGE_FRAME] = {.max_events = 12960, .window_secs = 60}, // 12960 frames/min = 216 FPS
50 [RATE_EVENT_AUDIO] = {.max_events = 15480, .window_secs = 60}, // 15480 packets/min = 258 FPS
51 [RATE_EVENT_PING] = {.max_events = 180, .window_secs = 60}, // 180 pings/min = 3 Hz
52 [RATE_EVENT_CLIENT_JOIN] = {.max_events = 25, .window_secs = 60}, // 25 joins per minute (for testing reconnects)
53 [RATE_EVENT_CONTROL] = {.max_events = 150, .window_secs = 60}, // 150 control packets/min
54#endif
55};
56
57// ============================================================================
58// Backend Interface
59// ============================================================================
60
68
69// ============================================================================
70// Public API Implementation
71// ============================================================================
72
74 rate_limiter_t *limiter = malloc(sizeof(rate_limiter_t));
75 if (!limiter) {
76 log_error("Failed to allocate rate limiter");
77 return NULL;
78 }
79
81 if (!limiter->backend_data) {
82 free(limiter);
83 return NULL;
84 }
85
86 limiter->ops = &memory_backend_ops;
87 return limiter;
88}
89
91 rate_limiter_t *limiter = malloc(sizeof(rate_limiter_t));
92 if (!limiter) {
93 log_error("Failed to allocate rate limiter");
94 return NULL;
95 }
96
97 limiter->backend_data = sqlite_backend_create(db_path);
98 if (!limiter->backend_data) {
99 free(limiter);
100 return NULL;
101 }
102
103 limiter->ops = &sqlite_backend_ops;
104 return limiter;
105}
106
108 if (!limiter || !limiter->backend_data) {
109 return;
110 }
111
112 // Call the SQLite backend function to set the database handle
113 sqlite_backend_set_db(limiter->backend_data, (sqlite3 *)db);
114}
115
117 if (!limiter) {
118 return;
119 }
120
121 if (limiter->ops && limiter->ops->destroy) {
122 limiter->ops->destroy(limiter->backend_data);
123 }
124
125 free(limiter);
126}
127
128asciichat_error_t rate_limiter_check(rate_limiter_t *limiter, const char *ip_address, rate_event_type_t event_type,
129 const rate_limit_config_t *config, bool *allowed) {
130 if (!limiter || !limiter->ops || !limiter->ops->check) {
131 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid rate limiter");
132 }
133
134 if (!ip_address || !allowed) {
135 return SET_ERRNO(ERROR_INVALID_PARAM, "ip_address or allowed is NULL");
136 }
137
138 if (event_type >= RATE_EVENT_MAX) {
139 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid event_type: %d", event_type);
140 }
141
142 return limiter->ops->check(limiter->backend_data, ip_address, event_type, config, allowed);
143}
144
145asciichat_error_t rate_limiter_record(rate_limiter_t *limiter, const char *ip_address, rate_event_type_t event_type) {
146 if (!limiter || !limiter->ops || !limiter->ops->record) {
147 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid rate limiter");
148 }
149
150 if (!ip_address) {
151 return SET_ERRNO(ERROR_INVALID_PARAM, "ip_address is NULL");
152 }
153
154 if (event_type >= RATE_EVENT_MAX) {
155 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid event_type: %d", event_type);
156 }
157
158 return limiter->ops->record(limiter->backend_data, ip_address, event_type);
159}
160
162 if (!limiter || !limiter->ops || !limiter->ops->cleanup) {
163 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid rate limiter");
164 }
165
166 return limiter->ops->cleanup(limiter->backend_data, max_age_secs);
167}
168
169// ============================================================================
170// Helper Functions (available to backends)
171// ============================================================================
172
177 if (event_type >= RATE_EVENT_MAX) {
178 return "unknown";
179 }
180 return event_type_strings[event_type];
181}
unsigned int uint32_t
Definition common.h:58
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_INVALID_PARAM
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
📝 Logging API with multiple log levels and terminal output control
void * memory_backend_create(void)
Create memory backend instance.
const rate_limiter_backend_ops_t memory_backend_ops
Memory backend operations vtable.
🧠 In-memory rate limiting backend interface
asciichat_error_t rate_limiter_record(rate_limiter_t *limiter, const char *ip_address, rate_event_type_t event_type)
Record a rate limit event.
Definition rate_limit.c:145
void rate_limiter_destroy(rate_limiter_t *limiter)
Destroy rate limiter and free resources.
Definition rate_limit.c:116
void rate_limiter_set_sqlite_db(rate_limiter_t *limiter, void *db)
Set SQLite database handle for rate limiter.
Definition rate_limit.c:107
rate_limiter_t * rate_limiter_create_memory(void)
Create in-memory rate limiter.
Definition rate_limit.c:73
const char * rate_limiter_event_type_string(rate_event_type_t event_type)
Get event type string for logging.
Definition rate_limit.c:176
rate_limiter_t * rate_limiter_create_sqlite(const char *db_path)
Create SQLite-backed rate limiter.
Definition rate_limit.c:90
asciichat_error_t rate_limiter_check(rate_limiter_t *limiter, const char *ip_address, rate_event_type_t event_type, const rate_limit_config_t *config, bool *allowed)
Check if an event from an IP address should be rate limited.
Definition rate_limit.c:128
const rate_limit_config_t DEFAULT_RATE_LIMITS[RATE_EVENT_MAX]
Default rate limits for each event type.
Definition rate_limit.c:30
asciichat_error_t rate_limiter_prune(rate_limiter_t *limiter, uint32_t max_age_secs)
Clean up old rate limit events.
Definition rate_limit.c:161
🚦 Rate limiting API with pluggable backends
rate_event_type_t
Rate limit event types.
Definition rate_limit.h:46
@ RATE_EVENT_SESSION_LOOKUP
Session lookup.
Definition rate_limit.h:49
@ RATE_EVENT_CONTROL
Control packets (CAPABILITIES, STREAM_START/STOP, LEAVE)
Definition rate_limit.h:58
@ RATE_EVENT_SESSION_JOIN
Session join.
Definition rate_limit.h:50
@ RATE_EVENT_CONNECTION
New connection.
Definition rate_limit.h:53
@ RATE_EVENT_PING
Ping/pong keepalive (PACKET_TYPE_PING, PACKET_TYPE_PONG)
Definition rate_limit.h:56
@ RATE_EVENT_SESSION_CREATE
Session creation.
Definition rate_limit.h:48
@ RATE_EVENT_AUDIO
Audio packet (PACKET_TYPE_AUDIO, PACKET_TYPE_AUDIO_BATCH)
Definition rate_limit.h:55
@ RATE_EVENT_MAX
Sentinel value.
Definition rate_limit.h:60
@ RATE_EVENT_IMAGE_FRAME
Image frame from client (PACKET_TYPE_IMAGE_FRAME)
Definition rate_limit.h:54
@ RATE_EVENT_CLIENT_JOIN
Client join request (PACKET_TYPE_CLIENT_JOIN)
Definition rate_limit.h:57
void sqlite_backend_set_db(void *backend_data, sqlite3 *db)
Set SQLite database handle for backend.
Definition sqlite.c:167
void * sqlite_backend_create(const char *db_path)
Create SQLite backend instance.
Definition sqlite.c:145
const rate_limiter_backend_ops_t sqlite_backend_ops
SQLite backend operations vtable.
Definition sqlite.c:174
💾 SQLite rate limiting backend interface
Rate limit configuration.
Definition rate_limit.h:66
uint32_t max_events
Maximum events allowed.
Definition rate_limit.h:67
Backend operations vtable.
Definition rate_limit.h:83
void(* destroy)(void *backend_data)
Definition rate_limit.h:91
asciichat_error_t(* cleanup)(void *backend_data, uint32_t max_age_secs)
Definition rate_limit.h:89
asciichat_error_t(* record)(void *backend_data, const char *ip_address, rate_event_type_t event_type)
Definition rate_limit.h:87
asciichat_error_t(* check)(void *backend_data, const char *ip_address, rate_event_type_t event_type, const rate_limit_config_t *config, bool *allowed)
Definition rate_limit.h:84
Rate limiter structure.
Definition rate_limit.c:64
void * backend_data
Backend-specific data.
Definition rate_limit.c:66
const rate_limiter_backend_ops_t * ops
Backend operations.
Definition rate_limit.c:65
⏱️ High-precision timing utilities using sokol_time.h and uthash