ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
network/rate_limit/memory.c
Go to the documentation of this file.
1
8#include <ascii-chat/network/rate_limit/memory.h>
9#include <ascii-chat/common.h>
10#include <ascii-chat/log/logging.h>
11#include <ascii-chat/util/time.h>
12#include <ascii-chat/platform/abstraction.h>
13#include <ascii-chat/uthash/uthash.h>
14#include <stdlib.h>
15#include <string.h>
16
20typedef struct rate_event_s {
21 char key[256];
22 uint64_t *timestamps;
23 size_t count;
24 size_t capacity;
25 size_t head;
26 UT_hash_handle hh;
28
32typedef struct {
34 mutex_t lock;
36
40static void make_key(const char *ip_address, rate_event_type_t event_type, char *key, size_t key_size) {
41 safe_snprintf(key, key_size, "%s:%d", ip_address, event_type);
42}
43
47static void add_timestamp(rate_event_t *event, uint64_t timestamp) {
48 if (event->count < event->capacity) {
49 // Buffer not full yet
50 event->timestamps[event->count++] = timestamp;
51 } else {
52 // Buffer full, overwrite oldest
53 event->timestamps[event->head] = timestamp;
54 event->head = (event->head + 1) % event->capacity;
55 }
56}
57
61static uint32_t count_events_in_window(rate_event_t *event, uint64_t window_start_ms) {
62 uint32_t count = 0;
63
64 for (size_t i = 0; i < event->count; i++) {
65 if (event->timestamps[i] >= window_start_ms) {
66 count++;
67 }
68 }
69
70 return count;
71}
72
76static void cleanup_old_events(rate_event_t *event, uint64_t cutoff_ns) {
77 // Compact array by removing old timestamps
78 size_t write_idx = 0;
79
80 for (size_t read_idx = 0; read_idx < event->count; read_idx++) {
81 if (event->timestamps[read_idx] >= cutoff_ns) {
82 event->timestamps[write_idx++] = event->timestamps[read_idx];
83 }
84 }
85
86 event->count = write_idx;
87 event->head = 0; // Reset circular buffer head
88}
89
90static asciichat_error_t memory_check(void *backend_data, const char *ip_address, rate_event_type_t event_type,
91 const rate_limit_config_t *config, bool *allowed) {
92 memory_backend_t *backend = (memory_backend_t *)backend_data;
93
94 // Use provided config or default
95 const rate_limit_config_t *limit = config ? config : &DEFAULT_RATE_LIMITS[event_type];
96
97 // Get current time in nanoseconds
98 uint64_t now_ns = time_get_realtime_ns();
99 uint64_t window_start_ns = now_ns - ((uint64_t)limit->window_secs * NS_PER_SEC_INT);
100
101 // Create hash key
102 char key[256];
103 make_key(ip_address, event_type, key, sizeof(key));
104
105 mutex_lock(&backend->lock);
106
107 // Find event record
108 rate_event_t *event = NULL;
109 HASH_FIND_STR(backend->events, key, event);
110
111 uint32_t event_count = 0;
112
113 if (event) {
114 // Count events in window
115 event_count = count_events_in_window(event, window_start_ns);
116 }
117
118 mutex_unlock(&backend->lock);
119
120 // Check if limit exceeded
121 *allowed = (event_count < limit->max_events);
122
123 if (!*allowed) {
124 log_warn("Rate limit exceeded for %s (event: %s, count: %u/%u)", ip_address,
125 rate_limiter_event_type_string(event_type), event_count, limit->max_events);
126 }
127
128 return ASCIICHAT_OK;
129}
130
131static asciichat_error_t memory_record(void *backend_data, const char *ip_address, rate_event_type_t event_type) {
132 memory_backend_t *backend = (memory_backend_t *)backend_data;
133
134 // Get current time in nanoseconds
135 uint64_t now_ns = time_get_realtime_ns();
136
137 // Create hash key
138 char key[256];
139 make_key(ip_address, event_type, key, sizeof(key));
140
141 mutex_lock(&backend->lock);
142
143 // Find or create event record
144 rate_event_t *event = NULL;
145 HASH_FIND_STR(backend->events, key, event);
146
147 if (!event) {
148 // Create new event record
149 event = SAFE_MALLOC(sizeof(rate_event_t), rate_event_t *);
150 if (!event) {
151 mutex_unlock(&backend->lock);
152 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate rate event");
153 }
154
155 memset(event, 0, sizeof(*event));
156 SAFE_STRNCPY(event->key, key, sizeof(event->key));
157
158 // Allocate timestamp buffer (default: 100 events)
159 event->capacity = 100;
160 event->timestamps = SAFE_MALLOC(sizeof(uint64_t) * event->capacity, uint64_t *);
161 if (!event->timestamps) {
162 SAFE_FREE(event);
163 mutex_unlock(&backend->lock);
164 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate timestamp buffer");
165 }
166
167 HASH_ADD_STR(backend->events, key, event);
168 }
169
170 // Add timestamp (in nanoseconds)
171 add_timestamp(event, now_ns);
172
173 mutex_unlock(&backend->lock);
174
175 log_debug("Rate event recorded: %s - %s", ip_address, rate_limiter_event_type_string(event_type));
176 return ASCIICHAT_OK;
177}
178
179static asciichat_error_t memory_cleanup(void *backend_data, uint32_t max_age_secs) {
180 memory_backend_t *backend = (memory_backend_t *)backend_data;
181
182 // Default to 1 hour cleanup window
183 if (max_age_secs == 0) {
184 max_age_secs = SEC_PER_HOUR;
185 }
186
187 // Calculate cutoff time in nanoseconds
188 uint64_t now_ns = time_get_realtime_ns();
189 uint64_t cutoff_ns = now_ns - ((uint64_t)max_age_secs * NS_PER_SEC_INT);
190
191 mutex_lock(&backend->lock);
192
193 size_t total_removed = 0;
194 rate_event_t *event = NULL, *tmp = NULL;
195
196 HASH_ITER(hh, backend->events, event, tmp) {
197 size_t before_count = event->count;
198
199 // Remove old timestamps
200 cleanup_old_events(event, cutoff_ns);
201
202 total_removed += (before_count - event->count);
203
204 // Remove event record if empty
205 if (event->count == 0) {
206 HASH_DEL(backend->events, event);
207 SAFE_FREE(event->timestamps);
208 SAFE_FREE(event);
209 }
210 }
211
212 mutex_unlock(&backend->lock);
213
214 if (total_removed > 0) {
215 log_debug("Cleaned up %zu old rate events", total_removed);
216 }
217
218 return ASCIICHAT_OK;
219}
220
221static void memory_destroy(void *backend_data) {
222 memory_backend_t *backend = (memory_backend_t *)backend_data;
223 if (!backend) {
224 return;
225 }
226
227 // Free all event records
228 rate_event_t *event, *tmp;
229 HASH_ITER(hh, backend->events, event, tmp) {
230 HASH_DEL(backend->events, event);
231 SAFE_FREE(event->timestamps);
232 SAFE_FREE(event);
233 }
234
235 mutex_destroy(&backend->lock);
236 SAFE_FREE(backend);
237}
238
240 memory_backend_t *backend = SAFE_MALLOC(sizeof(memory_backend_t), memory_backend_t *);
241 if (!backend) {
242 log_error("Failed to allocate memory backend");
243 return NULL;
244 }
245
246 memset(backend, 0, sizeof(*backend));
247
248 if (mutex_init(&backend->lock) != 0) {
249 log_error("Failed to initialize mutex");
250 SAFE_FREE(backend);
251 return NULL;
252 }
253
254 log_debug("Memory rate limiter backend initialized");
255 return backend;
256}
257
258const rate_limiter_backend_ops_t memory_backend_ops = {
259 .check = memory_check,
260 .record = memory_record,
261 .cleanup = memory_cleanup,
262 .destroy = memory_destroy,
263};
void * memory_backend_create(void)
const rate_limiter_backend_ops_t memory_backend_ops
struct rate_event_s rate_event_t
Rate event record in memory.
const char * rate_limiter_event_type_string(rate_event_type_t event_type)
Get event type string for logging.
Definition rate_limit.c:176
const rate_limit_config_t DEFAULT_RATE_LIMITS[RATE_EVENT_MAX]
Definition rate_limit.c:30
Memory backend data.
mutex_t lock
Mutex for thread safety.
rate_event_t * events
Hash table of rate events.
Rate event record in memory.
char key[256]
Hash key: "ip_address:event_type".
size_t count
Number of events in buffer.
UT_hash_handle hh
uthash handle
size_t capacity
Buffer capacity.
size_t head
Head index (oldest event)
uint64_t * timestamps
Array of timestamps (circular buffer)
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456
int mutex_init(mutex_t *mutex)
Definition threading.c:16
int mutex_destroy(mutex_t *mutex)
Definition threading.c:21
uint64_t time_get_realtime_ns(void)
Definition util/time.c:59