BOC v1.0: RS256 auth, Ledger integration, Prometheus metrics, CI/CD, backup
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -O2 -fPIC -D_GNU_SOURCE
|
||||
LDFLAGS = -shared -lpthread -lrt
|
||||
|
||||
TARGET = libcrt.so
|
||||
OBJS = shm.o ringbuf.o cache.o
|
||||
|
||||
.PHONY: all clean test
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS)
|
||||
$(CC) $(LDFLAGS) -o $@ $^
|
||||
|
||||
%.o: %.c %.h
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
test: test_crt
|
||||
./test_crt
|
||||
|
||||
test_crt: test_crt.c $(TARGET)
|
||||
$(CC) -o $@ $< -L. -lcrt -Wl,-rpath,.
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(TARGET) test_crt
|
||||
@@ -0,0 +1,220 @@
|
||||
#include "cache.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
|
||||
struct crt_cache {
|
||||
crt_cache_entry_t** buckets;
|
||||
size_t bucket_count;
|
||||
size_t max_entries;
|
||||
size_t current_entries;
|
||||
time_t default_ttl;
|
||||
pthread_rwlock_t lock;
|
||||
};
|
||||
|
||||
static size_t hash_key(const char* key) {
|
||||
size_t hash = 5381;
|
||||
int c;
|
||||
while ((c = *key++)) {
|
||||
hash = ((hash << 5) + hash) + c;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
crt_cache_t* crt_cache_create(size_t max_entries, time_t default_ttl_sec) {
|
||||
crt_cache_t* cache = calloc(1, sizeof(crt_cache_t));
|
||||
if (!cache) return NULL;
|
||||
|
||||
cache->bucket_count = 1024;
|
||||
cache->buckets = calloc(cache->bucket_count, sizeof(crt_cache_entry_t*));
|
||||
if (!cache->buckets) {
|
||||
free(cache);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cache->max_entries = max_entries;
|
||||
cache->default_ttl = default_ttl_sec;
|
||||
cache->current_entries = 0;
|
||||
pthread_rwlock_init(&cache->lock, NULL);
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
void crt_cache_destroy(crt_cache_t* cache) {
|
||||
if (!cache) return;
|
||||
crt_cache_clear(cache);
|
||||
free(cache->buckets);
|
||||
pthread_rwlock_destroy(&cache->lock);
|
||||
free(cache);
|
||||
}
|
||||
|
||||
bool crt_cache_set(crt_cache_t* cache, const char* key, const void* data, size_t len) {
|
||||
return crt_cache_set_ttl(cache, key, data, len, cache->default_ttl);
|
||||
}
|
||||
|
||||
bool crt_cache_set_ttl(crt_cache_t* cache, const char* key, const void* data, size_t len, time_t ttl_sec) {
|
||||
if (!cache || !key || !data || len == 0) return false;
|
||||
|
||||
pthread_rwlock_wrlock(&cache->lock);
|
||||
|
||||
if (cache->current_entries >= cache->max_entries) {
|
||||
crt_cache_evict_expired(cache);
|
||||
}
|
||||
|
||||
size_t idx = hash_key(key) % cache->bucket_count;
|
||||
crt_cache_entry_t* entry = cache->buckets[idx];
|
||||
|
||||
while (entry) {
|
||||
if (strcmp(entry->key, key) == 0) {
|
||||
free(entry->data);
|
||||
entry->data = malloc(len);
|
||||
if (!entry->data) {
|
||||
pthread_rwlock_unlock(&cache->lock);
|
||||
return false;
|
||||
}
|
||||
memcpy(entry->data, data, len);
|
||||
entry->len = len;
|
||||
entry->expires_at = time(NULL) + ttl_sec;
|
||||
pthread_rwlock_unlock(&cache->lock);
|
||||
return true;
|
||||
}
|
||||
entry = entry->next;
|
||||
}
|
||||
|
||||
entry = malloc(sizeof(crt_cache_entry_t));
|
||||
if (!entry) {
|
||||
pthread_rwlock_unlock(&cache->lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
strncpy(entry->key, key, sizeof(entry->key) - 1);
|
||||
entry->data = malloc(len);
|
||||
if (!entry->data) {
|
||||
free(entry);
|
||||
pthread_rwlock_unlock(&cache->lock);
|
||||
return false;
|
||||
}
|
||||
memcpy(entry->data, data, len);
|
||||
entry->len = len;
|
||||
entry->expires_at = time(NULL) + ttl_sec;
|
||||
entry->next = cache->buckets[idx];
|
||||
cache->buckets[idx] = entry;
|
||||
cache->current_entries++;
|
||||
|
||||
pthread_rwlock_unlock(&cache->lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool crt_cache_get(crt_cache_t* cache, const char* key, void* out, size_t* len) {
|
||||
if (!cache || !key || !out || !len) return false;
|
||||
|
||||
pthread_rwlock_rdlock(&cache->lock);
|
||||
|
||||
size_t idx = hash_key(key) % cache->bucket_count;
|
||||
crt_cache_entry_t* entry = cache->buckets[idx];
|
||||
time_t now = time(NULL);
|
||||
|
||||
while (entry) {
|
||||
if (strcmp(entry->key, key) == 0) {
|
||||
if (entry->expires_at < now) {
|
||||
pthread_rwlock_unlock(&cache->lock);
|
||||
crt_cache_delete(cache, key);
|
||||
return false;
|
||||
}
|
||||
memcpy(out, entry->data, entry->len);
|
||||
*len = entry->len;
|
||||
pthread_rwlock_unlock(&cache->lock);
|
||||
return true;
|
||||
}
|
||||
entry = entry->next;
|
||||
}
|
||||
|
||||
pthread_rwlock_unlock(&cache->lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool crt_cache_delete(crt_cache_t* cache, const char* key) {
|
||||
if (!cache || !key) return false;
|
||||
|
||||
pthread_rwlock_wrlock(&cache->lock);
|
||||
|
||||
size_t idx = hash_key(key) % cache->bucket_count;
|
||||
crt_cache_entry_t* entry = cache->buckets[idx];
|
||||
crt_cache_entry_t* prev = NULL;
|
||||
|
||||
while (entry) {
|
||||
if (strcmp(entry->key, key) == 0) {
|
||||
if (prev) {
|
||||
prev->next = entry->next;
|
||||
} else {
|
||||
cache->buckets[idx] = entry->next;
|
||||
}
|
||||
free(entry->data);
|
||||
free(entry);
|
||||
cache->current_entries--;
|
||||
pthread_rwlock_unlock(&cache->lock);
|
||||
return true;
|
||||
}
|
||||
prev = entry;
|
||||
entry = entry->next;
|
||||
}
|
||||
|
||||
pthread_rwlock_unlock(&cache->lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
void crt_cache_clear(crt_cache_t* cache) {
|
||||
if (!cache) return;
|
||||
|
||||
pthread_rwlock_wrlock(&cache->lock);
|
||||
|
||||
for (size_t i = 0; i < cache->bucket_count; i++) {
|
||||
crt_cache_entry_t* entry = cache->buckets[i];
|
||||
while (entry) {
|
||||
crt_cache_entry_t* next = entry->next;
|
||||
free(entry->data);
|
||||
free(entry);
|
||||
entry = next;
|
||||
}
|
||||
cache->buckets[i] = NULL;
|
||||
}
|
||||
cache->current_entries = 0;
|
||||
|
||||
pthread_rwlock_unlock(&cache->lock);
|
||||
}
|
||||
|
||||
size_t crt_cache_size(crt_cache_t* cache) {
|
||||
if (!cache) return 0;
|
||||
pthread_rwlock_rdlock(&cache->lock);
|
||||
size_t size = cache->current_entries;
|
||||
pthread_rwlock_unlock(&cache->lock);
|
||||
return size;
|
||||
}
|
||||
|
||||
void crt_cache_evict_expired(crt_cache_t* cache) {
|
||||
if (!cache) return;
|
||||
|
||||
time_t now = time(NULL);
|
||||
|
||||
for (size_t i = 0; i < cache->bucket_count; i++) {
|
||||
crt_cache_entry_t* entry = cache->buckets[i];
|
||||
crt_cache_entry_t* prev = NULL;
|
||||
|
||||
while (entry) {
|
||||
crt_cache_entry_t* next = entry->next;
|
||||
if (entry->expires_at < now) {
|
||||
if (prev) {
|
||||
prev->next = next;
|
||||
} else {
|
||||
cache->buckets[i] = next;
|
||||
}
|
||||
free(entry->data);
|
||||
free(entry);
|
||||
cache->current_entries--;
|
||||
} else {
|
||||
prev = entry;
|
||||
}
|
||||
entry = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef CRT_CACHE_H
|
||||
#define CRT_CACHE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct crt_cache crt_cache_t;
|
||||
typedef struct crt_cache_entry {
|
||||
char key[256];
|
||||
uint8_t* data;
|
||||
size_t len;
|
||||
time_t expires_at;
|
||||
struct crt_cache_entry* next;
|
||||
} crt_cache_entry_t;
|
||||
|
||||
crt_cache_t* crt_cache_create(size_t max_entries, time_t default_ttl_sec);
|
||||
void crt_cache_destroy(crt_cache_t* cache);
|
||||
|
||||
bool crt_cache_set(crt_cache_t* cache, const char* key, const void* data, size_t len);
|
||||
bool crt_cache_set_ttl(crt_cache_t* cache, const char* key, const void* data, size_t len, time_t ttl_sec);
|
||||
bool crt_cache_get(crt_cache_t* cache, const char* key, void* out, size_t* len);
|
||||
bool crt_cache_delete(crt_cache_t* cache, const char* key);
|
||||
void crt_cache_clear(crt_cache_t* cache);
|
||||
size_t crt_cache_size(crt_cache_t* cache);
|
||||
void crt_cache_evict_expired(crt_cache_t* cache);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,149 @@
|
||||
#include "ringbuf.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
struct crt_ringbuf {
|
||||
uint8_t* buffer;
|
||||
size_t capacity;
|
||||
size_t head;
|
||||
size_t tail;
|
||||
atomic_size_t count;
|
||||
};
|
||||
|
||||
crt_ringbuf_t* crt_ringbuf_create(size_t capacity) {
|
||||
crt_ringbuf_t* rb = calloc(1, sizeof(crt_ringbuf_t));
|
||||
if (!rb) return NULL;
|
||||
|
||||
rb->buffer = malloc(capacity);
|
||||
if (!rb->buffer) {
|
||||
free(rb);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rb->capacity = capacity;
|
||||
rb->head = 0;
|
||||
rb->tail = 0;
|
||||
atomic_init(&rb->count, 0);
|
||||
|
||||
return rb;
|
||||
}
|
||||
|
||||
void crt_ringbuf_destroy(crt_ringbuf_t* rb) {
|
||||
if (!rb) return;
|
||||
free(rb->buffer);
|
||||
free(rb);
|
||||
}
|
||||
|
||||
bool crt_ringbuf_push(crt_ringbuf_t* rb, const void* data, size_t len) {
|
||||
if (!rb || !data || len == 0) return false;
|
||||
if (len + sizeof(size_t) > rb->capacity) return false;
|
||||
|
||||
size_t current = atomic_load(&rb->count);
|
||||
if (current >= rb->capacity - len - sizeof(size_t)) return false;
|
||||
|
||||
size_t head = rb->head;
|
||||
size_t needed = len + sizeof(size_t);
|
||||
|
||||
if (head + needed <= rb->capacity) {
|
||||
memcpy(rb->buffer + head, &len, sizeof(size_t));
|
||||
memcpy(rb->buffer + head + sizeof(size_t), data, len);
|
||||
rb->head = (head + needed) % rb->capacity;
|
||||
} else {
|
||||
size_t first_part = rb->capacity - head;
|
||||
if (first_part >= sizeof(size_t)) {
|
||||
memcpy(rb->buffer + head, &len, sizeof(size_t));
|
||||
memcpy(rb->buffer + head + sizeof(size_t), data, first_part - sizeof(size_t));
|
||||
memcpy(rb->buffer, (uint8_t*)data + first_part - sizeof(size_t), len - (first_part - sizeof(size_t)));
|
||||
} else {
|
||||
memcpy(rb->buffer + head, &len, first_part);
|
||||
memcpy(rb->buffer, (uint8_t*)&len + first_part, sizeof(size_t) - first_part);
|
||||
memcpy(rb->buffer + sizeof(size_t) - first_part, data, len);
|
||||
}
|
||||
rb->head = needed - first_part;
|
||||
}
|
||||
|
||||
atomic_fetch_add(&rb->count, needed);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool crt_ringbuf_pop(crt_ringbuf_t* rb, void* out, size_t* len) {
|
||||
if (!rb || !out || !len) return false;
|
||||
if (atomic_load(&rb->count) == 0) return false;
|
||||
|
||||
size_t tail = rb->tail;
|
||||
size_t stored_len;
|
||||
|
||||
if (tail + sizeof(size_t) <= rb->capacity) {
|
||||
memcpy(&stored_len, rb->buffer + tail, sizeof(size_t));
|
||||
} else {
|
||||
size_t first_part = rb->capacity - tail;
|
||||
memcpy(&stored_len, rb->buffer + tail, first_part);
|
||||
memcpy((uint8_t*)&stored_len + first_part, rb->buffer, sizeof(size_t) - first_part);
|
||||
}
|
||||
|
||||
size_t total_size = stored_len + sizeof(size_t);
|
||||
if (tail + total_size <= rb->capacity) {
|
||||
memcpy(out, rb->buffer + tail + sizeof(size_t), stored_len);
|
||||
} else {
|
||||
size_t first_part = rb->capacity - tail - sizeof(size_t);
|
||||
memcpy(out, rb->buffer + tail + sizeof(size_t), first_part);
|
||||
memcpy((uint8_t*)out + first_part, rb->buffer, stored_len - first_part);
|
||||
}
|
||||
|
||||
rb->tail = (tail + total_size) % rb->capacity;
|
||||
atomic_fetch_sub(&rb->count, total_size);
|
||||
*len = stored_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool crt_ringbuf_peek(crt_ringbuf_t* rb, void* out, size_t* len) {
|
||||
if (!rb || !out || !len) return false;
|
||||
if (atomic_load(&rb->count) == 0) return false;
|
||||
|
||||
size_t tail = rb->tail;
|
||||
size_t stored_len;
|
||||
|
||||
if (tail + sizeof(size_t) <= rb->capacity) {
|
||||
memcpy(&stored_len, rb->buffer + tail, sizeof(size_t));
|
||||
} else {
|
||||
size_t first_part = rb->capacity - tail;
|
||||
memcpy(&stored_len, rb->buffer + tail, first_part);
|
||||
memcpy((uint8_t*)&stored_len + first_part, rb->buffer, sizeof(size_t) - first_part);
|
||||
}
|
||||
|
||||
size_t total_size = stored_len + sizeof(size_t);
|
||||
if (tail + total_size <= rb->capacity) {
|
||||
memcpy(out, rb->buffer + tail + sizeof(size_t), stored_len);
|
||||
} else {
|
||||
size_t first_part = rb->capacity - tail - sizeof(size_t);
|
||||
memcpy(out, rb->buffer + tail + sizeof(size_t), first_part);
|
||||
memcpy((uint8_t*)out + first_part, rb->buffer, stored_len - first_part);
|
||||
}
|
||||
|
||||
*len = stored_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t crt_ringbuf_count(crt_ringbuf_t* rb) {
|
||||
return rb ? atomic_load(&rb->count) : 0;
|
||||
}
|
||||
|
||||
size_t crt_ringbuf_capacity(crt_ringbuf_t* rb) {
|
||||
return rb ? rb->capacity : 0;
|
||||
}
|
||||
|
||||
bool crt_ringbuf_empty(crt_ringbuf_t* rb) {
|
||||
return rb ? atomic_load(&rb->count) == 0 : true;
|
||||
}
|
||||
|
||||
bool crt_ringbuf_full(crt_ringbuf_t* rb) {
|
||||
return rb ? atomic_load(&rb->count) >= rb->capacity : true;
|
||||
}
|
||||
|
||||
void crt_ringbuf_clear(crt_ringbuf_t* rb) {
|
||||
if (!rb) return;
|
||||
rb->head = 0;
|
||||
rb->tail = 0;
|
||||
atomic_store(&rb->count, 0);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef CRT_RINGBUF_H
|
||||
#define CRT_RINGBUF_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct crt_ringbuf crt_ringbuf_t;
|
||||
|
||||
crt_ringbuf_t* crt_ringbuf_create(size_t capacity);
|
||||
void crt_ringbuf_destroy(crt_ringbuf_t* rb);
|
||||
|
||||
bool crt_ringbuf_push(crt_ringbuf_t* rb, const void* data, size_t len);
|
||||
bool crt_ringbuf_pop(crt_ringbuf_t* rb, void* out, size_t* len);
|
||||
bool crt_ringbuf_peek(crt_ringbuf_t* rb, void* out, size_t* len);
|
||||
|
||||
size_t crt_ringbuf_count(crt_ringbuf_t* rb);
|
||||
size_t crt_ringbuf_capacity(crt_ringbuf_t* rb);
|
||||
bool crt_ringbuf_empty(crt_ringbuf_t* rb);
|
||||
bool crt_ringbuf_full(crt_ringbuf_t* rb);
|
||||
void crt_ringbuf_clear(crt_ringbuf_t* rb);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
#include "shm.h"
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
struct crt_shm {
|
||||
int fd;
|
||||
void* ptr;
|
||||
size_t size;
|
||||
char name[256];
|
||||
};
|
||||
|
||||
crt_shm_t* crt_shm_create(const char* name, size_t size) {
|
||||
crt_shm_t* shm = calloc(1, sizeof(crt_shm_t));
|
||||
if (!shm) return NULL;
|
||||
|
||||
strncpy(shm->name, name, sizeof(shm->name) - 1);
|
||||
shm->size = size;
|
||||
|
||||
shm->fd = shm_open(name, O_CREAT | O_RDWR, 0666);
|
||||
if (shm->fd < 0) {
|
||||
free(shm);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ftruncate(shm->fd, size) < 0) {
|
||||
close(shm->fd);
|
||||
shm_unlink(name);
|
||||
free(shm);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
shm->ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, shm->fd, 0);
|
||||
if (shm->ptr == MAP_FAILED) {
|
||||
close(shm->fd);
|
||||
shm_unlink(name);
|
||||
free(shm);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset(shm->ptr, 0, size);
|
||||
return shm;
|
||||
}
|
||||
|
||||
crt_shm_t* crt_shm_open(const char* name) {
|
||||
crt_shm_t* shm = calloc(1, sizeof(crt_shm_t));
|
||||
if (!shm) return NULL;
|
||||
|
||||
strncpy(shm->name, name, sizeof(shm->name) - 1);
|
||||
|
||||
shm->fd = shm_open(name, O_RDWR, 0666);
|
||||
if (shm->fd < 0) {
|
||||
free(shm);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (fstat(shm->fd, &st) < 0) {
|
||||
close(shm->fd);
|
||||
free(shm);
|
||||
return NULL;
|
||||
}
|
||||
shm->size = st.st_size;
|
||||
|
||||
shm->ptr = mmap(NULL, shm->size, PROT_READ | PROT_WRITE, MAP_SHARED, shm->fd, 0);
|
||||
if (shm->ptr == MAP_FAILED) {
|
||||
close(shm->fd);
|
||||
free(shm);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return shm;
|
||||
}
|
||||
|
||||
void crt_shm_close(crt_shm_t* shm) {
|
||||
if (!shm) return;
|
||||
if (shm->ptr && shm->ptr != MAP_FAILED) {
|
||||
munmap(shm->ptr, shm->size);
|
||||
}
|
||||
if (shm->fd >= 0) {
|
||||
close(shm->fd);
|
||||
}
|
||||
free(shm);
|
||||
}
|
||||
|
||||
void* crt_shm_ptr(crt_shm_t* shm) {
|
||||
return shm ? shm->ptr : NULL;
|
||||
}
|
||||
|
||||
size_t crt_shm_size(crt_shm_t* shm) {
|
||||
return shm ? shm->size : 0;
|
||||
}
|
||||
|
||||
int crt_shm_resize(crt_shm_t* shm, size_t new_size) {
|
||||
if (!shm || !shm->ptr) return -1;
|
||||
|
||||
if (munmap(shm->ptr, shm->size) < 0) return -1;
|
||||
if (ftruncate(shm->fd, new_size) < 0) return -1;
|
||||
|
||||
shm->ptr = mmap(NULL, new_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm->fd, 0);
|
||||
if (shm->ptr == MAP_FAILED) return -1;
|
||||
|
||||
shm->size = new_size;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef CRT_SHM_H
|
||||
#define CRT_SHM_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct crt_shm crt_shm_t;
|
||||
|
||||
crt_shm_t* crt_shm_create(const char* name, size_t size);
|
||||
crt_shm_t* crt_shm_open(const char* name);
|
||||
void crt_shm_close(crt_shm_t* shm);
|
||||
|
||||
void* crt_shm_ptr(crt_shm_t* shm);
|
||||
size_t crt_shm_size(crt_shm_t* shm);
|
||||
int crt_shm_resize(crt_shm_t* shm, size_t new_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user