ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
rate_limit.c File Reference

🚦 Rate limiting implementation with backend abstraction More...

Go to the source code of this file.

Data Structures

struct  rate_limiter_s
 Rate limiter structure. More...
 

Functions

rate_limiter_trate_limiter_create_memory (void)
 Create in-memory rate limiter.
 
rate_limiter_trate_limiter_create_sqlite (const char *db_path)
 Create SQLite-backed rate limiter.
 
void rate_limiter_set_sqlite_db (rate_limiter_t *limiter, void *db)
 Set SQLite database handle for rate limiter.
 
void rate_limiter_destroy (rate_limiter_t *limiter)
 Destroy rate limiter and free resources.
 
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.
 
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.
 
asciichat_error_t rate_limiter_cleanup (rate_limiter_t *limiter, uint32_t max_age_secs)
 Clean up old rate limit events.
 
uint64_t rate_limiter_get_time_ms (void)
 Get current time in milliseconds.
 
const char * rate_limiter_event_type_string (rate_event_type_t event_type)
 Get event type string for logging.
 

Variables

const rate_limit_config_t DEFAULT_RATE_LIMITS [RATE_EVENT_MAX]
 Default rate limits for each event type.
 

Detailed Description

🚦 Rate limiting implementation with backend abstraction

Definition in file rate_limit.c.

Function Documentation

◆ rate_limiter_check()

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.

Uses sliding window: counts events in the last window_secs seconds.

Parameters
limiterRate limiter instance
ip_addressIP address string (IPv4 or IPv6)
event_typeType of event
configRate limit configuration (NULL = use defaults)
[out]allowedTrue if event is allowed, false if rate limited
Returns
ASCIICHAT_OK on success, error code otherwise

Definition at line 127 of file rate_limit.c.

128 {
129 if (!limiter || !limiter->ops || !limiter->ops->check) {
130 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid rate limiter");
131 }
132
133 if (!ip_address || !allowed) {
134 return SET_ERRNO(ERROR_INVALID_PARAM, "ip_address or allowed is NULL");
135 }
136
137 if (event_type >= RATE_EVENT_MAX) {
138 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid event_type: %d", event_type);
139 }
140
141 return limiter->ops->check(limiter->backend_data, ip_address, event_type, config, allowed);
142}
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
@ ERROR_INVALID_PARAM
@ RATE_EVENT_MAX
Sentinel value.
Definition rate_limit.h:60
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
void * backend_data
Backend-specific data.
Definition rate_limit.c:65
const rate_limiter_backend_ops_t * ops
Backend operations.
Definition rate_limit.c:64

References rate_limiter_s::backend_data, rate_limiter_backend_ops_t::check, ERROR_INVALID_PARAM, rate_limiter_s::ops, RATE_EVENT_MAX, and SET_ERRNO.

Referenced by check_and_record_rate_limit().

◆ rate_limiter_cleanup()

asciichat_error_t rate_limiter_cleanup ( rate_limiter_t limiter,
uint32_t  max_age_secs 
)

Clean up old rate limit events.

Deletes events older than the specified age to prevent unbounded growth. Should be called periodically (e.g., every 5 minutes).

Parameters
limiterRate limiter instance
max_age_secsDelete events older than this (0 = use default 1 hour)
Returns
ASCIICHAT_OK on success, error code otherwise

Definition at line 160 of file rate_limit.c.

160 {
161 if (!limiter || !limiter->ops || !limiter->ops->cleanup) {
162 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid rate limiter");
163 }
164
165 return limiter->ops->cleanup(limiter->backend_data, max_age_secs);
166}
asciichat_error_t(* cleanup)(void *backend_data, uint32_t max_age_secs)
Definition rate_limit.h:89

References rate_limiter_s::backend_data, rate_limiter_backend_ops_t::cleanup, ERROR_INVALID_PARAM, rate_limiter_s::ops, and SET_ERRNO.

◆ rate_limiter_create_memory()

rate_limiter_t * rate_limiter_create_memory ( void  )

Create in-memory rate limiter.

Thread-safe implementation using uthash and mutexes. Suitable for ascii-chat server where persistence is not needed.

Returns
Rate limiter instance or NULL on failure

Definition at line 72 of file rate_limit.c.

72 {
73 rate_limiter_t *limiter = malloc(sizeof(rate_limiter_t));
74 if (!limiter) {
75 log_error("Failed to allocate rate limiter");
76 return NULL;
77 }
78
80 if (!limiter->backend_data) {
81 free(limiter);
82 return NULL;
83 }
84
85 limiter->ops = &memory_backend_ops;
86 return limiter;
87}
#define log_error(...)
Log an ERROR message.
void * memory_backend_create(void)
Create memory backend instance.
const rate_limiter_backend_ops_t memory_backend_ops
Memory backend operations vtable.
Rate limiter structure.
Definition rate_limit.c:63

References rate_limiter_s::backend_data, log_error, memory_backend_create(), memory_backend_ops, and rate_limiter_s::ops.

Referenced by server_main().

◆ rate_limiter_create_sqlite()

rate_limiter_t * rate_limiter_create_sqlite ( const char *  db_path)

Create SQLite-backed rate limiter.

Persistent implementation using SQLite database. Suitable for acds discovery server where persistence is needed.

Parameters
db_pathPath to SQLite database (NULL = externally managed database)
Returns
Rate limiter instance or NULL on failure

Definition at line 89 of file rate_limit.c.

89 {
90 rate_limiter_t *limiter = malloc(sizeof(rate_limiter_t));
91 if (!limiter) {
92 log_error("Failed to allocate rate limiter");
93 return NULL;
94 }
95
96 limiter->backend_data = sqlite_backend_create(db_path);
97 if (!limiter->backend_data) {
98 free(limiter);
99 return NULL;
100 }
101
102 limiter->ops = &sqlite_backend_ops;
103 return limiter;
104}
void * sqlite_backend_create(const char *db_path)
Create SQLite backend instance.
Definition sqlite.c:157
const rate_limiter_backend_ops_t sqlite_backend_ops
SQLite backend operations vtable.
Definition sqlite.c:186

References rate_limiter_s::backend_data, log_error, rate_limiter_s::ops, sqlite_backend_create(), and sqlite_backend_ops.

Referenced by acds_server_init().

◆ rate_limiter_destroy()

void rate_limiter_destroy ( rate_limiter_t limiter)

Destroy rate limiter and free resources.

Parameters
limiterRate limiter instance (NULL-safe)

Definition at line 115 of file rate_limit.c.

115 {
116 if (!limiter) {
117 return;
118 }
119
120 if (limiter->ops && limiter->ops->destroy) {
121 limiter->ops->destroy(limiter->backend_data);
122 }
123
124 free(limiter);
125}
void(* destroy)(void *backend_data)
Definition rate_limit.h:91

References rate_limiter_s::backend_data, rate_limiter_backend_ops_t::destroy, and rate_limiter_s::ops.

Referenced by acds_server_init(), acds_server_shutdown(), and server_main().

◆ rate_limiter_event_type_string()

const char * rate_limiter_event_type_string ( rate_event_type_t  event_type)

Get event type string for logging.

Helper: Get event type string for logging.

Definition at line 184 of file rate_limit.c.

184 {
185 if (event_type >= RATE_EVENT_MAX) {
186 return "unknown";
187 }
188 return event_type_strings[event_type];
189}

References RATE_EVENT_MAX.

◆ rate_limiter_get_time_ms()

uint64_t rate_limiter_get_time_ms ( void  )

Get current time in milliseconds.

Helper: Get current time in milliseconds.

Definition at line 175 of file rate_limit.c.

175 {
176 struct timespec ts;
177 clock_gettime(CLOCK_REALTIME, &ts);
178 return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
179}
unsigned long long uint64_t
Definition common.h:59

◆ rate_limiter_record()

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.

Should be called after rate_limiter_check() returns allowed=true.

Parameters
limiterRate limiter instance
ip_addressIP address string
event_typeType of event
Returns
ASCIICHAT_OK on success, error code otherwise

Definition at line 144 of file rate_limit.c.

144 {
145 if (!limiter || !limiter->ops || !limiter->ops->record) {
146 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid rate limiter");
147 }
148
149 if (!ip_address) {
150 return SET_ERRNO(ERROR_INVALID_PARAM, "ip_address is NULL");
151 }
152
153 if (event_type >= RATE_EVENT_MAX) {
154 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid event_type: %d", event_type);
155 }
156
157 return limiter->ops->record(limiter->backend_data, ip_address, event_type);
158}
asciichat_error_t(* record)(void *backend_data, const char *ip_address, rate_event_type_t event_type)
Definition rate_limit.h:87

References rate_limiter_s::backend_data, ERROR_INVALID_PARAM, rate_limiter_s::ops, RATE_EVENT_MAX, rate_limiter_backend_ops_t::record, and SET_ERRNO.

Referenced by check_and_record_rate_limit().

◆ rate_limiter_set_sqlite_db()

void rate_limiter_set_sqlite_db ( rate_limiter_t limiter,
void *  db 
)

Set SQLite database handle for rate limiter.

For SQLite-backed rate limiters where the database lifecycle is managed externally (e.g., ACDS manages its own database). Must be called after rate_limiter_create_sqlite(NULL).

Parameters
limiterRate limiter instance (must be SQLite backend)
dbSQLite database handle

Definition at line 106 of file rate_limit.c.

106 {
107 if (!limiter || !limiter->backend_data) {
108 return;
109 }
110
111 // Call the SQLite backend function to set the database handle
112 sqlite_backend_set_db(limiter->backend_data, (sqlite3 *)db);
113}
void sqlite_backend_set_db(void *backend_data, sqlite3 *db)
Set SQLite database handle for backend.
Definition sqlite.c:179

References rate_limiter_s::backend_data, and sqlite_backend_set_db().

Referenced by acds_server_init().

Variable Documentation

◆ DEFAULT_RATE_LIMITS

const rate_limit_config_t DEFAULT_RATE_LIMITS[RATE_EVENT_MAX]
Initial value:
= {
[RATE_EVENT_SESSION_CREATE] = {.max_events = 15, .window_secs = 60},
[RATE_EVENT_SESSION_LOOKUP] = {.max_events = 45, .window_secs = 60},
[RATE_EVENT_SESSION_JOIN] = {.max_events = 30, .window_secs = 60},
[RATE_EVENT_CONNECTION] = {.max_events = 75, .window_secs = 60},
[RATE_EVENT_IMAGE_FRAME] = {.max_events = 12960, .window_secs = 60},
[RATE_EVENT_AUDIO] = {.max_events = 15480, .window_secs = 60},
[RATE_EVENT_PING] = {.max_events = 180, .window_secs = 60},
[RATE_EVENT_CLIENT_JOIN] = {.max_events = 25, .window_secs = 60},
[RATE_EVENT_CONTROL] = {.max_events = 150, .window_secs = 60},
}
@ 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_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

Default rate limits for each event type.

Definition at line 29 of file rate_limit.c.

29 {
30// ACDS discovery server limits
31#ifdef NDEBUG
32 // Release mode: production limits (144 FPS video, 172 FPS audio)
33 [RATE_EVENT_SESSION_CREATE] = {.max_events = 10, .window_secs = 60}, // 10 creates per minute
34 [RATE_EVENT_SESSION_LOOKUP] = {.max_events = 30, .window_secs = 60}, // 30 lookups per minute
35 [RATE_EVENT_SESSION_JOIN] = {.max_events = 20, .window_secs = 60}, // 20 joins per minute
36 [RATE_EVENT_CONNECTION] = {.max_events = 50, .window_secs = 60}, // 50 connections per minute
37 [RATE_EVENT_IMAGE_FRAME] = {.max_events = 8640, .window_secs = 60}, // 8640 frames/min = 144 FPS
38 [RATE_EVENT_AUDIO] = {.max_events = 10320, .window_secs = 60}, // 10320 packets/min = 172 FPS
39 [RATE_EVENT_PING] = {.max_events = 120, .window_secs = 60}, // 120 pings/min = 2 Hz max
40 [RATE_EVENT_CLIENT_JOIN] = {.max_events = 10, .window_secs = 60}, // 10 joins per minute
41 [RATE_EVENT_CONTROL] = {.max_events = 100, .window_secs = 60}, // 100 control packets/min
42#else
43 // Debug mode: slightly relaxed limits for development/testing (1.5x production limits)
44 [RATE_EVENT_SESSION_CREATE] = {.max_events = 15, .window_secs = 60}, // 15 creates per minute
45 [RATE_EVENT_SESSION_LOOKUP] = {.max_events = 45, .window_secs = 60}, // 45 lookups per minute
46 [RATE_EVENT_SESSION_JOIN] = {.max_events = 30, .window_secs = 60}, // 30 joins per minute
47 [RATE_EVENT_CONNECTION] = {.max_events = 75, .window_secs = 60}, // 75 connections per minute
48 [RATE_EVENT_IMAGE_FRAME] = {.max_events = 12960, .window_secs = 60}, // 12960 frames/min = 216 FPS
49 [RATE_EVENT_AUDIO] = {.max_events = 15480, .window_secs = 60}, // 15480 packets/min = 258 FPS
50 [RATE_EVENT_PING] = {.max_events = 180, .window_secs = 60}, // 180 pings/min = 3 Hz
51 [RATE_EVENT_CLIENT_JOIN] = {.max_events = 25, .window_secs = 60}, // 25 joins per minute (for testing reconnects)
52 [RATE_EVENT_CONTROL] = {.max_events = 150, .window_secs = 60}, // 150 control packets/min
53#endif
54};