v2.1.2 - Fix async REQ callback leak and validate Phase 2+3 subscription offload path

This commit is contained in:
Laan Tungir
2026-04-01 11:01:38 -04:00
parent 17be9c2b03
commit bb64651a0c
15 changed files with 1530 additions and 253 deletions
+194
View File
@@ -0,0 +1,194 @@
# Main Thread CPU Offload Plan
## Problem Statement
The main `c_relay` thread consumes ~56% CPU while the DB worker threads (`db-read-1..4`, `db-write`) sit near 0%. All protocol handling, JSON parsing, event validation, subscription matching, and message queueing happens synchronously inside `nostr_relay_callback()` on the main libwebsockets event-loop thread.
## Current Architecture
```mermaid
flowchart TD
LWS[lws_service - main thread] --> CB[nostr_relay_callback]
CB --> PARSE[JSON parse - cJSON_Parse]
CB --> VALIDATE[Signature verify - nostr_validate_unified_request]
CB --> STORE[store_event - builds payload]
STORE --> SYNC_WRITE[thread_pool_execute_store_event_sync]
SYNC_WRITE --> |blocks main thread| DB_WRITE[db-write thread]
DB_WRITE --> |signal| SYNC_WRITE
CB --> REQ[handle_req_message - builds SQL]
REQ --> SYNC_READ[thread_pool_execute_req_sync]
SYNC_READ --> |blocks main thread| DB_READ[db-read-N thread]
DB_READ --> |signal| SYNC_READ
CB --> BROADCAST[broadcast_event_to_subscriptions]
BROADCAST --> QUEUE[queue_message per subscriber]
CB --> WRITEABLE[LWS_CALLBACK_SERVER_WRITEABLE]
WRITEABLE --> DRAIN[process_message_queue - lws_write]
```
### Why the main thread is hot
| Work item | Where | Cost |
|-----------|-------|------|
| JSON parsing | `cJSON_Parse` in `LWS_CALLBACK_RECEIVE` | Medium - per message |
| Signature verification | `nostr_validate_unified_request` - ed25519 crypto | **High** - per EVENT |
| SQL query building | `handle_req_message` filter-to-SQL loop | Low-Medium |
| Sync DB wait | `thread_pool_execute_*_sync` - pthread_cond_wait | Blocks but yields CPU |
| Result iteration + expiration check | Row loop in `handle_req_message` with `cJSON_Parse` per row | Medium-High for large result sets |
| Subscription matching | `broadcast_event_to_subscriptions` - filter matching | Medium - scales with subscriber count |
| Message serialization + queueing | `snprintf` + `queue_message_take_ownership` per subscriber | Medium |
| Config lookups | `get_config_int/bool` called repeatedly in hot paths | Low but frequent |
### Key constraint: libwebsockets is single-threaded
libwebsockets requires that **all `lws_write`, `lws_callback_on_writable`, and `lws_close_reason` calls happen from the service thread** (the thread running `lws_service`). This means we cannot directly write to WebSocket connections from worker threads. However, we **can** do computation on worker threads and post results back to the main thread for I/O.
## Offload Strategy
### Phase 1: Async EVENT Processing (Highest Impact)
Convert EVENT handling from synchronous to async. Currently the main thread does: parse → validate → store → broadcast, all blocking. Instead:
1. **Main thread**: Parse JSON (fast), extract event ID for dedup check, then submit a job to a new "event processing" worker thread
2. **Worker thread**: Signature verification (expensive crypto), store_event (already goes to DB thread), prepare broadcast payload
3. **Main thread callback**: Receive result via `wake_loop_cb` + `lws_cancel_service`, send OK response and broadcast to subscribers
```mermaid
flowchart TD
LWS[lws_service - main thread] --> RECEIVE[LWS_CALLBACK_RECEIVE]
RECEIVE --> PARSE[JSON parse + dedup check]
PARSE --> SUBMIT[Submit to event-worker queue]
SUBMIT --> LWS
WORKER[event-worker thread] --> VERIFY[Signature verification]
VERIFY --> STORE_DB[store_event via DB pool]
STORE_DB --> PREP[Prepare broadcast payload]
PREP --> RESULT_Q[Push result to completion queue]
RESULT_Q --> WAKE[lws_cancel_service]
LWS2[lws_service wakes] --> POLL[Poll completion queue]
POLL --> OK[Send OK response via queue_message]
POLL --> BCAST[broadcast_event_to_subscriptions]
```
**What this offloads**: ed25519 signature verification (~the most expensive per-event operation), event classification, tag serialization, and the synchronous DB store wait.
**What stays on main thread**: JSON parse (needed to extract event ID for dedup), OK response writing, broadcast fan-out (requires lws access).
### Phase 2: Async REQ Query Execution (Medium Impact)
Convert REQ handling from sync to async:
1. **Main thread**: Parse filters, build SQL, create subscription, submit query job
2. **DB reader thread**: Execute query (already happens, but currently blocks main thread via `_sync`)
3. **Main thread callback**: Iterate results, queue EVENT messages, send EOSE
This is simpler than Phase 1 because the thread pool already supports async submission via `thread_pool_submit_read` with a `result_cb`. The `_sync` wrappers just add a condvar wait on top. We need to:
- Use `thread_pool_submit_read` directly instead of `thread_pool_execute_req_sync`
- Store pending REQ context (sub_id, wsi, pss) so the callback can complete the work
- In the result callback, push results to a completion queue and call `lws_cancel_service`
- On the main thread, drain the completion queue and send EVENT + EOSE messages
### Phase 3: Reduce Per-Row Overhead in REQ Results (Low-Medium Impact)
Currently each row from a REQ query gets `cJSON_Parse` just to check NIP-40 expiration. This is wasteful:
- Option A: Add an `expiration` column to the events table so expiration filtering can be done in SQL
- Option B: Store expiration timestamp in a fast-parse format (extract during INSERT, store as integer column)
- Option C: Use string search on the raw JSON for the expiration tag instead of full parse
### Phase 4: Config Value Caching (Low Impact, Easy Win)
`get_config_int` and `get_config_bool` are called on every message in hot paths. These do SQLite queries. Cache config values in memory with a TTL or invalidation signal, so the main loop only refreshes them periodically (already done for `debug_level` every 60s — extend to all hot-path config values).
## Implementation Priority
| Phase | Impact | Risk | Complexity |
|-------|--------|------|------------|
| Phase 1: Async EVENT | **High** - removes crypto from main thread | Medium - async state management | Medium-High |
| Phase 2: Async REQ | **Medium** - unblocks main thread during queries | Low - infrastructure exists | Medium |
| Phase 3: Expiration optimization | **Low-Medium** - reduces per-row parse cost | Low | Low |
| Phase 4: Config caching | **Low** - reduces DB round-trips | Very Low | Low |
## Detailed Design: Phase 1 (Async EVENT Processing)
### New Components
#### Completion Queue (`src/completion_queue.h/.c`)
A thread-safe FIFO queue for posting results from worker threads back to the main thread:
```c
typedef struct {
int type; // COMPLETION_TYPE_EVENT_RESULT, COMPLETION_TYPE_REQ_RESULT, etc.
void* data; // Type-specific result data
struct lws* wsi; // Target WebSocket connection
void* pss; // Per-session data
} completion_item_t;
int completion_queue_init(void);
int completion_queue_push(completion_item_t* item);
completion_item_t* completion_queue_pop(void); // Non-blocking
void completion_queue_shutdown(void);
```
#### Event Worker Thread
A dedicated pthread that processes EVENT validation/storage:
```c
typedef struct {
cJSON* event; // Parsed event JSON - ownership transferred
cJSON* full_message; // Full message JSON for context
struct lws* wsi;
void* pss;
char sub_id[64]; // For response routing
} event_work_item_t;
```
#### Main Loop Integration
Add a completion queue drain step to the main event loop. After `lws_service` returns (either from timeout or `lws_cancel_service` wake), check the completion queue:
```c
while (g_server_running && !g_shutdown_flag) {
int result = lws_service(ws_context, 1000);
// NEW: Drain completion queue
completion_item_t* item;
while ((item = completion_queue_pop()) != NULL) {
process_completion(item); // Send OK, broadcast, etc.
free(item);
}
// ... existing periodic checks ...
}
```
### Changes to Existing Code
1. **`nostr_relay_callback` EVENT path**: After JSON parse and dedup check, instead of calling `nostr_validate_unified_request` + `store_event` + `broadcast_event_to_subscriptions` synchronously, submit an `event_work_item_t` to the event worker queue and return 0 immediately.
2. **`store_event`**: No changes needed — it already uses `thread_pool_execute_store_event_sync` which will run on the DB writer thread. The event worker thread will call it.
3. **`broadcast_event_to_subscriptions`**: No changes needed — it will be called from the main thread when processing the completion item, which is correct since it calls `queue_message_take_ownership` (requires lws thread).
### Thread Safety Considerations
- The `cJSON* event` object must be fully owned by the worker thread during processing. The main thread must not access it after submission.
- The `wsi` and `pss` pointers could become invalid if the client disconnects while the event is being processed. The completion handler must validate that the connection is still alive before sending the OK response.
- A generation counter or epoch on `pss` can detect stale references.
## What Cannot Be Offloaded
- **`lws_write` / `queue_message`**: Must happen on the lws service thread
- **`lws_callback_on_writable`**: Must happen on the lws service thread
- **`lws_close_reason`**: Must happen on the lws service thread
- **Subscription list iteration for broadcast**: Accesses `lws_wsi_user` which is lws-internal
These are fundamental libwebsockets constraints. The pattern is always: do computation off-thread, post result to completion queue, wake main thread, do I/O on main thread.
## Expected Impact
With Phase 1 alone, the main thread would no longer perform:
- ed25519 signature verification (~100-500μs per event depending on CPU)
- Synchronous DB store wait (~50-200μs per event)
- Event classification, tag serialization, JSON serialization for storage
This should reduce main-thread CPU by roughly 30-50% for EVENT-heavy workloads, shifting that work to the event worker thread and DB threads.
+1 -1
View File
@@ -1 +1 @@
513827
567688
+54 -10
View File
@@ -13,17 +13,58 @@
extern sqlite3* g_db;
extern char g_database_path[512];
// Optional per-thread connection override (used by thread pool workers)
static __thread sqlite3* g_thread_db = NULL;
static sqlite3* db_active_connection(void) {
return g_thread_db ? g_thread_db : g_db;
}
typedef struct db_stmt {
sqlite3_stmt* stmt;
} db_stmt_t;
int db_init(const char* connection_string) {
if (!connection_string || connection_string[0] == '\0') return DB_MISUSE;
if (g_db) return DB_OK;
int rc = sqlite3_open(connection_string, &g_db);
if (rc != SQLITE_OK) {
if (g_db) {
sqlite3_close(g_db);
g_db = NULL;
}
return DB_ERROR;
}
strncpy(g_database_path, connection_string, sizeof(g_database_path) - 1);
g_database_path[sizeof(g_database_path) - 1] = '\0';
return DB_OK;
}
void db_close(void) {
if (!g_db) return;
sqlite3_close(g_db);
g_db = NULL;
}
int db_set_thread_connection(void* connection) {
g_thread_db = (sqlite3*)connection;
return DB_OK;
}
void db_clear_thread_connection(void) {
g_thread_db = NULL;
}
int db_is_available(void) {
return g_db != NULL;
return db_active_connection() != NULL;
}
const char* db_last_error(void) {
if (!g_db) return "database not available";
return sqlite3_errmsg(g_db);
sqlite3* db = db_active_connection();
if (!db) return "database not available";
return sqlite3_errmsg(db);
}
const char* db_get_database_path(void) {
@@ -31,10 +72,11 @@ const char* db_get_database_path(void) {
}
int db_prepare(const char* sql, db_stmt_t** out_stmt) {
if (!g_db || !sql || !out_stmt) return DB_MISUSE;
sqlite3* db = db_active_connection();
if (!db || !sql || !out_stmt) return DB_MISUSE;
sqlite3_stmt* raw_stmt = NULL;
int rc = sqlite3_prepare_v2(g_db, sql, -1, &raw_stmt, NULL);
int rc = sqlite3_prepare_v2(db, sql, -1, &raw_stmt, NULL);
if (rc != SQLITE_OK) return rc;
db_stmt_t* wrapper = (db_stmt_t*)malloc(sizeof(db_stmt_t));
@@ -372,10 +414,11 @@ int db_count_active_whitelist_rules(void) {
}
int db_count_with_sql(const char* sql, const char** bind_params, int bind_param_count, int* out_count) {
if (!g_db || !sql || !out_count) return -1;
sqlite3* db = db_active_connection();
if (!db || !sql || !out_count) return -1;
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
return -1;
}
@@ -886,7 +929,8 @@ int db_insert_event_with_json(const char* id, const char* pubkey, long long crea
int kind, const char* event_type, const char* content,
const char* sig, const char* tags_json, const char* event_json,
int* out_step_rc, int* out_extended_errcode) {
if (!g_db || !id || !pubkey || !event_type || !content || !sig || !tags_json || !event_json) {
sqlite3* db = db_active_connection();
if (!db || !id || !pubkey || !event_type || !content || !sig || !tags_json || !event_json) {
return -1;
}
@@ -895,7 +939,7 @@ int db_insert_event_with_json(const char* id, const char* pubkey, long long crea
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
return -1;
}
@@ -910,7 +954,7 @@ int db_insert_event_with_json(const char* id, const char* pubkey, long long crea
sqlite3_bind_text(stmt, 9, event_json, -1, SQLITE_TRANSIENT);
int step_rc = sqlite3_step(stmt);
int extended_errcode = sqlite3_extended_errcode(g_db);
int extended_errcode = sqlite3_extended_errcode(db);
sqlite3_finalize(stmt);
if (out_step_rc) {
+6
View File
@@ -6,10 +6,16 @@
#include <cjson/cJSON.h>
// Generic helpers
int db_init(const char* connection_string);
void db_close(void);
int db_is_available(void);
const char* db_last_error(void);
const char* db_get_database_path(void);
// Per-thread connection override (used by thread pool workers)
int db_set_thread_connection(void* connection);
void db_clear_thread_connection(void);
// DB result codes (backend-agnostic)
#define DB_OK 0
#define DB_ERROR 1
+369 -134
View File
@@ -115,6 +115,12 @@ int process_admin_event_in_config(cJSON* event, char* error_message, size_t erro
// Forward declaration for NIP-45 COUNT message handling
int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, struct per_session_data *pss);
// Forward declaration for NOTICE message support
void send_notice_message(struct lws* wsi, struct per_session_data* pss, const char* message);
// Thread pool wake callback (called by worker threads)
static void wake_event_loop_from_thread_pool(void* ctx);
// Parameter binding helpers for SQL queries
static void add_bind_param(char*** params, int* count, int* capacity, const char* value) {
if (*count >= *capacity) {
@@ -131,11 +137,308 @@ static void free_bind_params(char** params, int count) {
free(params);
}
typedef struct req_async_state {
char sub_id[SUBSCRIPTION_ID_MAX_LENGTH];
struct lws* wsi_token;
pthread_mutex_t mutex;
int pending_jobs;
} req_async_state_t;
typedef struct req_async_submit_ctx {
req_async_state_t* state;
} req_async_submit_ctx_t;
typedef struct req_async_completion {
req_async_state_t* state;
thread_pool_status_t status;
thread_pool_req_result_t* req_result;
struct req_async_completion* next;
} req_async_completion_t;
static pthread_mutex_t g_req_async_completion_mutex = PTHREAD_MUTEX_INITIALIZER;
static req_async_completion_t* g_req_async_completion_head = NULL;
static req_async_completion_t* g_req_async_completion_tail = NULL;
static void free_req_payload_main(void* p) {
thread_pool_req_payload_t* payload = (thread_pool_req_payload_t*)p;
if (!payload) return;
free(payload->sql);
if (payload->bind_params) {
for (int i = 0; i < payload->bind_param_count; i++) {
free(payload->bind_params[i]);
}
free(payload->bind_params);
}
free(payload);
}
static req_async_state_t* req_async_state_create(const char* sub_id, struct lws* wsi) {
req_async_state_t* state = calloc(1, sizeof(*state));
if (!state) {
return NULL;
}
if (sub_id) {
strncpy(state->sub_id, sub_id, sizeof(state->sub_id) - 1);
state->sub_id[sizeof(state->sub_id) - 1] = '\0';
}
state->wsi_token = wsi;
pthread_mutex_init(&state->mutex, NULL);
return state;
}
static void req_async_state_free(req_async_state_t* state) {
if (!state) return;
pthread_mutex_destroy(&state->mutex);
free(state);
}
static void req_async_state_increment_pending(req_async_state_t* state) {
if (!state) return;
pthread_mutex_lock(&state->mutex);
state->pending_jobs++;
pthread_mutex_unlock(&state->mutex);
}
static int req_async_state_decrement_pending(req_async_state_t* state) {
if (!state) return 0;
pthread_mutex_lock(&state->mutex);
if (state->pending_jobs > 0) {
state->pending_jobs--;
}
int remaining = state->pending_jobs;
pthread_mutex_unlock(&state->mutex);
return remaining;
}
static void req_async_completion_push(req_async_completion_t* completion) {
if (!completion) return;
pthread_mutex_lock(&g_req_async_completion_mutex);
completion->next = NULL;
if (!g_req_async_completion_tail) {
g_req_async_completion_head = completion;
g_req_async_completion_tail = completion;
} else {
g_req_async_completion_tail->next = completion;
g_req_async_completion_tail = completion;
}
pthread_mutex_unlock(&g_req_async_completion_mutex);
}
static req_async_completion_t* req_async_completion_pop(void) {
pthread_mutex_lock(&g_req_async_completion_mutex);
req_async_completion_t* completion = g_req_async_completion_head;
if (completion) {
g_req_async_completion_head = completion->next;
if (!g_req_async_completion_head) {
g_req_async_completion_tail = NULL;
}
}
pthread_mutex_unlock(&g_req_async_completion_mutex);
return completion;
}
static int resolve_req_async_target(req_async_state_t* state, struct lws** out_wsi, struct per_session_data** out_pss) {
if (!state || !out_wsi || !out_pss) {
return 0;
}
*out_wsi = NULL;
*out_pss = NULL;
pthread_mutex_lock(&g_subscription_manager.subscriptions_lock);
subscription_t* sub = g_subscription_manager.active_subscriptions;
while (sub) {
if (sub->active && sub->wsi == state->wsi_token && strcmp(sub->id, state->sub_id) == 0) {
*out_wsi = sub->wsi;
break;
}
sub = sub->next;
}
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
if (!*out_wsi) {
return 0;
}
*out_pss = (struct per_session_data*)lws_wsi_user(*out_wsi);
return (*out_pss != NULL);
}
static void send_eose_message(struct lws* wsi, struct per_session_data* pss, const char* sub_id) {
if (!wsi || !pss || !sub_id) {
return;
}
cJSON* eose_response = cJSON_CreateArray();
if (!eose_response) {
return;
}
cJSON_AddItemToArray(eose_response, cJSON_CreateString("EOSE"));
cJSON_AddItemToArray(eose_response, cJSON_CreateString(sub_id));
char *eose_str = cJSON_Print(eose_response);
if (eose_str) {
size_t eose_len = strlen(eose_str);
DEBUG_TRACE("WS_FRAME_SEND: type=EOSE len=%zu data=%.100s%s",
eose_len,
eose_str,
eose_len > 100 ? "..." : "");
if (queue_message(wsi, pss, eose_str, eose_len, LWS_WRITE_TEXT) != 0) {
DEBUG_ERROR("Failed to queue EOSE message");
}
free(eose_str);
}
cJSON_Delete(eose_response);
}
static void req_async_result_cb(const thread_pool_result_t* result, void* user_ctx) {
req_async_submit_ctx_t* ctx = (req_async_submit_ctx_t*)user_ctx;
if (!ctx || !ctx->state || !result) {
if (result && result->result_data) {
thread_pool_free_req_result((thread_pool_req_result_t*)result->result_data);
}
if (ctx) free(ctx);
return;
}
req_async_completion_t* completion = calloc(1, sizeof(*completion));
if (!completion) {
if (result->result_data) {
thread_pool_free_req_result((thread_pool_req_result_t*)result->result_data);
}
free(ctx);
return;
}
completion->state = ctx->state;
completion->status = result->status;
completion->req_result = (thread_pool_req_result_t*)result->result_data;
req_async_completion_push(completion);
wake_event_loop_from_thread_pool(NULL);
free(ctx);
}
static int submit_req_query_async(req_async_state_t* state, const char* sql, const char** bind_params, int bind_param_count) {
if (!state || !sql) {
return -1;
}
thread_pool_req_payload_t* payload = calloc(1, sizeof(*payload));
if (!payload) {
return -1;
}
payload->sql = strdup(sql);
payload->bind_param_count = bind_param_count;
if (!payload->sql) {
free_req_payload_main(payload);
return -1;
}
if (bind_param_count > 0) {
payload->bind_params = calloc((size_t)bind_param_count, sizeof(char*));
if (!payload->bind_params) {
free_req_payload_main(payload);
return -1;
}
for (int i = 0; i < bind_param_count; i++) {
const char* v = (bind_params && bind_params[i]) ? bind_params[i] : "";
payload->bind_params[i] = strdup(v);
if (!payload->bind_params[i]) {
free_req_payload_main(payload);
return -1;
}
}
}
req_async_submit_ctx_t* ctx = calloc(1, sizeof(*ctx));
if (!ctx) {
free_req_payload_main(payload);
return -1;
}
ctx->state = state;
thread_pool_job_t job;
memset(&job, 0, sizeof(job));
job.type = THREAD_POOL_JOB_REQ_QUERY;
job.payload = payload;
job.payload_size = sizeof(*payload);
job.payload_free = free_req_payload_main;
job.result_cb = req_async_result_cb;
job.result_cb_ctx = ctx;
req_async_state_increment_pending(state);
thread_pool_status_t submit_rc = thread_pool_submit_read(&job, NULL);
if (submit_rc != THREAD_POOL_STATUS_OK) {
req_async_state_decrement_pending(state);
free(ctx);
free_req_payload_main(payload);
return -1;
}
return 0;
}
void process_req_async_completions(void) {
req_async_completion_t* completion = NULL;
while ((completion = req_async_completion_pop()) != NULL) {
struct lws* target_wsi = NULL;
struct per_session_data* target_pss = NULL;
int has_target = resolve_req_async_target(completion->state, &target_wsi, &target_pss);
if (has_target && completion->status == THREAD_POOL_STATUS_OK && completion->req_result) {
for (int r = 0; r < completion->req_result->row_count; r++) {
const char* event_json_str = completion->req_result->event_json_rows[r];
if (!event_json_str) {
continue;
}
size_t sub_id_len = strlen(completion->state->sub_id);
size_t event_json_len = strlen(event_json_str);
size_t msg_len = 10 + sub_id_len + 3 + event_json_len + 1;
unsigned char* buf = malloc(LWS_PRE + msg_len + 1);
if (!buf) {
continue;
}
char* msg_ptr = (char*)(buf + LWS_PRE);
snprintf(msg_ptr, msg_len + 1, "[\"EVENT\",\"%s\",%s]", completion->state->sub_id, event_json_str);
size_t actual_len = strlen(msg_ptr);
if (queue_message_take_ownership(target_wsi, target_pss, buf, actual_len, LWS_WRITE_TEXT) != 0) {
DEBUG_ERROR("Failed to queue async EVENT message for sub=%s", completion->state->sub_id);
}
}
} else if (has_target && completion->status != THREAD_POOL_STATUS_OK) {
send_notice_message(target_wsi, target_pss, "error: failed to execute subscription query");
}
int remaining = req_async_state_decrement_pending(completion->state);
if (remaining == 0) {
if (has_target) {
send_eose_message(target_wsi, target_pss, completion->state->sub_id);
}
req_async_state_free(completion->state);
}
if (completion->req_result) {
thread_pool_free_req_result(completion->req_result);
}
free(completion);
}
}
// Forward declaration for enhanced admin event authorization
int is_authorized_admin_event(cJSON* event, char* error_message, size_t error_size);
// Forward declaration for NOTICE message support
void send_notice_message(struct lws* wsi, struct per_session_data* pss, const char* message);
// Forward declarations for NIP-42 authentication functions
void send_nip42_auth_challenge(struct lws* wsi, struct per_session_data* pss);
@@ -164,8 +467,6 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header);
// Forward declaration for WebSocket relay server
int start_websocket_relay(int port_override, int strict_port);
// Thread pool wake callback (called by worker threads)
static void wake_event_loop_from_thread_pool(void* ctx);
// Forward declarations for IP ban system
void ip_ban_init(void);
@@ -425,7 +726,7 @@ int init_database(const char* database_path_override) {
// Clean up stale WAL files before opening database
cleanup_stale_wal_files(db_path);
int rc = sqlite3_open(db_path, &g_db);
int rc = db_init(db_path);
if (rc != DB_OK) {
DEBUG_ERROR("Cannot open database");
DEBUG_TRACE("Exiting init_database() - failed to open database");
@@ -437,7 +738,7 @@ int init_database(const char* database_path_override) {
// Check config table row count immediately after database open
int row_count = 0;
if (db_get_config_row_count(&row_count) == 0) {
DEBUG_LOG("Config table row count immediately after sqlite3_open(): %d", row_count);
DEBUG_LOG("Config table row count immediately after db_init(): %d", row_count);
} else {
DEBUG_LOG("Config table count unavailable immediately after sqlite3_open() (table may not exist yet)");
}
@@ -600,8 +901,7 @@ void close_database() {
DEBUG_WARN("WAL checkpoint warning");
}
sqlite3_close(g_db);
g_db = NULL;
db_close();
DEBUG_LOG("Database connection closed");
}
@@ -1101,7 +1401,18 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
}
int events_sent = config_events_sent; // Start with synthetic config events
int submitted_jobs = 0;
int expiration_enabled = get_config_bool("expiration_enabled", 1);
int filter_responses = get_config_bool("expiration_filter", 1);
time_t query_now = time(NULL);
req_async_state_t* async_state = req_async_state_create(sub_id, wsi);
if (!async_state) {
DEBUG_ERROR("Failed to allocate async REQ state");
free_bind_params(bind_params, bind_param_count);
return events_sent;
}
// Process each filter in the array
for (int i = 0; i < cJSON_GetArraySize(filters); i++) {
cJSON* filter = cJSON_GetArrayItem(filters, i);
@@ -1125,13 +1436,26 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
// Build SQL query based on filter - exclude ephemeral events (kinds 20000-29999) from historical queries
// Select event_json for fast retrieval (no JSON reconstruction needed)
char sql[1024] = "SELECT event_json FROM events WHERE 1=1 AND (kind < 20000 OR kind >= 30000)";
char sql[1408] = "SELECT event_json FROM events WHERE 1=1 AND (kind < 20000 OR kind >= 30000)";
char* sql_ptr = sql + strlen(sql);
int remaining = sizeof(sql) - strlen(sql);
// Note: Expiration filtering will be done at application level
// after retrieving events to ensure compatibility with all SQLite versions
// Phase 3: push expiration filtering into SQL using indexed event_tags table.
if (expiration_enabled && filter_responses) {
snprintf(sql_ptr, remaining,
" AND NOT EXISTS (SELECT 1 FROM event_tags et_exp "
"WHERE et_exp.event_id = events.id "
"AND et_exp.tag_name = ? "
"AND CAST(et_exp.tag_value AS INTEGER) <= ?)");
sql_ptr += strlen(sql_ptr);
remaining = sizeof(sql) - strlen(sql);
add_bind_param(&bind_params, &bind_param_count, &bind_param_capacity, "expiration");
char now_buf[32];
snprintf(now_buf, sizeof(now_buf), "%lld", (long long)query_now);
add_bind_param(&bind_params, &bind_param_count, &bind_param_capacity, now_buf);
}
// Handle kinds filter
cJSON* kinds = cJSON_GetObjectItemCaseSensitive(filter, "kinds");
if (kinds && cJSON_IsArray(kinds)) {
@@ -1140,7 +1464,7 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
snprintf(sql_ptr, remaining, " AND kind IN (");
sql_ptr += strlen(sql_ptr);
remaining = sizeof(sql) - strlen(sql);
for (int k = 0; k < kind_count; k++) {
cJSON* kind = cJSON_GetArrayItem(kinds, k);
if (cJSON_IsNumber(kind)) {
@@ -1159,7 +1483,7 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
remaining = sizeof(sql) - strlen(sql);
}
}
// Handle authors filter
cJSON* authors = cJSON_GetObjectItemCaseSensitive(filter, "authors");
if (authors && cJSON_IsArray(authors)) {
@@ -1205,8 +1529,8 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
if (ids && cJSON_IsArray(ids)) {
int id_count = 0;
// Count valid ids
for (int i = 0; i < cJSON_GetArraySize(ids); i++) {
cJSON* id = cJSON_GetArrayItem(ids, i);
for (int j = 0; j < cJSON_GetArraySize(ids); j++) {
cJSON* id = cJSON_GetArrayItem(ids, j);
if (cJSON_IsString(id)) {
id_count++;
}
@@ -1216,8 +1540,8 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
sql_ptr += strlen(sql_ptr);
remaining = sizeof(sql) - strlen(sql);
for (int i = 0; i < id_count; i++) {
if (i > 0) {
for (int j = 0; j < id_count; j++) {
if (j > 0) {
snprintf(sql_ptr, remaining, ",");
sql_ptr++;
remaining--;
@@ -1231,8 +1555,8 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
remaining = sizeof(sql) - strlen(sql);
// Add id values to bind params
for (int i = 0; i < cJSON_GetArraySize(ids); i++) {
cJSON* id = cJSON_GetArrayItem(ids, i);
for (int j = 0; j < cJSON_GetArraySize(ids); j++) {
cJSON* id = cJSON_GetArrayItem(ids, j);
if (cJSON_IsString(id)) {
add_bind_param(&bind_params, &bind_param_count, &bind_param_capacity, cJSON_GetStringValue(id));
}
@@ -1251,8 +1575,8 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
if (cJSON_IsArray(filter_item)) {
int tag_value_count = 0;
// Count valid tag values
for (int i = 0; i < cJSON_GetArraySize(filter_item); i++) {
cJSON* tag_value = cJSON_GetArrayItem(filter_item, i);
for (int j = 0; j < cJSON_GetArraySize(filter_item); j++) {
cJSON* tag_value = cJSON_GetArrayItem(filter_item, j);
if (cJSON_IsString(tag_value)) {
tag_value_count++;
}
@@ -1263,8 +1587,8 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
sql_ptr += strlen(sql_ptr);
remaining = sizeof(sql) - strlen(sql);
for (int i = 0; i < tag_value_count; i++) {
if (i > 0) {
for (int j = 0; j < tag_value_count; j++) {
if (j > 0) {
snprintf(sql_ptr, remaining, ",");
sql_ptr++;
remaining--;
@@ -1279,8 +1603,8 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
// Add tag name and values to bind params
add_bind_param(&bind_params, &bind_param_count, &bind_param_capacity, tag_name);
for (int i = 0; i < cJSON_GetArraySize(filter_item); i++) {
cJSON* tag_value = cJSON_GetArrayItem(filter_item, i);
for (int j = 0; j < cJSON_GetArraySize(filter_item); j++) {
cJSON* tag_value = cJSON_GetArrayItem(filter_item, j);
if (cJSON_IsString(tag_value)) {
add_bind_param(&bind_params, &bind_param_count, &bind_param_capacity, cJSON_GetStringValue(tag_value));
}
@@ -1299,12 +1623,12 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
// Escape single quotes in search term for SQL safety
char escaped_search[256];
size_t escaped_len = 0;
for (size_t i = 0; search_term[i] && escaped_len < sizeof(escaped_search) - 1; i++) {
if (search_term[i] == '\'') {
for (size_t j = 0; search_term[j] && escaped_len < sizeof(escaped_search) - 1; j++) {
if (search_term[j] == '\'') {
escaped_search[escaped_len++] = '\'';
escaped_search[escaped_len++] = '\'';
} else {
escaped_search[escaped_len++] = search_term[i];
escaped_search[escaped_len++] = search_term[j];
}
}
escaped_search[escaped_len] = '\0';
@@ -1325,7 +1649,7 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
sql_ptr += strlen(sql_ptr);
remaining = sizeof(sql) - strlen(sql);
}
// Handle until filter
cJSON* until = cJSON_GetObjectItemCaseSensitive(filter, "until");
if (until && cJSON_IsNumber(until)) {
@@ -1333,12 +1657,12 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
sql_ptr += strlen(sql_ptr);
remaining = sizeof(sql) - strlen(sql);
}
// Add ordering and limit
snprintf(sql_ptr, remaining, " ORDER BY created_at DESC");
sql_ptr += strlen(sql_ptr);
remaining = sizeof(sql) - strlen(sql);
// Handle limit filter
cJSON* limit = cJSON_GetObjectItemCaseSensitive(filter, "limit");
if (limit && cJSON_IsNumber(limit)) {
@@ -1351,116 +1675,27 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
snprintf(sql_ptr, remaining, " LIMIT 500");
}
// Start query timing
struct timespec query_start, query_end;
clock_gettime(CLOCK_MONOTONIC, &query_start);
// Execute query through db thread pool helper and send events
thread_pool_req_result_t* req_result = NULL;
if (thread_pool_execute_req_sync(sql, (const char**)bind_params, bind_param_count, 5000, &req_result) != 0 || !req_result) {
char error_msg[256];
snprintf(error_msg, sizeof(error_msg), "Failed to execute subscription query: %s", db_last_error());
DEBUG_ERROR(error_msg);
// Log the failed query so we can see what SQL was generated
if (g_debug_level >= DEBUG_LEVEL_DEBUG) {
time_t now = time(NULL);
struct tm* tm_info = localtime(&now);
char timestamp[32];
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info);
fprintf(stderr, "[%s] [QUERY_FAILED] type=REQ sub=%s ip=%s error=%s sql=%s\n",
timestamp,
sub_id,
pss ? pss->client_ip : "N/A",
db_last_error(),
sql);
fflush(stderr);
}
// Submit async REQ query (results processed on lws thread via completion queue)
if (submit_req_query_async(async_state, sql, (const char**)bind_params, bind_param_count) != 0) {
DEBUG_ERROR("Failed to submit async REQ query for subscription %s", sub_id);
continue;
}
// Track query execution for abuse detection
submitted_jobs++;
if (pss) {
pss->db_queries_executed++;
}
// Cache config values outside the row loop (performance fix)
int expiration_enabled = get_config_bool("expiration_enabled", 1);
int filter_responses = get_config_bool("expiration_filter", 1);
int row_count = 0;
for (int r = 0; r < req_result->row_count; r++) {
const char* event_json_str = req_result->event_json_rows[r];
row_count++;
// Track rows returned for abuse detection
if (pss) {
pss->db_rows_returned++;
}
if (!event_json_str) {
DEBUG_ERROR("Event has NULL event_json field");
continue;
}
// Parse event JSON only for expiration check
cJSON* event = cJSON_Parse(event_json_str);
if (!event) {
DEBUG_ERROR("Failed to parse event_json from database");
continue;
}
// Check expiration filtering (NIP-40) at application level
// (expiration_enabled and filter_responses are cached outside the loop)
if (expiration_enabled && filter_responses) {
time_t current_time = time(NULL);
if (is_event_expired(event, current_time)) {
// Skip this expired event
cJSON_Delete(event);
continue;
}
}
// Build EVENT message using zero-copy path: allocate with LWS_PRE prefix,
// write directly, transfer ownership to queue — no memcpy.
// Format: ["EVENT","<sub_id>",<event_json>]
size_t sub_id_len = strlen(sub_id);
size_t event_json_len = strlen(event_json_str);
size_t msg_len = 10 + sub_id_len + 3 + event_json_len + 1;
unsigned char* buf = malloc(LWS_PRE + msg_len + 1);
if (buf) {
char* msg_ptr = (char*)(buf + LWS_PRE);
snprintf(msg_ptr, msg_len + 1, "[\"EVENT\",\"%s\",%s]", sub_id, event_json_str);
size_t actual_len = strlen(msg_ptr);
// queue_message_take_ownership takes buf ownership — no memcpy, no free needed here
if (queue_message_take_ownership(wsi, pss, buf, actual_len, LWS_WRITE_TEXT) != 0) {
DEBUG_ERROR("Failed to queue EVENT message for sub=%s", sub_id);
// buf already freed by queue_message_take_ownership on failure
}
}
cJSON_Delete(event);
events_sent++;
}
thread_pool_free_req_result(req_result);
// Stop query timing and log
clock_gettime(CLOCK_MONOTONIC, &query_end);
long elapsed_us = (query_end.tv_sec - query_start.tv_sec) * 1000000L +
(query_end.tv_nsec - query_start.tv_nsec) / 1000L;
log_query_execution("REQ", sub_id, pss ? pss->client_ip : NULL,
sql, elapsed_us, row_count);
}
// Cleanup bind params
free_bind_params(bind_params, bind_param_count);
return events_sent;
if (submitted_jobs == 0) {
req_async_state_free(async_state);
return events_sent;
}
return HANDLE_REQ_ASYNC_PENDING;
}
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
@@ -2042,7 +2277,7 @@ int main(int argc, char* argv[]) {
ip_ban_load_from_db();
// Optional thread pool scaffold initialization (execution wiring is future work)
int thread_pool_enabled = get_config_bool("thread_pool_enabled", 0);
int thread_pool_enabled = get_config_bool("thread_pool_enabled", 1);
int thread_pool_initialized = 0;
if (thread_pool_enabled) {
thread_pool_config_t tp_cfg;
+8 -2
View File
@@ -13,8 +13,8 @@
// Using CRELAY_ prefix to avoid conflicts with nostr_core_lib VERSION macros
#define CRELAY_VERSION_MAJOR 2
#define CRELAY_VERSION_MINOR 1
#define CRELAY_VERSION_PATCH 1
#define CRELAY_VERSION "v2.1.1"
#define CRELAY_VERSION_PATCH 2
#define CRELAY_VERSION "v2.1.2"
// Relay metadata (authoritative source for NIP-11 information)
#define RELAY_NAME "C-Relay"
@@ -28,4 +28,10 @@
#define POSTING_POLICY ""
#define PAYMENTS_URL ""
// Async REQ handler status used by websocket callback to defer EOSE until worker completion
#define HANDLE_REQ_ASYNC_PENDING (-2)
// Drains completed async REQ jobs and queues EVENT/EOSE on the lws service thread.
void process_req_async_completions(void);
#endif /* MAIN_H */
+68 -2
View File
@@ -4,7 +4,10 @@
#include "debug.h"
#include "db_ops.h"
#include <sqlite3.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -37,6 +40,8 @@ typedef struct {
thread_pool_wake_loop_cb wake_loop_cb;
void* wake_loop_ctx;
char db_path[512];
pthread_mutex_t state_mutex;
} thread_pool_state_t;
@@ -193,6 +198,26 @@ static void wake_event_loop(void) {
}
}
static sqlite3* open_worker_connection(void) {
if (g_pool.db_path[0] == '\0') {
return NULL;
}
sqlite3* db = NULL;
int rc = sqlite3_open_v2(g_pool.db_path, &db, SQLITE_OPEN_READWRITE, NULL);
if (rc != SQLITE_OK) {
if (db) {
sqlite3_close(db);
}
return NULL;
}
sqlite3_exec(db, "PRAGMA journal_mode=WAL;", NULL, NULL, NULL);
sqlite3_busy_timeout(db, 5000);
return db;
}
static void complete_job_with_result(thread_pool_job_node_t* node,
thread_pool_status_t status,
const char* message,
@@ -374,24 +399,54 @@ static void execute_write_job(thread_pool_job_node_t* node) {
}
static void* reader_worker_main(void* arg) {
(void)arg;
int reader_index = (int)(intptr_t)arg;
char thread_name[16];
snprintf(thread_name, sizeof(thread_name), "db-read-%d", reader_index);
pthread_setname_np(pthread_self(), thread_name);
sqlite3* worker_db = open_worker_connection();
if (!worker_db) {
DEBUG_ERROR("Reader worker failed to open SQLite connection");
return NULL;
}
db_set_thread_connection(worker_db);
while (g_pool.running) {
thread_pool_job_node_t* node = queue_pop(&g_pool.read_q);
if (!node) break;
execute_read_job(node);
}
db_clear_thread_connection();
sqlite3_close(worker_db);
return NULL;
}
static void* writer_worker_main(void* arg) {
(void)arg;
pthread_setname_np(pthread_self(), "db-write");
sqlite3* worker_db = open_worker_connection();
if (!worker_db) {
DEBUG_ERROR("Writer worker failed to open SQLite connection");
return NULL;
}
db_set_thread_connection(worker_db);
while (g_pool.running) {
thread_pool_job_node_t* node = queue_pop(&g_pool.write_q);
if (!node) break;
execute_write_job(node);
}
db_clear_thread_connection();
sqlite3_close(worker_db);
return NULL;
}
@@ -417,10 +472,21 @@ int thread_pool_init(const thread_pool_config_t* config) {
g_pool.next_job_id = 1;
g_pool.wake_loop_cb = config->wake_loop_cb;
g_pool.wake_loop_ctx = config->wake_loop_ctx;
const char* db_path = (config->db_path && config->db_path[0] != '\0') ? config->db_path : db_get_database_path();
if (!db_path || db_path[0] == '\0') {
free(g_pool.readers);
g_pool.readers = NULL;
pthread_mutex_unlock(&g_pool.state_mutex);
return -1;
}
strncpy(g_pool.db_path, db_path, sizeof(g_pool.db_path) - 1);
g_pool.db_path[sizeof(g_pool.db_path) - 1] = '\0';
g_pool.running = 1;
for (int i = 0; i < g_pool.reader_count; i++) {
if (pthread_create(&g_pool.readers[i], NULL, reader_worker_main, NULL) != 0) {
if (pthread_create(&g_pool.readers[i], NULL, reader_worker_main, (void*)(intptr_t)(i + 1)) != 0) {
g_pool.running = 0;
pthread_cond_broadcast(&g_pool.read_q.cond);
pthread_cond_broadcast(&g_pool.write_q.cond);
+53 -42
View File
@@ -33,6 +33,8 @@
#include "thread_pool.h" // Thread pool scaffold
#include "db_ops.h" // DB abstraction wrappers
#include "main.h" // Async REQ completion integration
// Forward declarations for logging functions
// Forward declarations for configuration functions
@@ -1286,38 +1288,41 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
DEBUG_TRACE("REQ filters validated successfully");
DEBUG_TRACE("About to call handle_req_message for subscription %s", subscription_id);
handle_req_message(subscription_id, filters, wsi, pss);
int req_result = handle_req_message(subscription_id, filters, wsi, pss);
DEBUG_TRACE("handle_req_message completed for subscription %s", subscription_id);
// Clean up the filters array we created
cJSON_Delete(filters);
DEBUG_LOG("REQ subscription %s processed, sending EOSE", subscription_id);
// Async path will send EOSE from completion handler.
if (req_result != HANDLE_REQ_ASYNC_PENDING) {
DEBUG_LOG("REQ subscription %s processed, sending EOSE", subscription_id);
// Send EOSE (End of Stored Events)
cJSON* eose_response = cJSON_CreateArray();
if (eose_response) {
cJSON_AddItemToArray(eose_response, cJSON_CreateString("EOSE"));
cJSON_AddItemToArray(eose_response, cJSON_CreateString(subscription_id));
// Send EOSE (End of Stored Events)
cJSON* eose_response = cJSON_CreateArray();
if (eose_response) {
cJSON_AddItemToArray(eose_response, cJSON_CreateString("EOSE"));
cJSON_AddItemToArray(eose_response, cJSON_CreateString(subscription_id));
char *eose_str = cJSON_Print(eose_response);
if (eose_str) {
size_t eose_len = strlen(eose_str);
char *eose_str = cJSON_Print(eose_response);
if (eose_str) {
size_t eose_len = strlen(eose_str);
// DEBUG: Log WebSocket frame details before sending
DEBUG_TRACE("WS_FRAME_SEND: type=EOSE len=%zu data=%.100s%s",
eose_len,
eose_str,
eose_len > 100 ? "..." : "");
// DEBUG: Log WebSocket frame details before sending
DEBUG_TRACE("WS_FRAME_SEND: type=EOSE len=%zu data=%.100s%s",
eose_len,
eose_str,
eose_len > 100 ? "..." : "");
// Queue message for proper libwebsockets pattern
if (queue_message(wsi, pss, eose_str, eose_len, LWS_WRITE_TEXT) != 0) {
DEBUG_ERROR("Failed to queue EOSE message");
// Queue message for proper libwebsockets pattern
if (queue_message(wsi, pss, eose_str, eose_len, LWS_WRITE_TEXT) != 0) {
DEBUG_ERROR("Failed to queue EOSE message");
}
free(eose_str);
}
free(eose_str);
cJSON_Delete(eose_response);
}
cJSON_Delete(eose_response);
}
} else {
send_notice_message(wsi, pss, "error: missing or invalid subscription ID in REQ");
@@ -2058,38 +2063,41 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
DEBUG_TRACE("REQ filters validated successfully");
DEBUG_TRACE("About to call handle_req_message for subscription %s", subscription_id);
handle_req_message(subscription_id, filters, wsi, pss);
int req_result = handle_req_message(subscription_id, filters, wsi, pss);
DEBUG_TRACE("handle_req_message completed for subscription %s", subscription_id);
// Clean up the filters array we created
cJSON_Delete(filters);
DEBUG_LOG("REQ subscription %s processed, sending EOSE", subscription_id);
// Async path will send EOSE from completion handler.
if (req_result != HANDLE_REQ_ASYNC_PENDING) {
DEBUG_LOG("REQ subscription %s processed, sending EOSE", subscription_id);
// Send EOSE (End of Stored Events)
cJSON* eose_response = cJSON_CreateArray();
if (eose_response) {
cJSON_AddItemToArray(eose_response, cJSON_CreateString("EOSE"));
cJSON_AddItemToArray(eose_response, cJSON_CreateString(subscription_id));
// Send EOSE (End of Stored Events)
cJSON* eose_response = cJSON_CreateArray();
if (eose_response) {
cJSON_AddItemToArray(eose_response, cJSON_CreateString("EOSE"));
cJSON_AddItemToArray(eose_response, cJSON_CreateString(subscription_id));
char *eose_str = cJSON_Print(eose_response);
if (eose_str) {
size_t eose_len = strlen(eose_str);
char *eose_str = cJSON_Print(eose_response);
if (eose_str) {
size_t eose_len = strlen(eose_str);
// DEBUG: Log WebSocket frame details before sending
DEBUG_TRACE("WS_FRAME_SEND: type=EOSE len=%zu data=%.100s%s",
eose_len,
eose_str,
eose_len > 100 ? "..." : "");
// DEBUG: Log WebSocket frame details before sending
DEBUG_TRACE("WS_FRAME_SEND: type=EOSE len=%zu data=%.100s%s",
eose_len,
eose_str,
eose_len > 100 ? "..." : "");
// Queue message for proper libwebsockets pattern
if (queue_message(wsi, pss, eose_str, eose_len, LWS_WRITE_TEXT) != 0) {
DEBUG_ERROR("Failed to queue EOSE message");
// Queue message for proper libwebsockets pattern
if (queue_message(wsi, pss, eose_str, eose_len, LWS_WRITE_TEXT) != 0) {
DEBUG_ERROR("Failed to queue EOSE message");
}
free(eose_str);
}
free(eose_str);
cJSON_Delete(eose_response);
}
cJSON_Delete(eose_response);
}
} else {
send_notice_message(wsi, pss, "error: missing or invalid subscription ID in REQ");
@@ -2647,6 +2655,9 @@ int start_websocket_relay(int port_override, int strict_port) {
break;
}
// Drain completed async REQ jobs and emit EVENT/EOSE on service thread.
process_req_async_completions();
// Check if it's time to post status update
time_t current_time = time(NULL);
int status_post_hours = get_config_int("kind_1_status_posts_hours", 0);
-18
View File
@@ -1,18 +0,0 @@
2026-04-01 09:10:57 - ==========================================
2026-04-01 09:10:57 - C-Relay Comprehensive Test Suite Runner
2026-04-01 09:10:57 - ==========================================
2026-04-01 09:10:57 - Relay URL: ws://127.0.0.1:8888
2026-04-01 09:10:57 - Log file: test_results_20260401_091057.log
2026-04-01 09:10:57 - Report file: test_report_20260401_091057.html
2026-04-01 09:10:57 -
2026-04-01 09:10:57 - Checking relay status at ws://127.0.0.1:8888...
2026-04-01 09:10:57 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m
2026-04-01 09:10:57 -
2026-04-01 09:10:57 - Starting comprehensive test execution...
2026-04-01 09:10:57 -
2026-04-01 09:10:57 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m
2026-04-01 09:10:57 - ==========================================
2026-04-01 09:10:57 - Running Test Suite: SQL Injection Tests
2026-04-01 09:10:57 - Description: Comprehensive SQL injection vulnerability testing
2026-04-01 09:10:57 - ==========================================
2026-04-01 09:10:57 - \033[0;31mERROR: Test script sql_injection_tests.sh not found\033[0m
-18
View File
@@ -1,18 +0,0 @@
2026-04-01 09:32:38 - ==========================================
2026-04-01 09:32:38 - C-Relay Comprehensive Test Suite Runner
2026-04-01 09:32:38 - ==========================================
2026-04-01 09:32:38 - Relay URL: ws://127.0.0.1:8888
2026-04-01 09:32:38 - Log file: test_results_20260401_093238.log
2026-04-01 09:32:38 - Report file: test_report_20260401_093238.html
2026-04-01 09:32:38 -
2026-04-01 09:32:38 - Checking relay status at ws://127.0.0.1:8888...
2026-04-01 09:32:38 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m
2026-04-01 09:32:38 -
2026-04-01 09:32:38 - Starting comprehensive test execution...
2026-04-01 09:32:38 -
2026-04-01 09:32:38 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m
2026-04-01 09:32:38 - ==========================================
2026-04-01 09:32:38 - Running Test Suite: SQL Injection Tests
2026-04-01 09:32:38 - Description: Comprehensive SQL injection vulnerability testing
2026-04-01 09:32:38 - ==========================================
2026-04-01 09:32:38 - \033[0;31mERROR: Test script sql_injection_tests.sh not found\033[0m
+18
View File
@@ -0,0 +1,18 @@
2026-04-01 09:57:22 - ==========================================
2026-04-01 09:57:22 - C-Relay Comprehensive Test Suite Runner
2026-04-01 09:57:22 - ==========================================
2026-04-01 09:57:22 - Relay URL: ws://127.0.0.1:8888
2026-04-01 09:57:22 - Log file: test_results_20260401_095722.log
2026-04-01 09:57:22 - Report file: test_report_20260401_095722.html
2026-04-01 09:57:22 -
2026-04-01 09:57:22 - Checking relay status at ws://127.0.0.1:8888...
2026-04-01 09:57:22 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m
2026-04-01 09:57:22 -
2026-04-01 09:57:22 - Starting comprehensive test execution...
2026-04-01 09:57:22 -
2026-04-01 09:57:22 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m
2026-04-01 09:57:22 - ==========================================
2026-04-01 09:57:22 - Running Test Suite: SQL Injection Tests
2026-04-01 09:57:22 - Description: Comprehensive SQL injection vulnerability testing
2026-04-01 09:57:22 - ==========================================
2026-04-01 09:57:22 - \033[0;31mERROR: Test script sql_injection_tests.sh not found\033[0m
+26 -26
View File
@@ -1,5 +1,5 @@
=== NIP-42 Authentication Test Started ===
2026-04-01 05:46:11 - Starting NIP-42 authentication tests
2026-04-01 10:00:44 - Starting NIP-42 authentication tests
[INFO] === Starting NIP-42 Authentication Tests ===
[INFO] Checking dependencies...
[WARNING] wscat not found. Some manual WebSocket tests will be skipped
@@ -7,7 +7,7 @@
[SUCCESS] Dependencies check complete
[INFO] Test 1: Checking NIP-42 support in relay info
[SUCCESS] NIP-42 is advertised in supported NIPs
2026-04-01 05:46:11 - Supported NIPs: 1,2,4,9,11,12,13,15,16,20,22,33,40,42,50,70
2026-04-01 10:00:44 - Supported NIPs: 1,2,4,9,11,12,13,15,16,20,22,33,40,42,50,70
[INFO] Test 2: Testing AUTH challenge generation
[WARNING] Could not extract admin private key from relay.log - using manual test approach
[INFO] Manual test: Connect to relay and send an event without auth to trigger challenge
@@ -15,8 +15,8 @@
[INFO] Generated test keypair: test_pubkey
[INFO] Attempting to publish event without authentication...
[INFO] Publishing test event to relay...
2026-04-01 05:46:12 - Event publish result: connecting to localhost:8888... ok.
{"kind":1,"id":"334754e42c2bd54bdf733fb2c9a28c1e0f33d2275ca2c70f26062187793b37fe","pubkey":"40d9f28055baeba6a9bcac1942a17370add58a95a46985f66c5c14d485573983","created_at":1775036772,"tags":[],"content":"NIP-42 test event - should require auth","sig":"6e81c7335d34a770c72566250ac363aa5a126383a1efe2effe5b4b7e84bf600c4d9531b26e9be188fe884e9bd32aa384d49739815c7fba4594174277b90cbf7d"}
2026-04-01 10:00:45 - Event publish result: connecting to localhost:8888... ok.
{"kind":1,"id":"28174ed72f9ef5484a6596db6c6be181d2ed10e46d8c22a9f7a267becbc9a47a","pubkey":"b5f9adc3b362c0e81569a46648a3996708d6b47754d36c617683f18e1f1daf6b","created_at":1775052045,"tags":[],"content":"NIP-42 test event - should require auth","sig":"0041347b233a21d6542a263634f52f8ca7e3b5ecf07bc37c86f390f1a7ab0bc2051306b14aa70edff020bd7311b259d03c2c35a035b285f9e19051a924546666"}
publishing to ws://localhost:8888... success.
[SUCCESS] Relay requested authentication as expected
[INFO] Test 4: Testing WebSocket AUTH message handling
@@ -26,50 +26,50 @@ publishing to ws://localhost:8888... success.
[WARNING] Could not retrieve configuration events
[INFO] Test 6: Testing NIP-42 performance and stability
[INFO] Testing multiple authentication attempts...
2026-04-01 05:46:13 - Attempt 1: .187311847s - connecting to localhost:8888... ok.
{"kind":1,"id":"470a74d32e1b739e6bfd275c0d03ab1508fb5908c0fa01523e4a3bdce6a95db3","pubkey":"59d792f5cfd57b3a284ce769b5d4b1e8a6eb231c63239cdad7f07d791433e79a","created_at":1775036773,"tags":[],"content":"Performance test event 1","sig":"0c30a125cb67d336413f006a5dfa422d788e177254e496b966854bf45183238d23f6575dba37dec6fa46b6f6b906a25afafcbbcaa7d0c15add89dd7fd1a80c22"}
2026-04-01 10:00:46 - Attempt 1: .195528002s - connecting to localhost:8888... ok.
{"kind":1,"id":"72526c57e50d721bfcdcca23856f2c5d3021c3100f0121538d9d829042e9b8b5","pubkey":"29af457d7eea750c11c836dfbeed3076fa0bf1f1b67a404ee4c25733d9b54238","created_at":1775052046,"tags":[],"content":"Performance test event 1","sig":"1a10db86a39aaf47bda6d0cca5f888691bae32bfd85df8c178dddbf24861503a491bfe4da8d66dfe3e5e0afec7bf8644e7eb69c9f051c601673ae13284f9ab31"}
publishing to ws://localhost:8888... success.
2026-04-01 05:46:13 - Attempt 2: .185565503s - connecting to localhost:8888... ok.
{"kind":1,"id":"e4f9db77d30d8e71ef01b16dcb1179d03462aaf368aae30120898f3758a9d0b9","pubkey":"59d792f5cfd57b3a284ce769b5d4b1e8a6eb231c63239cdad7f07d791433e79a","created_at":1775036773,"tags":[],"content":"Performance test event 2","sig":"24a8f38c2cb230d95120e60a246a1a03ac17d4d195d25f2ae0b2f674c3383287eca53cd7fc61bc3cdc58433a3d2c2108558f4f1a98dcfc02a97b4ec8c537cdde"}
2026-04-01 10:00:46 - Attempt 2: .185198180s - connecting to localhost:8888... ok.
{"kind":1,"id":"84eeb907205806bb88fc353bb83f1bf0149f3c93c18695306d8480d36752878b","pubkey":"29af457d7eea750c11c836dfbeed3076fa0bf1f1b67a404ee4c25733d9b54238","created_at":1775052046,"tags":[],"content":"Performance test event 2","sig":"e568dbca3113966f61e69eff05576d296fac42b5ff2ab7341778acc02cf6e9440b59bf651a8a2995d89180a62ec20ffc1143e211cf2960c5abaa358a82992ac6"}
publishing to ws://localhost:8888... success.
2026-04-01 05:46:14 - Attempt 3: .185110183s - connecting to localhost:8888... ok.
{"kind":1,"id":"4a85e466756452e90358fe5d98fa41bda93b6bc77cb079bb1616ca8873f4fca1","pubkey":"59d792f5cfd57b3a284ce769b5d4b1e8a6eb231c63239cdad7f07d791433e79a","created_at":1775036774,"tags":[],"content":"Performance test event 3","sig":"72ab716ff3b4786309fd4cef27ae8cd9df1e3c1746b5e49ba45c070cafcfb72a5b7ba045e7d798f23b5d82989526b4d3c818509f0eec639c8ad265be85a4728e"}
2026-04-01 10:00:47 - Attempt 3: .197159540s - connecting to localhost:8888... ok.
{"kind":1,"id":"0874716ba0b2a845d379e703226513f90db6228ce958369f4dce5aff3df64c5a","pubkey":"29af457d7eea750c11c836dfbeed3076fa0bf1f1b67a404ee4c25733d9b54238","created_at":1775052046,"tags":[],"content":"Performance test event 3","sig":"e74e885eb464998bddf655c24c95a18d7299908c63fb82a47f8d14af65992f90e73794a9f289b550bc1dec1c0298112d2b25941be7e26e3f17dd66b4c5dc742e"}
publishing to ws://localhost:8888... success.
2026-04-01 05:46:14 - Attempt 4: .185725746s - connecting to localhost:8888... ok.
{"kind":1,"id":"4c05b6359da88f85c0aa4c94a296a465f71bddaa10d849ed0923f972ac0628a9","pubkey":"59d792f5cfd57b3a284ce769b5d4b1e8a6eb231c63239cdad7f07d791433e79a","created_at":1775036774,"tags":[],"content":"Performance test event 4","sig":"3b0c8bd1f3d965a259a0ab228ea4c47defef3ad563ebce860ec65ab0ee341c5305c0988ac88651664d3090ce2ff282a0fe4913231448158c423a74b6e3b2a7a9"}
2026-04-01 10:00:47 - Attempt 4: .202520319s - connecting to localhost:8888... ok.
{"kind":1,"id":"5614d1b795cd028e096720418bb4dbc64870d442c6295b9bb2931564a332099a","pubkey":"29af457d7eea750c11c836dfbeed3076fa0bf1f1b67a404ee4c25733d9b54238","created_at":1775052047,"tags":[],"content":"Performance test event 4","sig":"1c0645a791ab9df573b152b782884bcb5ec2a18b8f4e1c18d7dc2175977b7d1d572674169ea1d2b3e3fa8d228de90f6cc281d56a3ef471ba4f9510e920ed586e"}
publishing to ws://localhost:8888... success.
2026-04-01 05:46:15 - Attempt 5: .184467365s - connecting to localhost:8888... ok.
{"kind":1,"id":"0902e868c5e7909f0143fde7738d5e8f4c42ea0ced2c69a404cfeb6cd686ceaa","pubkey":"59d792f5cfd57b3a284ce769b5d4b1e8a6eb231c63239cdad7f07d791433e79a","created_at":1775036774,"tags":[],"content":"Performance test event 5","sig":"a0754e774e5bc3514dc4cf04ec51cab077413251cfce480e80badccc7660bfe8d1eac87d43c6e332d936cc171d24e8318e1ef5df7425a8218e01af661f291c47"}
2026-04-01 10:00:47 - Attempt 5: .222530325s - connecting to localhost:8888... ok.
{"kind":1,"id":"9139f1eb39c31c43cb9f02e60a0f75a0f99f6c7d9693a69a225c8b3e530f774a","pubkey":"29af457d7eea750c11c836dfbeed3076fa0bf1f1b67a404ee4c25733d9b54238","created_at":1775052047,"tags":[],"content":"Performance test event 5","sig":"bd245a7629c33619c0ed426f631e0c2413adb4488c281bea09886ceb6407b40e247d42f82e374e2f53ee5281f0d8361661a4c613c6414c59d09e5bb23eeeb02d"}
publishing to ws://localhost:8888... success.
[SUCCESS] Performance test completed: 5/5 successful responses
[INFO] Test 7: Testing kind-specific NIP-42 authentication requirements
[INFO] Generated test keypair for kind-specific tests: test_pubkey
[INFO] Testing kind 1 event (regular note) - should work without authentication...
2026-04-01 05:46:15 - Kind 1 event result: connecting to localhost:8888... ok.
{"kind":1,"id":"a1d9545c4ac16c2ab1fae4d5d77cabd368d765533b6f6d1f4dbb3c7859696a5e","pubkey":"06ff6b7ca3c72cc7cebb9fe70ea76b6f79b6226c4dc5adc4ebcc59220c23c9bf","created_at":1775036775,"tags":[],"content":"Regular note - should not require auth","sig":"520796b21690bf03b20da28f30ffc84c9c198d0a908c07540f232f789c7a0327861e1f580d499be15b244bdf66c5c4b1cdf621bfdc5f60b1313d7f40036ffab8"}
2026-04-01 10:00:48 - Kind 1 event result: connecting to localhost:8888... ok.
{"kind":1,"id":"def386c66576a91f144a7321e9520a3ed46f9fecc56d9e79d32a162f249cace9","pubkey":"7c9b00749c966e50d18d4b807b44d4612bf9a8f67925fac3f00d21918b6e7f9c","created_at":1775052048,"tags":[],"content":"Regular note - should not require auth","sig":"7ee8fbf45e96d040df1a688e9bf028faaa49b81b26eeae6452b3a32cd9fd941c27b41734f14268d3deef14dbb19e514d29789211fffb27bfa9b73d20cddcc83e"}
publishing to ws://localhost:8888... success.
[SUCCESS] Kind 1 event accepted without authentication (correct behavior)
[INFO] Testing kind 4 event (direct message) - should require authentication...
2026-04-01 05:46:25 - Kind 4 event result: connecting to localhost:8888... ok.
{"kind":4,"id":"c047f5a9fb6ff46828d0bb862e4bcc32120e50fc7804d651485e2cdbddaf993a","pubkey":"06ff6b7ca3c72cc7cebb9fe70ea76b6f79b6226c4dc5adc4ebcc59220c23c9bf","created_at":1775036775,"tags":[["p,test_pubkey"]],"content":"This is a direct message - should require auth","sig":"a7a027c302faf75e269d866a1b0137f2fbc02643d76435dc68707d2ba6560af95df709787f4a1a1bc7c1b9951615ad081a97cd7f694da94d5606d17100f0b0dc"}
2026-04-01 10:00:58 - Kind 4 event result: connecting to localhost:8888... ok.
{"kind":4,"id":"a2f2d176a835d17a0c9229c0ce3b1ba61a5eb81308e4a3b3446634f468721238","pubkey":"7c9b00749c966e50d18d4b807b44d4612bf9a8f67925fac3f00d21918b6e7f9c","created_at":1775052048,"tags":[["p,test_pubkey"]],"content":"This is a direct message - should require auth","sig":"441f9caba6ebcec6b6c55d13ffd9571f3fe441a54ddd0692f2868429a5087378d791c97289b8b990560dcf40f5c66e86ba6b41f1de78220a5f389c1f119bb887"}
publishing to ws://localhost:8888...
[SUCCESS] Kind 4 event requested authentication (correct behavior for DMs)
[INFO] Testing kind 14 event (chat message) - should require authentication...
2026-04-01 05:46:36 - Kind 14 event result: connecting to localhost:8888... ok.
{"kind":14,"id":"73e50afdaefdbb01ed021f8c888a87b00aa59e4d68c124101353fabcdfa59ae1","pubkey":"06ff6b7ca3c72cc7cebb9fe70ea76b6f79b6226c4dc5adc4ebcc59220c23c9bf","created_at":1775036786,"tags":[["p,test_pubkey"]],"content":"Chat message - should require auth","sig":"ec2c713e2add876856e6cb0d1d9790c43525c5139a0fe06fbee71898f1042173dc3af9187e6fbccfef7406c1af2f218631a1cdcb7ad73e34553cdf3aecc4f209"}
2026-04-01 10:01:09 - Kind 14 event result: connecting to localhost:8888... ok.
{"kind":14,"id":"e234a6faa63f95cd24f8596fbfac573730533bb0abcde47b4d341ff0acace657","pubkey":"7c9b00749c966e50d18d4b807b44d4612bf9a8f67925fac3f00d21918b6e7f9c","created_at":1775052059,"tags":[["p,test_pubkey"]],"content":"Chat message - should require auth","sig":"fd6ba0ddaa7e2379aa49c5cda6599d0a38264799b228a8e907b948bef645080603f7f910c587f6cfb1ef1bac946c47dc681fdab6abd248376cd552bcd535ef9e"}
publishing to ws://localhost:8888...
[SUCCESS] Kind 14 event requested authentication (correct behavior for DMs)
[INFO] Testing other event kinds - should work without authentication...
2026-04-01 05:46:36 - Kind 0 event result: connecting to localhost:8888... ok.
{"kind":0,"id":"51fe1630721cfa3472ba7c3af911a3164c788a48c6a678179c62be48ac3d1f1c","pubkey":"06ff6b7ca3c72cc7cebb9fe70ea76b6f79b6226c4dc5adc4ebcc59220c23c9bf","created_at":1775036796,"tags":[],"content":"Test event kind 0 - should not require auth","sig":"d72d827742fdc86daf1389c8dad40efc56c22b9744ad23547ea0b0a37598e5c18ccdac20b9ef2e6adf46d7683bff19f2cbc11fb00a252903efc1076c8f613b38"}
2026-04-01 10:01:09 - Kind 0 event result: connecting to localhost:8888... ok.
{"kind":0,"id":"d87b69e8a2c8f6d98229b55cde3f0fe1b70c6cafd080e4462209849338eafc76","pubkey":"7c9b00749c966e50d18d4b807b44d4612bf9a8f67925fac3f00d21918b6e7f9c","created_at":1775052069,"tags":[],"content":"Test event kind 0 - should not require auth","sig":"e3222bd4882c881d039e6790a6a8d9f8e20a71d0f9d5089e98604acaa4feff7c1b1b2d9b4171d640da3bba955408a9391a0fd52334cc4a4165fa932e5163f9dd"}
publishing to ws://localhost:8888... success.
[SUCCESS] Kind 0 event accepted without authentication (correct)
2026-04-01 05:46:36 - Kind 3 event result: connecting to localhost:8888... ok.
{"kind":3,"id":"bf4155eb8b54bdd73a0b64365666caf886433634693cc3c4ab1d586187fc6bae","pubkey":"06ff6b7ca3c72cc7cebb9fe70ea76b6f79b6226c4dc5adc4ebcc59220c23c9bf","created_at":1775036796,"tags":[],"content":"Test event kind 3 - should not require auth","sig":"d27f654ccc9f95e76aacafcce7d42528545fa90b77d422a17301de6931883d8c00a0a84af56ab5d37a0f425140dbfc227458557f9050597beec42cacd0a15864"}
2026-04-01 10:01:09 - Kind 3 event result: connecting to localhost:8888... ok.
{"kind":3,"id":"ac0d45d7db2818bb5e674d4760711349928cf3d5dc8f1fc3717d9e7274e26fb0","pubkey":"7c9b00749c966e50d18d4b807b44d4612bf9a8f67925fac3f00d21918b6e7f9c","created_at":1775052069,"tags":[],"content":"Test event kind 3 - should not require auth","sig":"b65b317c81bc733ee2ff5921430b34d722e8d177147c5d1cddf14036573223582a3b7232810fc13169eb7a61ab3b99cd37ce13991673fce142d1ea10c702305e"}
publishing to ws://localhost:8888... success.
[SUCCESS] Kind 3 event accepted without authentication (correct)
2026-04-01 05:46:37 - Kind 7 event result: connecting to localhost:8888... ok.
{"kind":7,"id":"658e0caf1c4b52062363849d55312070005124e8a10202df285078222e523132","pubkey":"06ff6b7ca3c72cc7cebb9fe70ea76b6f79b6226c4dc5adc4ebcc59220c23c9bf","created_at":1775036797,"tags":[],"content":"Test event kind 7 - should not require auth","sig":"50361b3aa6b85747befe20e50e317d8f905128930cec0263494434ec9029089d880ff23cdf071c5b63b8417eff216658f1229c0f0dc5174a9a16a6d5fe38fd4d"}
2026-04-01 10:01:10 - Kind 7 event result: connecting to localhost:8888... ok.
{"kind":7,"id":"ad7e77948a4742abfca2d63c5bb748f3db3940c85841f521b3b7d634bf09955a","pubkey":"7c9b00749c966e50d18d4b807b44d4612bf9a8f67925fac3f00d21918b6e7f9c","created_at":1775052070,"tags":[],"content":"Test event kind 7 - should not require auth","sig":"8a5e5a03956cec04d56934314a0876fccbd221546432aaccdbc249b5f9cedc221219abf6e2856b98675de205afaa0e91ea642933ec6724576a40db43d17aa758"}
publishing to ws://localhost:8888... success.
[SUCCESS] Kind 7 event accepted without authentication (correct)
[INFO] Kind-specific authentication test completed
+733
View File
@@ -0,0 +1,733 @@
2026-04-01 09:57:29 - ==========================================
2026-04-01 09:57:30 - C-Relay Comprehensive Test Suite Runner
2026-04-01 09:57:30 - ==========================================
2026-04-01 09:57:30 - Relay URL: ws://127.0.0.1:8888
2026-04-01 09:57:30 - Log file: test_results_20260401_095729.log
2026-04-01 09:57:30 - Report file: test_report_20260401_095729.html
2026-04-01 09:57:30 -
2026-04-01 09:57:30 - Checking relay status at ws://127.0.0.1:8888...
2026-04-01 09:57:30 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m
2026-04-01 09:57:30 -
2026-04-01 09:57:30 - Starting comprehensive test execution...
2026-04-01 09:57:30 -
2026-04-01 09:57:30 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m
2026-04-01 09:57:30 - ==========================================
2026-04-01 09:57:30 - Running Test Suite: SQL Injection Tests
2026-04-01 09:57:30 - Description: Comprehensive SQL injection vulnerability testing
2026-04-01 09:57:30 - ==========================================
==========================================
C-Relay SQL Injection Test Suite
==========================================
Testing against relay at ws://127.0.0.1:8888
=== Basic Connectivity Test ===
Testing Basic connectivity... PASSED - Valid query works
=== Authors Filter SQL Injection Tests ===
Testing Authors filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: */... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: #... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
=== IDs Filter SQL Injection Tests ===
Testing IDs filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: */... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: #... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
=== Kinds Filter SQL Injection Tests ===
Testing Kinds filter with string injection... PASSED - SQL injection blocked (rejected with error)
Testing Kinds filter with negative value... PASSED - SQL injection blocked (rejected with error)
Testing Kinds filter with very large value... PASSED - SQL injection blocked (rejected with error)
=== Search Filter SQL Injection Tests ===
Testing Search filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing Search filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: */... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing Search filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing Search filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing Search filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
=== Tag Filter SQL Injection Tests ===
Testing #e tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
=== Timestamp Filter SQL Injection Tests ===
Testing Since parameter injection... PASSED - SQL injection blocked (rejected with error)
Testing Until parameter injection... PASSED - SQL injection blocked (rejected with error)
=== Limit Parameter SQL Injection Tests ===
Testing Limit parameter injection... PASSED - SQL injection blocked (rejected with error)
Testing Limit with UNION... PASSED - SQL injection blocked (rejected with error)
=== Complex Multi-Filter SQL Injection Tests ===
Testing Multi-filter with authors injection... PASSED - SQL injection blocked (rejected with error)
Testing Multi-filter with search injection... PASSED - SQL injection blocked (rejected with error)
Testing Multi-filter with tag injection... PASSED - SQL injection blocked (query sanitized)
=== COUNT Message SQL Injection Tests ===
Testing COUNT with authors payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' OR '1'='1... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing COUNT with authors payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: */... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: */... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: #... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: #... PASSED - SQL injection blocked (query sanitized)
Testing COUNT with authors payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing COUNT with authors payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing COUNT with authors payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
=== Edge Case SQL Injection Tests ===
Testing Empty string injection... PASSED - SQL injection blocked (rejected with error)
Testing Null byte injection... PASSED - SQL injection blocked (silently rejected)
Testing Unicode injection... PASSED - SQL injection blocked (rejected with error)
Testing Very long injection payload... PASSED - SQL injection blocked (rejected with error)
=== Subscription ID SQL Injection Tests ===
Testing Subscription ID injection... PASSED - SQL injection blocked (rejected with error)
Testing Subscription ID with quotes... PASSED - SQL injection blocked (silently rejected)
=== CLOSE Message SQL Injection Tests ===
Testing CLOSE with injection... PASSED - SQL injection blocked (rejected with error)
=== Test Results ===
Total tests: 318
Passed: 318
Failed: 0
✓ All SQL injection tests passed!
The relay appears to be protected against SQL injection attacks.
2026-04-01 09:57:36 - \033[0;32m✓ SQL Injection Tests PASSED\033[0m (Duration: 6s)
2026-04-01 09:57:36 - ==========================================
2026-04-01 09:57:36 - Running Test Suite: Filter Validation Tests
2026-04-01 09:57:36 - Description: Input validation for REQ and COUNT messages
2026-04-01 09:57:36 - ==========================================
=== C-Relay Filter Validation Tests ===
Testing against relay at ws://127.0.0.1:8888
Testing Valid REQ message... PASSED
Testing Valid COUNT message... PASSED
=== Testing Filter Array Validation ===
Testing Non-object filter... PASSED
Testing Too many filters... PASSED
=== Testing Authors Validation ===
Testing Invalid author type... PASSED
Testing Invalid author hex... PASSED
Testing Too many authors... PASSED
=== Testing IDs Validation ===
Testing Invalid ID type... PASSED
Testing Invalid ID hex... PASSED
Testing Too many IDs... PASSED
=== Testing Kinds Validation ===
Testing Invalid kind type... PASSED
Testing Negative kind... PASSED
Testing Too large kind... PASSED
Testing Too many kinds... PASSED
=== Testing Timestamp Validation ===
Testing Invalid since type... PASSED
Testing Negative since... PASSED
Testing Invalid until type... PASSED
Testing Negative until... PASSED
=== Testing Limit Validation ===
Testing Invalid limit type... PASSED
Testing Negative limit... PASSED
Testing Too large limit... PASSED
=== Testing Search Validation ===
Testing Invalid search type... PASSED
Testing Search too long... PASSED
Testing Search SQL injection... PASSED
=== Testing Tag Filter Validation ===
Testing Invalid tag filter type... PASSED
Testing Too many tag values... PASSED
Testing Tag value too long... PASSED
=== Testing Rate Limiting ===
Testing rate limiting with malformed requests... UNCERTAIN - Rate limiting may not have triggered (this could be normal)
=== Test Results ===
Total tests: 28
Passed: 28
Failed: 0
All tests passed!
2026-04-01 09:57:39 - \033[0;32m✓ Filter Validation Tests PASSED\033[0m (Duration: 3s)
2026-04-01 09:57:39 - ==========================================
2026-04-01 09:57:39 - Running Test Suite: Subscription Validation Tests
2026-04-01 09:57:39 - Description: Subscription ID and message validation
2026-04-01 09:57:39 - ==========================================
Testing subscription ID validation fixes...
Testing malformed subscription IDs...
Empty ID test: Connection failed (expected)
Long ID test: Connection failed (expected)
Invalid chars test: Connection failed (expected)
NULL ID test: Connection failed (expected)
Valid ID test: Failed
Testing CLOSE message validation...
CLOSE empty ID test: Connection failed (expected)
CLOSE valid ID test: Failed
Subscription validation tests completed.
2026-04-01 09:57:39 - \033[0;32m✓ Subscription Validation Tests PASSED\033[0m (Duration: 0s)
2026-04-01 09:57:39 - ==========================================
2026-04-01 09:57:39 - Running Test Suite: Memory Corruption Tests
2026-04-01 09:57:39 - Description: Buffer overflow and memory safety testing
2026-04-01 09:57:39 - ==========================================
==========================================
C-Relay Memory Corruption Test Suite
==========================================
Testing against relay at ws://127.0.0.1:8888
Note: These tests may cause the relay to crash if vulnerabilities exist
=== Basic Connectivity Test ===
Testing Basic connectivity... PASSED - No memory corruption detected
=== Subscription ID Memory Corruption Tests ===
Testing Empty subscription ID... UNCERTAIN - Expected error but got normal response
Testing Very long subscription ID (1KB)... UNCERTAIN - Expected error but got normal response
Testing Very long subscription ID (10KB)... UNCERTAIN - Expected error but got normal response
Testing Subscription ID with null bytes... UNCERTAIN - Expected error but got normal response
Testing Subscription ID with special chars... UNCERTAIN - Expected error but got normal response
Testing Unicode subscription ID... UNCERTAIN - Expected error but got normal response
Testing Subscription ID with path traversal... UNCERTAIN - Expected error but got normal response
=== Filter Array Memory Corruption Tests ===
Testing Too many filters (50)... UNCERTAIN - Expected error but got normal response
=== Concurrent Access Memory Tests ===
Testing Concurrent subscription creation... ["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
PASSED - Concurrent access handled safely
Testing Concurrent CLOSE operations...
PASSED - Concurrent access handled safely
=== Malformed JSON Memory Tests ===
Testing Unclosed JSON object... UNCERTAIN - Expected error but got normal response
Testing Mismatched brackets... UNCERTAIN - Expected error but got normal response
Testing Extra closing brackets... UNCERTAIN - Expected error but got normal response
Testing Null bytes in JSON... UNCERTAIN - Expected error but got normal response
=== Large Message Memory Tests ===
Testing Very large filter array... UNCERTAIN - Expected error but got normal response
Testing Very long search term... UNCERTAIN - Expected error but got normal response
=== Test Results ===
Total tests: 17
Passed: 17
Failed: 0
✓ All memory corruption tests passed!
The relay appears to handle memory safely.
2026-04-01 09:57:40 - \033[0;32m✓ Memory Corruption Tests PASSED\033[0m (Duration: 1s)
2026-04-01 09:57:40 - ==========================================
2026-04-01 09:57:40 - Running Test Suite: Input Validation Tests
2026-04-01 09:57:40 - Description: Comprehensive input boundary testing
2026-04-01 09:57:40 - ==========================================
==========================================
C-Relay Input Validation Test Suite
==========================================
Testing against relay at ws://127.0.0.1:8888
=== Basic Connectivity Test ===
Testing Basic connectivity... PASSED - Input accepted correctly
=== Message Type Validation ===
Testing Invalid message type - string... PASSED - Invalid input properly rejected
Testing Invalid message type - number... PASSED - Invalid input properly rejected
Testing Invalid message type - null... PASSED - Invalid input properly rejected
Testing Invalid message type - object... PASSED - Invalid input properly rejected
Testing Empty message type... PASSED - Invalid input properly rejected
Testing Very long message type... PASSED - Invalid input properly rejected
=== Message Structure Validation ===
Testing Too few arguments... PASSED - Invalid input properly rejected
Testing Too many arguments... PASSED - Invalid input properly rejected
Testing Non-array message... PASSED - Invalid input properly rejected
Testing Empty array... PASSED - Invalid input properly rejected
Testing Nested arrays incorrectly... PASSED - Invalid input properly rejected
=== Subscription ID Boundary Tests ===
Testing Valid subscription ID... PASSED - Input accepted correctly
Testing Empty subscription ID... PASSED - Invalid input properly rejected
Testing Subscription ID with spaces... PASSED - Invalid input properly rejected
Testing Subscription ID with newlines... PASSED - Invalid input properly rejected
Testing Subscription ID with tabs... PASSED - Invalid input properly rejected
Testing Subscription ID with control chars... PASSED - Invalid input properly rejected
Testing Unicode subscription ID... PASSED - Invalid input properly rejected
Testing Very long subscription ID... PASSED - Invalid input properly rejected
=== Filter Object Validation ===
Testing Valid empty filter... PASSED - Input accepted correctly
Testing Non-object filter... PASSED - Invalid input properly rejected
Testing Null filter... PASSED - Invalid input properly rejected
Testing Array filter... PASSED - Invalid input properly rejected
Testing Filter with invalid keys... PASSED - Input accepted correctly
=== Authors Field Validation ===
Testing Valid authors array... PASSED - Input accepted correctly
Testing Empty authors array... PASSED - Input accepted correctly
Testing Non-array authors... PASSED - Invalid input properly rejected
Testing Invalid hex in authors... PASSED - Invalid input properly rejected
Testing Short pubkey in authors... PASSED - Invalid input properly rejected
=== IDs Field Validation ===
Testing Valid ids array... PASSED - Input accepted correctly
Testing Empty ids array... PASSED - Input accepted correctly
Testing Non-array ids... PASSED - Invalid input properly rejected
=== Kinds Field Validation ===
Testing Valid kinds array... PASSED - Input accepted correctly
Testing Empty kinds array... PASSED - Input accepted correctly
Testing Non-array kinds... PASSED - Invalid input properly rejected
Testing String in kinds... PASSED - Invalid input properly rejected
=== Timestamp Field Validation ===
Testing Valid since timestamp... PASSED - Input accepted correctly
Testing Valid until timestamp... PASSED - Input accepted correctly
Testing String since timestamp... PASSED - Invalid input properly rejected
Testing Negative timestamp... PASSED - Invalid input properly rejected
=== Limit Field Validation ===
Testing Valid limit... PASSED - Input accepted correctly
Testing Zero limit... PASSED - Input accepted correctly
Testing String limit... PASSED - Invalid input properly rejected
Testing Negative limit... PASSED - Invalid input properly rejected
=== Multiple Filters ===
Testing Two valid filters... PASSED - Input accepted correctly
Testing Many filters... PASSED - Input accepted correctly
=== Test Results ===
Total tests: 47
Passed: 47
Failed: 0
✓ All input validation tests passed!
The relay properly validates input.
2026-04-01 09:57:41 - \033[0;32m✓ Input Validation Tests PASSED\033[0m (Duration: 1s)
2026-04-01 09:57:41 -
2026-04-01 09:57:41 - \033[0;34m=== PERFORMANCE TEST SUITES ===\033[0m
2026-04-01 09:57:41 - ==========================================
2026-04-01 09:57:41 - Running Test Suite: Subscription Limit Tests
2026-04-01 09:57:41 - Description: Subscription limit enforcement testing
2026-04-01 09:57:41 - ==========================================
=== Subscription Limit Test ===
[INFO] Testing relay at: ws://127.0.0.1:8888
[INFO] Note: This test assumes default subscription limits (max 25 per client)
=== Test 1: Basic Connectivity ===
[INFO] Testing basic WebSocket connection...
[PASS] Basic connectivity works
=== Test 2: Subscription Limit Enforcement ===
[INFO] Testing subscription limits by creating multiple subscriptions...
[INFO] Creating multiple subscriptions within a single connection...
[INFO] Hit subscription limit at subscription 2
[PASS] Subscription limit enforcement working (limit hit after 1 subscriptions)
=== Test Complete ===
2026-04-01 09:57:42 - \033[0;32m✓ Subscription Limit Tests PASSED\033[0m (Duration: 1s)
2026-04-01 09:57:42 - ==========================================
2026-04-01 09:57:42 - Running Test Suite: Load Testing
2026-04-01 09:57:42 - Description: High concurrent connection testing
2026-04-01 09:57:42 - ==========================================
==========================================
C-Relay Load Testing Suite
==========================================
Testing against relay at ws://127.0.0.1:8888
=== Basic Connectivity Test ===
✓ Relay is accessible
==========================================
Load Test: Light Load Test
Description: Basic load test with moderate concurrent connections
Concurrent clients: 10
Messages per client: 5
==========================================
Launching 10 clients...
All clients completed. Processing results...
=== Load Test Results ===
Test duration: 1s
Total connections attempted: 10
Successful connections: 10
Failed connections: 0
Connection success rate: 100%
Messages expected: 50
Messages sent: 50
Messages received: 100
✓ EXCELLENT: High connection success rate
Checking relay responsiveness... ✓ Relay is still responsive
==========================================
Load Test: Medium Load Test
Description: Moderate load test with higher concurrency
Concurrent clients: 25
Messages per client: 10
==========================================
Launching 25 clients...
All clients completed. Processing results...
=== Load Test Results ===
Test duration: 4s
Total connections attempted: 35
Successful connections: 25
Failed connections: 0
Connection success rate: 71%
Messages expected: 250
Messages sent: 250
Messages received: 500
✗ POOR: Low connection success rate
Checking relay responsiveness... ✓ Relay is still responsive
==========================================
Load Test: Heavy Load Test
Description: Heavy load test with high concurrency
Concurrent clients: 50
Messages per client: 20
==========================================
Launching 50 clients...
All clients completed. Processing results...
=== Load Test Results ===
Test duration: 15s
Total connections attempted: 85
Successful connections: 50
Failed connections: 0
Connection success rate: 58%
Messages expected: 1000
Messages sent: 1000
Messages received: 2000
✗ POOR: Low connection success rate
Checking relay responsiveness... ✓ Relay is still responsive
==========================================
Load Test: Stress Test
Description: Maximum load test to find breaking point
Concurrent clients: 100
Messages per client: 50
==========================================
Launching 100 clients...
All clients completed. Processing results...
=== Load Test Results ===
Test duration: 64s
Total connections attempted: 185
Successful connections: 100
Failed connections: 0
Connection success rate: 54%
Messages expected: 5000
Messages sent: 5000
Messages received: 7500
✗ POOR: Low connection success rate
Checking relay responsiveness... ✓ Relay is still responsive
==========================================
Load Testing Complete
==========================================
All load tests completed. Check individual test results above.
If any tests failed, the relay may need optimization or have resource limits.
2026-04-01 09:59:07 - \033[0;32m✓ Load Testing PASSED\033[0m (Duration: 85s)
2026-04-01 09:59:07 - ==========================================
2026-04-01 09:59:07 - Running Test Suite: Stress Testing
2026-04-01 09:59:07 - Description: Resource usage and stability testing
2026-04-01 09:59:07 - ==========================================
2026-04-01 09:59:07 - \033[0;31mERROR: Test script stress_tests.sh not found\033[0m