ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
memory.c
Go to the documentation of this file.
1
7
8/* Storage capacities in elements; text buffers include the terminating NUL. */
9#define NETWORK_RATE_LIMIT_MEMORY_KEY_BUFFER_SIZE 256
10
11/* Existing buffer budgets in bytes, including NUL for text storage. */
12#define RATE_LIMIT_INITIAL_EVENT_CAPACITY 100
13
15#include <ascii-chat/common.h>
16#include <defer.h>
17#include <ascii-chat/log/log.h>
19#include <libplatform/compiler.h>
20#include <libplatform/mutex.h>
21#include <libplatform/system.h>
22#include <libplatform/terminal.h>
23#include <ascii-chat/uthash.h>
24#include <stdlib.h>
25#include <string.h>
26
45
49typedef struct {
51 mutex_t lock;
53
57static void make_key(const char *ip_address, rate_event_type_t event_type, char *key, size_t key_size) {
58 safe_snprintf(key, key_size, "%s:%d", ip_address, event_type);
59}
60
64static void add_timestamp(rate_event_t *event, uint64_t timestamp) {
65 if (event->count < event->capacity) {
66 // Buffer not full yet
67 event->timestamps[event->count++] = timestamp;
68 } else {
69 // Buffer full, overwrite oldest
70 event->timestamps[event->head] = timestamp;
71 event->head = (event->head + 1) % event->capacity;
72 }
73}
74
78static uint32_t count_events_in_window(rate_event_t *event, uint64_t window_start_ms) {
79 uint32_t count = 0;
80
81 for (size_t i = 0; i < event->count; i++) {
82 if (event->timestamps[i] >= window_start_ms) {
83 count++;
84 }
85 }
86
87 return count;
88}
89
93static void cleanup_old_events(rate_event_t *event, uint64_t cutoff_ns) {
94 // Compact array by removing old timestamps
95 size_t write_idx = 0;
96
97 for (size_t read_idx = 0; read_idx < event->count; read_idx++) {
98 if (event->timestamps[read_idx] >= cutoff_ns) {
99 event->timestamps[write_idx++] = event->timestamps[read_idx];
100 }
101 }
102
103 event->count = write_idx;
104 event->head = 0; // Reset circular buffer head
105}
106
107static asciichat_error_t memory_check(void *backend_data, const char *ip_address, rate_event_type_t event_type,
108 const rate_limit_config_t *config, bool *allowed) {
109 memory_backend_t *backend = (memory_backend_t *)backend_data;
110
111 // Use provided config or default
112 const rate_limit_config_t *limit = config ? config : &DEFAULT_RATE_LIMITS[event_type];
113
114 // Get current time in nanoseconds
115 uint64_t now_ns = time_get_realtime_ns();
116 uint64_t window_start_ns = now_ns - ((uint64_t)limit->window_secs * NS_PER_SEC_INT);
117
118 // Create hash key
120 make_key(ip_address, event_type, key, sizeof(key));
121
122 mutex_lock(&backend->lock);
123
124 // Find event record
125 rate_event_t *event = NULL;
126 HASH_FIND_STR(backend->events, key, event);
127
128 uint32_t event_count = 0;
129
130 if (event) {
131 // Count events in window
132 event_count = count_events_in_window(event, window_start_ns);
133 }
134
135 mutex_unlock(&backend->lock);
136
137 // Check if limit exceeded
138 *allowed = (event_count < limit->max_events);
139
140 if (!*allowed) {
141 log_warn("Rate limit exceeded for %s (event: %s, count: %u/%u)", ip_address,
142 rate_limiter_event_type_string(event_type), event_count, limit->max_events);
143 }
144
145 return ASCIICHAT_OK;
146}
147
148static asciichat_error_t memory_record(void *backend_data, const char *ip_address, rate_event_type_t event_type) {
149 memory_backend_t *backend = (memory_backend_t *)backend_data;
150
151 // Get current time in nanoseconds
152 uint64_t now_ns = time_get_realtime_ns();
153
154 // Create hash key
156 make_key(ip_address, event_type, key, sizeof(key));
157
158 mutex_lock(&backend->lock);
159
160 // Find or create event record
161 rate_event_t *event = NULL;
162 HASH_FIND_STR(backend->events, key, event);
163
164 if (!event) {
165 // Create new event record
166 event = SAFE_MALLOC(sizeof(rate_event_t), rate_event_t *);
167 if (!event) {
168 mutex_unlock(&backend->lock);
169 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate rate event");
170 }
171
172 memset(event, 0, sizeof(*event));
173 SAFE_STRNCPY(event->key, key, sizeof(event->key));
174
175 // Allocate timestamp buffer (default: 100 events)
176 event->capacity = RATE_LIMIT_INITIAL_EVENT_CAPACITY;
177 event->timestamps = SAFE_MALLOC(sizeof(uint64_t) * event->capacity, uint64_t *);
178 if (!event->timestamps) {
179 SAFE_FREE(event);
180 mutex_unlock(&backend->lock);
181 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate timestamp buffer");
182 }
183
184 HASH_ADD_STR(backend->events, key, event);
185 }
186
187 // Add timestamp (in nanoseconds)
188 add_timestamp(event, now_ns);
189
190 mutex_unlock(&backend->lock);
191
192 log_debug("Rate event recorded: %s - %s", ip_address, rate_limiter_event_type_string(event_type));
193 return ASCIICHAT_OK;
194}
195
196static asciichat_error_t memory_cleanup(void *backend_data, uint32_t max_age_secs) {
197 memory_backend_t *backend = (memory_backend_t *)backend_data;
198
199 // Default to 1 hour cleanup window
200 if (max_age_secs == 0) {
201 max_age_secs = SEC_PER_HOUR;
202 }
203
204 // Calculate cutoff time in nanoseconds
205 uint64_t now_ns = time_get_realtime_ns();
206 uint64_t cutoff_ns = now_ns - ((uint64_t)max_age_secs * NS_PER_SEC_INT);
207
208 mutex_lock(&backend->lock);
209
210 size_t total_removed = 0;
211 rate_event_t *event = NULL, *tmp = NULL;
212
213 HASH_ITER(hh, backend->events, event, tmp) {
214 size_t before_count = event->count;
215
216 // Remove old timestamps
217 cleanup_old_events(event, cutoff_ns);
218
219 total_removed += (before_count - event->count);
220
221 // Remove event record if empty
222 if (event->count == 0) {
223 HASH_DEL(backend->events, event);
224 SAFE_FREE(event->timestamps);
225 SAFE_FREE(event);
226 }
227 }
228
229 mutex_unlock(&backend->lock);
230
231 if (total_removed > 0) {
232 log_debug("Cleaned up %zu old rate events", total_removed);
233 }
234
235 return ASCIICHAT_OK;
236}
237
238static void memory_destroy(void *backend_data) {
239 memory_backend_t *backend = (memory_backend_t *)backend_data;
240 if (!backend) {
241 return;
242 }
243
244 // Free all event records
245 rate_event_t *event, *tmp;
246 HASH_ITER(hh, backend->events, event, tmp) {
247 HASH_DEL(backend->events, event);
248 SAFE_FREE(event->timestamps);
249 SAFE_FREE(event);
250 }
251
252 mutex_destroy(&backend->lock);
253 SAFE_FREE(backend);
254}
255
258 if (!backend) {
259 log_error("Failed to allocate memory backend");
260 return NULL;
261 }
262
263 defer_named(release_backend, SAFE_FREE(backend));
264 memset(backend, 0, sizeof(*backend));
265
266 if (mutex_init(&backend->lock, "rate_limiter") != 0) {
267 log_error("Failed to initialize mutex");
268 return NULL;
269 }
270
271 log_debug("Memory rate limiter backend initialized");
272 defer_cancel(release_backend);
273 return backend;
274}
275
277 .check = memory_check,
278 .record = memory_record,
279 .cleanup = memory_cleanup,
280 .destroy = memory_destroy,
281};
#define SET_ERRNO(code,...)
⚙️ Common definitions, error codes, macros, and types shared throughout the application
unsigned int uint32_t
Definition common.h:62
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:423
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_MALLOC(size, cast)
Definition common.h:265
unsigned long long uint64_t
Definition common.h:63
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
#define SEC_PER_HOUR
Definition time.h:180
#define NS_PER_SEC_INT
Definition time.h:169
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Platform-safe snprintf wrapper.
📝 Logging API with multiple log levels and terminal output control
void * memory_backend_create(void)
Create memory backend instance.
Definition memory.c:256
const rate_limiter_backend_ops_t memory_backend_ops
Memory backend operations vtable.
Definition memory.c:276
#define RATE_LIMIT_INITIAL_EVENT_CAPACITY
Definition memory.c:12
#define NETWORK_RATE_LIMIT_MEMORY_KEY_BUFFER_SIZE
Definition memory.c:9
struct rate_event_s rate_event_t
The useful part of memory backend is in events, lock: together they describe what is pending,...
🧠 In-memory rate limiting backend interface
const char * rate_limiter_event_type_string(rate_event_type_t event_type)
Get event type string for logging.
Definition rate_limit.c:194
const rate_limit_config_t DEFAULT_RATE_LIMITS[RATE_EVENT_MAX]
Default rate limits for each event type.
Definition rate_limit.c:33
rate_event_type_t
One sliding-window rate-limit policy.
Definition rate_limit.h:48
🔤 String Manipulation and Shell Escaping Utilities
Memory backend data.
Definition memory.c:49
mutex_t lock
Mutex for thread safety.
Definition memory.c:51
rate_event_t * events
Hash table of rate events.
Definition memory.c:50
The useful part of memory backend is in events, lock: together they describe what is pending,...
Definition memory.c:37
char key[256]
Hash key: "ip_address:event_type".
Definition memory.c:38
size_t count
Number of events in buffer.
Definition memory.c:40
UT_hash_handle hh
uthash handle
Definition memory.c:43
size_t capacity
Buffer capacity.
Definition memory.c:41
size_t head
Head index (oldest event).
Definition memory.c:42
uint64_t * timestamps
Array of timestamps (circular buffer).
Definition memory.c:39
One sliding-window rate-limit policy.
Definition rate_limit.h:73
uint32_t max_events
Maximum events allowed.
Definition rate_limit.h:74
uint32_t window_secs
Time window in seconds.
Definition rate_limit.h:75
Backend callbacks implementing rate-limit checks, recording, cleanup, and destruction.
Definition rate_limit.h:92
⏱️ High-precision timing utilities using sokol_time.h and uthash