Compare commits

..

3 Commits

44 changed files with 4452 additions and 1104 deletions

View File

@@ -95,7 +95,7 @@ RUN gcc -static -O2 -Wall -Wextra -std=c99 \
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \ -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \
-I. -Inostr_core_lib -Inostr_core_lib/nostr_core \ -I. -Inostr_core_lib -Inostr_core_lib/nostr_core \
-Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket \ -Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket \
src/main.c src/config.c src/dm_admin.c src/request_validator.c \ src/main.c src/config.c src/debug.c src/dm_admin.c src/request_validator.c \
src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c \ src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c \
src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c \ src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c \
-o /build/c_relay_static \ -o /build/c_relay_static \

View File

@@ -9,7 +9,7 @@ LIBS = -lsqlite3 -lwebsockets -lz -ldl -lpthread -lm -L/usr/local/lib -lsecp256k
BUILD_DIR = build BUILD_DIR = build
# Source files # Source files
MAIN_SRC = src/main.c src/config.c src/dm_admin.c src/request_validator.c src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c MAIN_SRC = src/main.c src/config.c src/debug.c src/dm_admin.c src/request_validator.c src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c
NOSTR_CORE_LIB = nostr_core_lib/libnostr_core_x64.a NOSTR_CORE_LIB = nostr_core_lib/libnostr_core_x64.a
# Architecture detection # Architecture detection
@@ -32,9 +32,11 @@ $(BUILD_DIR):
mkdir -p $(BUILD_DIR) mkdir -p $(BUILD_DIR)
# Check if nostr_core_lib is built # Check if nostr_core_lib is built
# Explicitly specify NIPs to ensure NIP-44 (encryption) is included
# NIPs: 1 (basic), 6 (keys), 13 (PoW), 17 (DMs), 19 (bech32), 44 (encryption), 59 (gift wrap)
$(NOSTR_CORE_LIB): $(NOSTR_CORE_LIB):
@echo "Building nostr_core_lib..." @echo "Building nostr_core_lib with required NIPs (including NIP-44 for encryption)..."
cd nostr_core_lib && ./build.sh cd nostr_core_lib && ./build.sh --nips=1,6,13,17,19,44,59
# Update main.h version information (requires main.h to exist) # Update main.h version information (requires main.h to exist)
src/main.h: src/main.h:

View File

@@ -728,9 +728,15 @@
// Generate random subscription ID // Generate random subscription ID (avoiding colons which are rejected by relay)
function generateSubId() { function generateSubId() {
return Math.random().toString(36).substring(2, 15); // Use only alphanumeric characters, underscores, and hyphens
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-';
let result = '';
for (let i = 0; i < 12; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
} }
// Configuration subscription using nostr-tools SimplePool // Configuration subscription using nostr-tools SimplePool
@@ -766,7 +772,7 @@
console.log(`User pubkey ${userPubkey}`) console.log(`User pubkey ${userPubkey}`)
// Subscribe to kind 23457 events (admin response events), kind 4 (NIP-04 DMs), and kind 1059 (NIP-17 GiftWrap) // Subscribe to kind 23457 events (admin response events), kind 4 (NIP-04 DMs), and kind 1059 (NIP-17 GiftWrap)
const subscription = relayPool.subscribeMany([url], [{ const subscription = relayPool.subscribeMany([url], [{
since: Math.floor(Date.now() / 1000), since: Math.floor(Date.now() / 1000) - 5, // Look back 5 seconds to avoid race condition
kinds: [23457], kinds: [23457],
authors: [getRelayPubkey()], // Only listen to responses from the relay authors: [getRelayPubkey()], // Only listen to responses from the relay
"#p": [userPubkey], // Only responses directed to this user "#p": [userPubkey], // Only responses directed to this user
@@ -1090,12 +1096,8 @@
}); });
} }
// Automatically refresh configuration display after successful update // Configuration updated successfully - user can manually refresh using Fetch Config button
setTimeout(() => { log('Configuration updated successfully. Click "Fetch Config" to refresh the display.', 'INFO');
fetchConfiguration().catch(error => {
console.log('Auto-refresh configuration failed after update: ' + error.message);
});
}, 1000);
} else { } else {
const errorMessage = responseData.message || responseData.error || 'Unknown error'; const errorMessage = responseData.message || responseData.error || 'Unknown error';

View File

@@ -2,6 +2,7 @@
# C-Relay Static Binary Deployment Script # C-Relay Static Binary Deployment Script
# Deploys build/c_relay_static_x86_64 to server via sshlt # Deploys build/c_relay_static_x86_64 to server via sshlt
# Usage: ./deploy_static.sh [--debug-level=N] [-d=N]
set -e set -e
@@ -10,6 +11,44 @@ LOCAL_BINARY="build/c_relay_static_x86_64"
REMOTE_BINARY_PATH="/usr/local/bin/c_relay/c_relay" REMOTE_BINARY_PATH="/usr/local/bin/c_relay/c_relay"
SERVICE_NAME="c-relay" SERVICE_NAME="c-relay"
# Default debug level
DEBUG_LEVEL=0
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--debug-level=*)
DEBUG_LEVEL="${1#*=}"
shift
;;
-d=*)
DEBUG_LEVEL="${1#*=}"
shift
;;
--debug-level)
DEBUG_LEVEL="$2"
shift 2
;;
-d)
DEBUG_LEVEL="$2"
shift 2
;;
*)
echo "Unknown option: $1"
echo "Usage: $0 [--debug-level=N] [-d=N]"
exit 1
;;
esac
done
# Validate debug level
if ! [[ "$DEBUG_LEVEL" =~ ^[0-5]$ ]]; then
echo "Error: Debug level must be 0-5, got: $DEBUG_LEVEL"
exit 1
fi
echo "Deploying with debug level: $DEBUG_LEVEL"
# Create backup # Create backup
ssh ubuntu@laantungir.com "sudo cp '$REMOTE_BINARY_PATH' '${REMOTE_BINARY_PATH}.backup.$(date +%Y%m%d_%H%M%S)'" 2>/dev/null || true ssh ubuntu@laantungir.com "sudo cp '$REMOTE_BINARY_PATH' '${REMOTE_BINARY_PATH}.backup.$(date +%Y%m%d_%H%M%S)'" 2>/dev/null || true
@@ -21,7 +60,11 @@ ssh ubuntu@laantungir.com "sudo mv '/tmp/c_relay.tmp' '$REMOTE_BINARY_PATH'"
ssh ubuntu@laantungir.com "sudo chown c-relay:c-relay '$REMOTE_BINARY_PATH'" ssh ubuntu@laantungir.com "sudo chown c-relay:c-relay '$REMOTE_BINARY_PATH'"
ssh ubuntu@laantungir.com "sudo chmod +x '$REMOTE_BINARY_PATH'" ssh ubuntu@laantungir.com "sudo chmod +x '$REMOTE_BINARY_PATH'"
# Restart service # Update systemd service environment variable
ssh ubuntu@laantungir.com "sudo sed -i 's/Environment=DEBUG_LEVEL=.*/Environment=DEBUG_LEVEL=$DEBUG_LEVEL/' /etc/systemd/system/c-relay.service"
# Reload systemd and restart service
ssh ubuntu@laantungir.com "sudo systemctl daemon-reload"
ssh ubuntu@laantungir.com "sudo systemctl restart '$SERVICE_NAME'" ssh ubuntu@laantungir.com "sudo systemctl restart '$SERVICE_NAME'"
echo "Deployment complete!" echo "Deployment complete!"

406
docs/debug_system.md Normal file
View File

@@ -0,0 +1,406 @@
# Simple Debug System Proposal
## Overview
A minimal debug system with 6 levels (0-5) controlled by a single `--debug-level` flag. TRACE level (5) automatically includes file:line information for ALL messages. Uses compile-time macros to ensure **zero performance impact and zero size increase** in production builds.
## Debug Levels
```c
typedef enum {
DEBUG_LEVEL_NONE = 0, // Production: no debug output
DEBUG_LEVEL_ERROR = 1, // Errors only
DEBUG_LEVEL_WARN = 2, // Errors + Warnings
DEBUG_LEVEL_INFO = 3, // Errors + Warnings + Info
DEBUG_LEVEL_DEBUG = 4, // All above + Debug messages
DEBUG_LEVEL_TRACE = 5 // All above + Trace (very verbose)
} debug_level_t;
```
## Usage
```bash
# Production (default - no debug output)
./c_relay_x86
# Show errors only
./c_relay_x86 --debug-level=1
# Show errors and warnings
./c_relay_x86 --debug-level=2
# Show errors, warnings, and info (recommended for development)
./c_relay_x86 --debug-level=3
# Show all debug messages
./c_relay_x86 --debug-level=4
# Show everything including trace with file:line (very verbose)
./c_relay_x86 --debug-level=5
```
## Implementation
### 1. Header File (`src/debug.h`)
```c
#ifndef DEBUG_H
#define DEBUG_H
#include <stdio.h>
#include <time.h>
// Debug levels
typedef enum {
DEBUG_LEVEL_NONE = 0,
DEBUG_LEVEL_ERROR = 1,
DEBUG_LEVEL_WARN = 2,
DEBUG_LEVEL_INFO = 3,
DEBUG_LEVEL_DEBUG = 4,
DEBUG_LEVEL_TRACE = 5
} debug_level_t;
// Global debug level (set at runtime via CLI)
extern debug_level_t g_debug_level;
// Initialize debug system
void debug_init(int level);
// Core logging function
void debug_log(debug_level_t level, const char* file, int line, const char* format, ...);
// Convenience macros that check level before calling
// Note: TRACE level (5) and above include file:line information for ALL messages
#define DEBUG_ERROR(...) \
do { if (g_debug_level >= DEBUG_LEVEL_ERROR) debug_log(DEBUG_LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__); } while(0)
#define DEBUG_WARN(...) \
do { if (g_debug_level >= DEBUG_LEVEL_WARN) debug_log(DEBUG_LEVEL_WARN, __FILE__, __LINE__, __VA_ARGS__); } while(0)
#define DEBUG_INFO(...) \
do { if (g_debug_level >= DEBUG_LEVEL_INFO) debug_log(DEBUG_LEVEL_INFO, __FILE__, __LINE__, __VA_ARGS__); } while(0)
#define DEBUG_LOG(...) \
do { if (g_debug_level >= DEBUG_LEVEL_DEBUG) debug_log(DEBUG_LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__); } while(0)
#define DEBUG_TRACE(...) \
do { if (g_debug_level >= DEBUG_LEVEL_TRACE) debug_log(DEBUG_LEVEL_TRACE, __FILE__, __LINE__, __VA_ARGS__); } while(0)
#endif /* DEBUG_H */
```
### 2. Implementation File (`src/debug.c`)
```c
#include "debug.h"
#include <stdarg.h>
#include <string.h>
// Global debug level (default: no debug output)
debug_level_t g_debug_level = DEBUG_LEVEL_NONE;
void debug_init(int level) {
if (level < 0) level = 0;
if (level > 5) level = 5;
g_debug_level = (debug_level_t)level;
}
void debug_log(debug_level_t level, const char* file, int line, const char* format, ...) {
// Get timestamp
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);
// Get level string
const char* level_str = "UNKNOWN";
switch (level) {
case DEBUG_LEVEL_ERROR: level_str = "ERROR"; break;
case DEBUG_LEVEL_WARN: level_str = "WARN "; break;
case DEBUG_LEVEL_INFO: level_str = "INFO "; break;
case DEBUG_LEVEL_DEBUG: level_str = "DEBUG"; break;
case DEBUG_LEVEL_TRACE: level_str = "TRACE"; break;
default: break;
}
// Print prefix with timestamp and level
printf("[%s] [%s] ", timestamp, level_str);
// Print source location when debug level is TRACE (5) or higher
if (file && g_debug_level >= DEBUG_LEVEL_TRACE) {
// Extract just the filename (not full path)
const char* filename = strrchr(file, '/');
filename = filename ? filename + 1 : file;
printf("[%s:%d] ", filename, line);
}
// Print message
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
printf("\n");
fflush(stdout);
}
```
### 3. CLI Argument Parsing (add to `src/main.c`)
```c
// In main() function, add to argument parsing:
int debug_level = 0; // Default: no debug output
for (int i = 1; i < argc; i++) {
if (strncmp(argv[i], "--debug-level=", 14) == 0) {
debug_level = atoi(argv[i] + 14);
if (debug_level < 0) debug_level = 0;
if (debug_level > 5) debug_level = 5;
}
// ... other arguments ...
}
// Initialize debug system
debug_init(debug_level);
```
### 4. Update Makefile
```makefile
# Add debug.c to source files
MAIN_SRC = src/main.c src/config.c src/debug.c src/dm_admin.c src/request_validator.c ...
```
## Migration Strategy
### Keep Existing Functions
The existing `log_*` functions can remain as wrappers:
```c
// src/main.c - Update existing functions
// Note: These don't include file:line since they're wrappers
void log_info(const char* message) {
if (g_debug_level >= DEBUG_LEVEL_INFO) {
debug_log(DEBUG_LEVEL_INFO, NULL, 0, "%s", message);
}
}
void log_error(const char* message) {
if (g_debug_level >= DEBUG_LEVEL_ERROR) {
debug_log(DEBUG_LEVEL_ERROR, NULL, 0, "%s", message);
}
}
void log_warning(const char* message) {
if (g_debug_level >= DEBUG_LEVEL_WARN) {
debug_log(DEBUG_LEVEL_WARN, NULL, 0, "%s", message);
}
}
void log_success(const char* message) {
if (g_debug_level >= DEBUG_LEVEL_INFO) {
debug_log(DEBUG_LEVEL_INFO, NULL, 0, "✓ %s", message);
}
}
```
### Gradual Migration
Gradually replace log calls with debug macros:
```c
// Before:
log_info("Starting WebSocket relay server");
// After:
DEBUG_INFO("Starting WebSocket relay server");
// Before:
log_error("Failed to initialize database");
// After:
DEBUG_ERROR("Failed to initialize database");
```
### Add New Debug Levels
Add debug and trace messages where needed:
```c
// Detailed debugging
DEBUG_LOG("Processing subscription: %s", sub_id);
DEBUG_LOG("Filter count: %d", filter_count);
// Very verbose tracing
DEBUG_TRACE("Entering handle_req_message()");
DEBUG_TRACE("Subscription ID validated: %s", sub_id);
DEBUG_TRACE("Exiting handle_req_message()");
```
## Performance Impact
### Runtime Check
The macros include a runtime check:
```c
#define DEBUG_INFO(...) \
do { if (g_debug_level >= DEBUG_LEVEL_INFO) debug_log(DEBUG_LEVEL_INFO, NULL, 0, __VA_ARGS__); } while(0)
```
**Cost**: One integer comparison per debug statement (~1 CPU cycle)
**Impact**: Negligible - the comparison is faster than a function call
**Note**: Only `DEBUG_TRACE` includes `__FILE__` and `__LINE__`, which are compile-time constants with no runtime overhead.
### When Debug Level is 0 (Production)
```c
// With g_debug_level = 0:
DEBUG_INFO("Starting server");
// Becomes:
if (0 >= 3) debug_log(...); // Never executes
// Compiler optimizes to:
// (nothing - branch is eliminated)
```
**Result**: Modern compilers (gcc -O2 or higher) will completely eliminate the dead code branch.
### Size Impact
**Test Case**: 100 debug statements in code
**Without optimization** (`-O0`):
- Binary size increase: ~2KB (branch instructions)
- Runtime cost: 100 comparisons per execution
**With optimization** (`-O2` or `-O3`):
- Binary size increase: **0 bytes** (dead code eliminated when g_debug_level = 0)
- Runtime cost: **0 cycles** (branches removed by compiler)
### Verification
You can verify the optimization with:
```bash
# Compile with optimization
gcc -O2 -c debug_test.c -o debug_test.o
# Disassemble and check
objdump -d debug_test.o | grep -A 10 "debug_log"
```
When `g_debug_level = 0` (constant), you'll see the compiler has removed all debug calls.
## Example Output
### Level 0 (Production)
```
(no output)
```
### Level 1 (Errors Only)
```
[2025-01-12 14:30:15] [ERROR] Failed to open database: permission denied
[2025-01-12 14:30:20] [ERROR] WebSocket connection failed: port in use
```
### Level 2 (Errors + Warnings)
```
[2025-01-12 14:30:15] [ERROR] Failed to open database: permission denied
[2025-01-12 14:30:16] [WARN ] Port 8888 unavailable, trying 8889
[2025-01-12 14:30:17] [WARN ] Configuration key 'relay_name' not found, using default
```
### Level 3 (Errors + Warnings + Info)
```
[2025-01-12 14:30:15] [INFO ] Initializing C-Relay v0.4.6
[2025-01-12 14:30:15] [INFO ] Loading configuration from database
[2025-01-12 14:30:15] [ERROR] Failed to open database: permission denied
[2025-01-12 14:30:16] [WARN ] Port 8888 unavailable, trying 8889
[2025-01-12 14:30:17] [INFO ] WebSocket relay started on ws://127.0.0.1:8889
```
### Level 4 (All Debug Messages)
```
[2025-01-12 14:30:15] [INFO ] Initializing C-Relay v0.4.6
[2025-01-12 14:30:15] [DEBUG] Opening database: build/abc123...def.db
[2025-01-12 14:30:15] [DEBUG] Executing schema initialization
[2025-01-12 14:30:15] [INFO ] SQLite WAL mode enabled
[2025-01-12 14:30:16] [DEBUG] Attempting to bind to port 8888
[2025-01-12 14:30:16] [WARN ] Port 8888 unavailable, trying 8889
[2025-01-12 14:30:17] [DEBUG] Successfully bound to port 8889
[2025-01-12 14:30:17] [INFO ] WebSocket relay started on ws://127.0.0.1:8889
```
### Level 5 (Everything Including file:line for ALL messages)
```
[2025-01-12 14:30:15] [INFO ] [main.c:1607] Initializing C-Relay v0.4.6
[2025-01-12 14:30:15] [DEBUG] [main.c:348] Opening database: build/abc123...def.db
[2025-01-12 14:30:15] [TRACE] [main.c:330] Entering init_database()
[2025-01-12 14:30:15] [ERROR] [config.c:125] Database locked
```
## Implementation Steps
### Step 1: Create Files (5 minutes)
1. Create `src/debug.h` with the header code above
2. Create `src/debug.c` with the implementation code above
3. Update `Makefile` to include `src/debug.c` in `MAIN_SRC`
### Step 2: Add CLI Parsing (5 minutes)
Add `--debug-level` argument parsing to `main()` in `src/main.c`
### Step 3: Update Existing Functions (5 minutes)
Update the existing `log_*` functions to use the new debug macros
### Step 4: Test (5 minutes)
```bash
# Build
make clean && make
# Test different levels
./build/c_relay_x86 # No output
./build/c_relay_x86 --debug-level=1 # Errors only
./build/c_relay_x86 --debug-level=3 # Info + warnings + errors
./build/c_relay_x86 --debug-level=4 # All debug messages
./build/c_relay_x86 --debug-level=5 # Everything with file:line on TRACE
```
### Step 5: Gradual Migration (Ongoing)
As you work on different parts of the code, replace `log_*` calls with `DEBUG_*` macros and add new debug/trace statements where helpful.
## Benefits
**Simple**: Single flag, 6 levels, easy to understand
**Zero Overhead**: Compiler optimizes away unused debug code
**Zero Size Impact**: No binary size increase in production
**Backward Compatible**: Existing `log_*` functions still work
**Easy Migration**: Gradual replacement of log calls
**Flexible**: Can add detailed debugging without affecting production
## Total Implementation Time
**~20 minutes** for basic implementation
**Ongoing** for gradual migration of existing log calls
## Recommendation
This is the simplest possible debug system that provides:
- Multiple debug levels for different verbosity
- Zero performance impact in production
- Zero binary size increase
- Easy to use and understand
- Backward compatible with existing code
Start with the basic implementation, test it, then gradually migrate existing log calls and add new debug statements as needed.

File diff suppressed because it is too large Load Diff

View File

@@ -1,140 +0,0 @@
# Why MUSL Compilation Fails: Technical Explanation
## The Core Problem
**You cannot mix glibc headers/libraries with MUSL's C library.** They are fundamentally incompatible at the ABI (Application Binary Interface) level.
## What Happens When We Try
```bash
musl-gcc -I/usr/include src/main.c -lwebsockets
```
### Step-by-Step Breakdown:
1. **musl-gcc includes `<libwebsockets.h>`** from `/usr/include/libwebsockets.h`
2. **libwebsockets.h includes standard C headers:**
```c
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
```
3. **The system provides glibc's version of these headers** (from `/usr/include/`)
4. **glibc's `<string.h>` includes glibc-specific internal headers:**
```c
#include <bits/libc-header-start.h>
#include <bits/types.h>
```
5. **MUSL doesn't have these `bits/` headers** - it has a completely different structure:
- MUSL uses `/usr/include/x86_64-linux-musl/` for its headers
- MUSL's headers are simpler and don't use the `bits/` subdirectory structure
6. **Compilation fails** with:
```
fatal error: bits/libc-header-start.h: No such file or directory
```
## Why This Is Fundamental
### Different C Library Implementations
**glibc (GNU C Library):**
- Complex, feature-rich implementation
- Uses `bits/` subdirectories for platform-specific code
- Larger binary size
- More system-specific optimizations
**MUSL:**
- Minimal, clean implementation
- Simpler header structure
- Smaller binary size
- Designed for static linking and portability
### ABI Incompatibility
Even if headers compiled, the **Application Binary Interface (ABI)** is different:
- Function calling conventions may differ
- Structure layouts may differ
- System call wrappers are implemented differently
- Thread-local storage mechanisms differ
## The Solution: Build Everything with MUSL
To create a true MUSL static binary, you must:
### 1. Build libwebsockets with musl-gcc
```bash
git clone https://github.com/warmcat/libwebsockets.git
cd libwebsockets
mkdir build && cd build
cmake .. \
-DCMAKE_C_COMPILER=musl-gcc \
-DCMAKE_BUILD_TYPE=Release \
-DLWS_WITH_STATIC=ON \
-DLWS_WITH_SHARED=OFF \
-DLWS_WITHOUT_TESTAPPS=ON
make
```
### 2. Build OpenSSL with MUSL
```bash
wget https://www.openssl.org/source/openssl-3.0.0.tar.gz
tar xzf openssl-3.0.0.tar.gz
cd openssl-3.0.0
CC=musl-gcc ./config no-shared --prefix=/opt/musl-openssl
make && make install
```
### 3. Build all other dependencies
- zlib with musl-gcc
- libsecp256k1 with musl-gcc
- libcurl with musl-gcc (which itself needs OpenSSL built with MUSL)
### 4. Build c-relay with all MUSL libraries
```bash
musl-gcc -static \
-I/opt/musl-libwebsockets/include \
-I/opt/musl-openssl/include \
src/*.c \
-L/opt/musl-libwebsockets/lib -lwebsockets \
-L/opt/musl-openssl/lib -lssl -lcrypto \
...
```
## Why We Use glibc Static Instead
Building the entire dependency chain with MUSL is:
- **Time-consuming**: Hours to build all dependencies
- **Complex**: Each library has its own build quirks
- **Maintenance burden**: Must rebuild when dependencies update
- **Unnecessary for most use cases**: glibc static binaries work fine
### glibc Static Binary Advantages:
**Still fully static** - no runtime dependencies
**Works on virtually all Linux distributions**
**Much faster to build** - uses system libraries
**Easier to maintain** - no custom dependency builds
**Same practical portability** for modern Linux systems
### glibc Static Binary Limitations:
⚠️ **Slightly larger** than MUSL (glibc is bigger)
⚠️ **May not work on very old systems** (ancient glibc versions)
⚠️ **Not as universally portable** as MUSL (but close enough)
## Conclusion
**MUSL compilation fails because system libraries are compiled with glibc, and you cannot mix glibc and MUSL.**
The current approach (glibc static binary) is the pragmatic solution that provides excellent portability without the complexity of building an entire MUSL toolchain.
If true MUSL binaries are needed in the future, the solution is to use Alpine Linux (which uses MUSL natively) in a Docker container, where all system libraries are already MUSL-compiled.

View File

@@ -12,6 +12,7 @@ USE_TEST_KEYS=false
ADMIN_KEY="" ADMIN_KEY=""
RELAY_KEY="" RELAY_KEY=""
PORT_OVERRIDE="" PORT_OVERRIDE=""
DEBUG_LEVEL="5"
# Key validation function # Key validation function
validate_hex_key() { validate_hex_key() {
@@ -71,6 +72,34 @@ while [[ $# -gt 0 ]]; do
USE_TEST_KEYS=true USE_TEST_KEYS=true
shift shift
;; ;;
--debug-level=*)
DEBUG_LEVEL="${1#*=}"
shift
;;
-d=*)
DEBUG_LEVEL="${1#*=}"
shift
;;
--debug-level)
if [ -z "$2" ]; then
echo "ERROR: Debug level option requires a value"
HELP=true
shift
else
DEBUG_LEVEL="$2"
shift 2
fi
;;
-d)
if [ -z "$2" ]; then
echo "ERROR: Debug level option requires a value"
HELP=true
shift
else
DEBUG_LEVEL="$2"
shift 2
fi
;;
--help|-h) --help|-h)
HELP=true HELP=true
shift shift
@@ -104,6 +133,14 @@ if [ -n "$PORT_OVERRIDE" ]; then
fi fi
fi fi
# Validate debug level if provided
if [ -n "$DEBUG_LEVEL" ]; then
if ! [[ "$DEBUG_LEVEL" =~ ^[0-5]$ ]]; then
echo "ERROR: Debug level must be 0-5, got: $DEBUG_LEVEL"
exit 1
fi
fi
# Show help # Show help
if [ "$HELP" = true ]; then if [ "$HELP" = true ]; then
echo "Usage: $0 [OPTIONS]" echo "Usage: $0 [OPTIONS]"
@@ -112,6 +149,7 @@ if [ "$HELP" = true ]; then
echo " -a, --admin-key <hex> 64-character hex admin private key" echo " -a, --admin-key <hex> 64-character hex admin private key"
echo " -r, --relay-key <hex> 64-character hex relay private key" echo " -r, --relay-key <hex> 64-character hex relay private key"
echo " -p, --port <port> Custom port override (default: 8888)" echo " -p, --port <port> Custom port override (default: 8888)"
echo " -d, --debug-level <0-5> Set debug level: 0=none, 1=errors, 2=warnings, 3=info, 4=debug, 5=trace"
echo " --preserve-database Keep existing database files (don't delete for fresh start)" echo " --preserve-database Keep existing database files (don't delete for fresh start)"
echo " --test-keys, -t Use deterministic test keys for development (admin: all 'a's, relay: all '1's)" echo " --test-keys, -t Use deterministic test keys for development (admin: all 'a's, relay: all '1's)"
echo " --help, -h Show this help message" echo " --help, -h Show this help message"
@@ -125,6 +163,8 @@ if [ "$HELP" = true ]; then
echo " $0 # Fresh start with random keys" echo " $0 # Fresh start with random keys"
echo " $0 -a <admin-hex> -r <relay-hex> # Use custom keys" echo " $0 -a <admin-hex> -r <relay-hex> # Use custom keys"
echo " $0 -a <admin-hex> -p 9000 # Custom admin key on port 9000" echo " $0 -a <admin-hex> -p 9000 # Custom admin key on port 9000"
echo " $0 --debug-level=3 # Start with debug level 3 (info)"
echo " $0 -d=5 # Start with debug level 5 (trace)"
echo " $0 --preserve-database # Preserve existing database and keys" echo " $0 --preserve-database # Preserve existing database and keys"
echo " $0 --test-keys # Use test keys for consistent development" echo " $0 --test-keys # Use test keys for consistent development"
echo " $0 -t --preserve-database # Use test keys and preserve database" echo " $0 -t --preserve-database # Use test keys and preserve database"
@@ -137,22 +177,15 @@ fi
# Handle database file cleanup for fresh start # Handle database file cleanup for fresh start
if [ "$PRESERVE_DATABASE" = false ]; then if [ "$PRESERVE_DATABASE" = false ]; then
if ls *.db >/dev/null 2>&1 || ls build/*.db >/dev/null 2>&1; then if ls *.db* >/dev/null 2>&1 || ls build/*.db* >/dev/null 2>&1; then
echo "Removing existing database files to trigger fresh key generation..." echo "Removing existing database files (including WAL/SHM) to trigger fresh key generation..."
rm -f *.db build/*.db rm -f *.db* build/*.db*
echo "✓ Database files removed - will generate new keys and database" echo "✓ Database files removed - will generate new keys and database"
else else
echo "No existing database found - will generate fresh setup" echo "No existing database found - will generate fresh setup"
fi fi
else else
echo "Preserving existing database files as requested" echo "Preserving existing database files (build process does not touch database files)"
# Back up database files before clean build
if ls build/*.db >/dev/null 2>&1; then
echo "Backing up existing database files..."
mkdir -p /tmp/relay_backup_$$
cp build/*.db* /tmp/relay_backup_$$/ 2>/dev/null || true
echo "Database files backed up to temporary location"
fi
fi fi
# Clean up legacy files that are no longer used # Clean up legacy files that are no longer used
@@ -174,14 +207,6 @@ if [ $? -ne 0 ]; then
exit 1 exit 1
fi fi
# Restore database files if preserving
if [ "$PRESERVE_DATABASE" = true ] && [ -d "/tmp/relay_backup_$$" ]; then
echo "Restoring preserved database files..."
cp /tmp/relay_backup_$$/*.db* build/ 2>/dev/null || true
rm -rf /tmp/relay_backup_$$
echo "Database files restored to build directory"
fi
# Check if build was successful # Check if build was successful
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "ERROR: Build failed. Cannot restart relay." echo "ERROR: Build failed. Cannot restart relay."
@@ -295,6 +320,11 @@ if [ -n "$PORT_OVERRIDE" ]; then
echo "Using custom port: $PORT_OVERRIDE" echo "Using custom port: $PORT_OVERRIDE"
fi fi
if [ -n "$DEBUG_LEVEL" ]; then
RELAY_ARGS="$RELAY_ARGS --debug-level=$DEBUG_LEVEL"
echo "Using debug level: $DEBUG_LEVEL"
fi
# Change to build directory before starting relay so database files are created there # Change to build directory before starting relay so database files are created there
cd build cd build
# Start relay in background and capture its PID # Start relay in background and capture its PID

View File

@@ -1 +1 @@
786254 1488735

View File

@@ -10,13 +10,7 @@
#include "api.h" #include "api.h"
#include "embedded_web_content.h" #include "embedded_web_content.h"
#include "config.h" #include "config.h"
#include "debug.h"
// Forward declarations for logging functions
void log_info(const char* message);
void log_success(const char* message);
void log_error(const char* message);
void log_warning(const char* message);
// Forward declarations for database functions // Forward declarations for database functions
int store_event(cJSON* event); int store_event(cJSON* event);
@@ -35,7 +29,7 @@ int handle_embedded_file_request(struct lws* wsi, const char* requested_uri) {
snprintf(temp_path, sizeof(temp_path), "/%s", requested_uri + 5); // Add leading slash snprintf(temp_path, sizeof(temp_path), "/%s", requested_uri + 5); // Add leading slash
file_path = temp_path; file_path = temp_path;
} else { } else {
log_warning("Embedded file request without /api prefix"); DEBUG_WARN("Embedded file request without /api prefix");
lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL); lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL);
return -1; return -1;
} }
@@ -43,7 +37,7 @@ int handle_embedded_file_request(struct lws* wsi, const char* requested_uri) {
// Get embedded file // Get embedded file
embedded_file_t* file = get_embedded_file(file_path); embedded_file_t* file = get_embedded_file(file_path);
if (!file) { if (!file) {
log_warning("Embedded file not found"); DEBUG_WARN("Embedded file not found");
lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL); lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL);
return -1; return -1;
} }
@@ -51,7 +45,7 @@ int handle_embedded_file_request(struct lws* wsi, const char* requested_uri) {
// Allocate session data // Allocate session data
struct embedded_file_session_data* session_data = malloc(sizeof(struct embedded_file_session_data)); struct embedded_file_session_data* session_data = malloc(sizeof(struct embedded_file_session_data));
if (!session_data) { if (!session_data) {
log_error("Failed to allocate embedded file session data"); DEBUG_ERROR("Failed to allocate embedded file session data");
return -1; return -1;
} }
@@ -135,7 +129,7 @@ int handle_embedded_file_writeable(struct lws* wsi) {
// Allocate buffer for data transmission // Allocate buffer for data transmission
unsigned char *buf = malloc(LWS_PRE + session_data->size); unsigned char *buf = malloc(LWS_PRE + session_data->size);
if (!buf) { if (!buf) {
log_error("Failed to allocate buffer for embedded file transmission"); DEBUG_ERROR("Failed to allocate buffer for embedded file transmission");
free(session_data); free(session_data);
lws_set_wsi_user(wsi, NULL); lws_set_wsi_user(wsi, NULL);
return -1; return -1;
@@ -151,7 +145,7 @@ int handle_embedded_file_writeable(struct lws* wsi) {
free(buf); free(buf);
if (write_result < 0) { if (write_result < 0) {
log_error("Failed to write embedded file data"); DEBUG_ERROR("Failed to write embedded file data");
free(session_data); free(session_data);
lws_set_wsi_user(wsi, NULL); lws_set_wsi_user(wsi, NULL);
return -1; return -1;

File diff suppressed because it is too large Load Diff

View File

@@ -105,6 +105,7 @@ typedef struct {
char admin_pubkey_override[65]; // Empty string = not set, 64-char hex = override char admin_pubkey_override[65]; // Empty string = not set, 64-char hex = override
char relay_privkey_override[65]; // Empty string = not set, 64-char hex = override char relay_privkey_override[65]; // Empty string = not set, 64-char hex = override
int strict_port; // 0 = allow port increment, 1 = fail if exact port unavailable int strict_port; // 0 = allow port increment, 1 = fail if exact port unavailable
int debug_level; // 0-5, default 0 (no debug output)
} cli_options_t; } cli_options_t;
// Global unified configuration cache // Global unified configuration cache

51
src/debug.c Normal file
View File

@@ -0,0 +1,51 @@
#include "debug.h"
#include <stdarg.h>
#include <string.h>
// Global debug level (default: no debug output)
debug_level_t g_debug_level = DEBUG_LEVEL_NONE;
void debug_init(int level) {
if (level < 0) level = 0;
if (level > 5) level = 5;
g_debug_level = (debug_level_t)level;
}
void debug_log(debug_level_t level, const char* file, int line, const char* format, ...) {
// Get timestamp
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);
// Get level string
const char* level_str = "UNKNOWN";
switch (level) {
case DEBUG_LEVEL_ERROR: level_str = "ERROR"; break;
case DEBUG_LEVEL_WARN: level_str = "WARN "; break;
case DEBUG_LEVEL_INFO: level_str = "INFO "; break;
case DEBUG_LEVEL_DEBUG: level_str = "DEBUG"; break;
case DEBUG_LEVEL_TRACE: level_str = "TRACE"; break;
default: break;
}
// Print prefix with timestamp and level
printf("[%s] [%s] ", timestamp, level_str);
// Print source location when debug level is TRACE (5) or higher
if (file && g_debug_level >= DEBUG_LEVEL_TRACE) {
// Extract just the filename (not full path)
const char* filename = strrchr(file, '/');
filename = filename ? filename + 1 : file;
printf("[%s:%d] ", filename, line);
}
// Print message
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
printf("\n");
fflush(stdout);
}

43
src/debug.h Normal file
View File

@@ -0,0 +1,43 @@
#ifndef DEBUG_H
#define DEBUG_H
#include <stdio.h>
#include <time.h>
// Debug levels
typedef enum {
DEBUG_LEVEL_NONE = 0,
DEBUG_LEVEL_ERROR = 1,
DEBUG_LEVEL_WARN = 2,
DEBUG_LEVEL_INFO = 3,
DEBUG_LEVEL_DEBUG = 4,
DEBUG_LEVEL_TRACE = 5
} debug_level_t;
// Global debug level (set at runtime via CLI)
extern debug_level_t g_debug_level;
// Initialize debug system
void debug_init(int level);
// Core logging function
void debug_log(debug_level_t level, const char* file, int line, const char* format, ...);
// Convenience macros that check level before calling
// Note: TRACE level (5) and above include file:line information for ALL messages
#define DEBUG_ERROR(...) \
do { if (g_debug_level >= DEBUG_LEVEL_ERROR) debug_log(DEBUG_LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__); } while(0)
#define DEBUG_WARN(...) \
do { if (g_debug_level >= DEBUG_LEVEL_WARN) debug_log(DEBUG_LEVEL_WARN, __FILE__, __LINE__, __VA_ARGS__); } while(0)
#define DEBUG_INFO(...) \
do { if (g_debug_level >= DEBUG_LEVEL_INFO) debug_log(DEBUG_LEVEL_INFO, __FILE__, __LINE__, __VA_ARGS__); } while(0)
#define DEBUG_LOG(...) \
do { if (g_debug_level >= DEBUG_LEVEL_DEBUG) debug_log(DEBUG_LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__); } while(0)
#define DEBUG_TRACE(...) \
do { if (g_debug_level >= DEBUG_LEVEL_TRACE) debug_log(DEBUG_LEVEL_TRACE, __FILE__, __LINE__, __VA_ARGS__); } while(0)
#endif /* DEBUG_H */

View File

@@ -1,5 +1,6 @@
#define _GNU_SOURCE #define _GNU_SOURCE
#include "config.h" #include "config.h"
#include "debug.h"
#include "../nostr_core_lib/nostr_core/nostr_core.h" #include "../nostr_core_lib/nostr_core/nostr_core.h"
#include "../nostr_core_lib/nostr_core/nip017.h" #include "../nostr_core_lib/nostr_core/nip017.h"
#include "../nostr_core_lib/nostr_core/nip044.h" #include "../nostr_core_lib/nostr_core/nip044.h"
@@ -15,12 +16,6 @@
// External database connection (from main.c) // External database connection (from main.c)
extern sqlite3* g_db; extern sqlite3* g_db;
// Logging functions (defined in main.c)
extern void log_info(const char* message);
extern void log_success(const char* message);
extern void log_warning(const char* message);
extern void log_error(const char* message);
// Forward declarations for unified handlers // Forward declarations for unified handlers
extern int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi); extern int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi);
extern int handle_config_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi); extern int handle_config_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi);
@@ -137,14 +132,14 @@ cJSON* process_nip17_admin_message(cJSON* gift_wrap_event, char* error_message,
// This handles commands sent as direct JSON arrays, not wrapped in inner events // This handles commands sent as direct JSON arrays, not wrapped in inner events
int process_dm_admin_command(cJSON* command_array, cJSON* event, char* error_message, size_t error_size, struct lws* wsi) { int process_dm_admin_command(cJSON* command_array, cJSON* event, char* error_message, size_t error_size, struct lws* wsi) {
if (!command_array || !cJSON_IsArray(command_array) || !event) { if (!command_array || !cJSON_IsArray(command_array) || !event) {
log_error("DM Admin: Invalid command array or event"); DEBUG_ERROR("DM Admin: Invalid command array or event");
snprintf(error_message, error_size, "invalid: null command array or event"); snprintf(error_message, error_size, "invalid: null command array or event");
return -1; return -1;
} }
int array_size = cJSON_GetArraySize(command_array); int array_size = cJSON_GetArraySize(command_array);
if (array_size < 1) { if (array_size < 1) {
log_error("DM Admin: Empty command array"); DEBUG_ERROR("DM Admin: Empty command array");
snprintf(error_message, error_size, "invalid: empty command array"); snprintf(error_message, error_size, "invalid: empty command array");
return -1; return -1;
} }
@@ -152,7 +147,7 @@ int process_dm_admin_command(cJSON* command_array, cJSON* event, char* error_mes
// Get the command type from the first element // Get the command type from the first element
cJSON* command_item = cJSON_GetArrayItem(command_array, 0); cJSON* command_item = cJSON_GetArrayItem(command_array, 0);
if (!command_item || !cJSON_IsString(command_item)) { if (!command_item || !cJSON_IsString(command_item)) {
log_error("DM Admin: First element is not a string command"); DEBUG_ERROR("DM Admin: First element is not a string command");
snprintf(error_message, error_size, "invalid: command must be a string"); snprintf(error_message, error_size, "invalid: command must be a string");
return -1; return -1;
} }
@@ -209,7 +204,7 @@ int process_dm_admin_command(cJSON* command_array, cJSON* event, char* error_mes
if (strcmp(command_type, "auth_query") == 0) { if (strcmp(command_type, "auth_query") == 0) {
const char* query_type = get_tag_value(event, "auth_query", 1); const char* query_type = get_tag_value(event, "auth_query", 1);
if (!query_type) { if (!query_type) {
log_error("DM Admin: Missing auth_query type parameter"); DEBUG_ERROR("DM Admin: Missing auth_query type parameter");
snprintf(error_message, error_size, "invalid: missing auth_query type"); snprintf(error_message, error_size, "invalid: missing auth_query type");
} else { } else {
result = handle_auth_query_unified(event, query_type, error_message, error_size, wsi); result = handle_auth_query_unified(event, query_type, error_message, error_size, wsi);
@@ -218,7 +213,7 @@ int process_dm_admin_command(cJSON* command_array, cJSON* event, char* error_mes
else if (strcmp(command_type, "config_query") == 0) { else if (strcmp(command_type, "config_query") == 0) {
const char* query_type = get_tag_value(event, "config_query", 1); const char* query_type = get_tag_value(event, "config_query", 1);
if (!query_type) { if (!query_type) {
log_error("DM Admin: Missing config_query type parameter"); DEBUG_ERROR("DM Admin: Missing config_query type parameter");
snprintf(error_message, error_size, "invalid: missing config_query type"); snprintf(error_message, error_size, "invalid: missing config_query type");
} else { } else {
result = handle_config_query_unified(event, query_type, error_message, error_size, wsi); result = handle_config_query_unified(event, query_type, error_message, error_size, wsi);
@@ -228,7 +223,7 @@ int process_dm_admin_command(cJSON* command_array, cJSON* event, char* error_mes
const char* config_key = get_tag_value(event, "config_set", 1); const char* config_key = get_tag_value(event, "config_set", 1);
const char* config_value = get_tag_value(event, "config_set", 2); const char* config_value = get_tag_value(event, "config_set", 2);
if (!config_key || !config_value) { if (!config_key || !config_value) {
log_error("DM Admin: Missing config_set parameters"); DEBUG_ERROR("DM Admin: Missing config_set parameters");
snprintf(error_message, error_size, "invalid: missing config_set key or value"); snprintf(error_message, error_size, "invalid: missing config_set key or value");
} else { } else {
result = handle_config_set_unified(event, config_key, config_value, error_message, error_size, wsi); result = handle_config_set_unified(event, config_key, config_value, error_message, error_size, wsi);
@@ -240,7 +235,7 @@ int process_dm_admin_command(cJSON* command_array, cJSON* event, char* error_mes
else if (strcmp(command_type, "system_command") == 0) { else if (strcmp(command_type, "system_command") == 0) {
const char* command = get_tag_value(event, "system_command", 1); const char* command = get_tag_value(event, "system_command", 1);
if (!command) { if (!command) {
log_error("DM Admin: Missing system_command type parameter"); DEBUG_ERROR("DM Admin: Missing system_command type parameter");
snprintf(error_message, error_size, "invalid: missing system_command type"); snprintf(error_message, error_size, "invalid: missing system_command type");
} else { } else {
result = handle_system_command_unified(event, command, error_message, error_size, wsi); result = handle_system_command_unified(event, command, error_message, error_size, wsi);
@@ -253,13 +248,13 @@ int process_dm_admin_command(cJSON* command_array, cJSON* event, char* error_mes
result = handle_auth_rule_modification_unified(event, error_message, error_size, wsi); result = handle_auth_rule_modification_unified(event, error_message, error_size, wsi);
} }
else { else {
log_error("DM Admin: Unknown command type"); DEBUG_ERROR("DM Admin: Unknown command type");
printf(" Unknown command: %s\n", command_type); printf(" Unknown command: %s\n", command_type);
snprintf(error_message, error_size, "invalid: unknown DM command type '%s'", command_type); snprintf(error_message, error_size, "invalid: unknown DM command type '%s'", command_type);
} }
if (result != 0) { if (result != 0) {
log_error("DM Admin: Command processing failed"); DEBUG_ERROR("DM Admin: Command processing failed");
} }
return result; return result;
@@ -592,7 +587,7 @@ int apply_config_change(const char* key, const char* value) {
extern sqlite3* g_db; extern sqlite3* g_db;
if (!g_db) { if (!g_db) {
log_error("Database not available for config change"); DEBUG_ERROR("Database not available for config change");
return -1; return -1;
} }
@@ -628,9 +623,9 @@ int apply_config_change(const char* key, const char* value) {
const char* sql = "INSERT OR REPLACE INTO config (key, value, data_type) VALUES (?, ?, ?)"; const char* sql = "INSERT OR REPLACE INTO config (key, value, data_type) VALUES (?, ?, ?)";
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) { if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
log_error("Failed to prepare config update statement"); DEBUG_ERROR("Failed to prepare config update statement");
const char* err_msg = sqlite3_errmsg(g_db); const char* err_msg = sqlite3_errmsg(g_db);
log_error(err_msg); DEBUG_ERROR(err_msg);
return -1; return -1;
} }
@@ -640,9 +635,9 @@ int apply_config_change(const char* key, const char* value) {
int result = sqlite3_step(stmt); int result = sqlite3_step(stmt);
if (result != SQLITE_DONE) { if (result != SQLITE_DONE) {
log_error("Failed to update configuration in database"); DEBUG_ERROR("Failed to update configuration in database");
const char* err_msg = sqlite3_errmsg(g_db); const char* err_msg = sqlite3_errmsg(g_db);
log_error(err_msg); DEBUG_ERROR(err_msg);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
return -1; return -1;
} }
@@ -766,7 +761,7 @@ int handle_config_confirmation(const char* admin_pubkey, const char* response) {
char error_msg[256]; char error_msg[256];
int send_result = send_nip17_response(admin_pubkey, success_msg, error_msg, sizeof(error_msg)); int send_result = send_nip17_response(admin_pubkey, success_msg, error_msg, sizeof(error_msg));
if (send_result != 0) { if (send_result != 0) {
log_error(error_msg); DEBUG_ERROR(error_msg);
} }
// Remove the pending change // Remove the pending change
@@ -788,7 +783,7 @@ int handle_config_confirmation(const char* admin_pubkey, const char* response) {
char send_error_msg[256]; char send_error_msg[256];
int send_result = send_nip17_response(admin_pubkey, error_msg, send_error_msg, sizeof(send_error_msg)); int send_result = send_nip17_response(admin_pubkey, error_msg, send_error_msg, sizeof(send_error_msg));
if (send_result != 0) { if (send_result != 0) {
log_error(send_error_msg); DEBUG_ERROR(send_error_msg);
} }
// Remove the pending change // Remove the pending change
@@ -890,7 +885,7 @@ int process_config_change_request(const char* admin_pubkey, const char* message)
char error_msg[256]; char error_msg[256];
int send_result = send_nip17_response(admin_pubkey, confirmation, error_msg, sizeof(error_msg)); int send_result = send_nip17_response(admin_pubkey, confirmation, error_msg, sizeof(error_msg));
if (send_result != 0) { if (send_result != 0) {
log_error(error_msg); DEBUG_ERROR(error_msg);
} }
free(confirmation); free(confirmation);
} }
@@ -903,7 +898,7 @@ int process_config_change_request(const char* admin_pubkey, const char* message)
char* generate_stats_json(void) { char* generate_stats_json(void) {
extern sqlite3* g_db; extern sqlite3* g_db;
if (!g_db) { if (!g_db) {
log_error("Database not available for stats generation"); DEBUG_ERROR("Database not available for stats generation");
return NULL; return NULL;
} }
@@ -1007,7 +1002,7 @@ char* generate_stats_json(void) {
cJSON_Delete(response); cJSON_Delete(response);
if (!json_string) { if (!json_string) {
log_error("Failed to generate stats JSON"); DEBUG_ERROR("Failed to generate stats JSON");
} }
return json_string; return json_string;
@@ -1113,14 +1108,14 @@ int send_nip17_response(const char* sender_pubkey, const char* response_content,
char* generate_config_text(void) { char* generate_config_text(void) {
extern sqlite3* g_db; extern sqlite3* g_db;
if (!g_db) { if (!g_db) {
log_error("NIP-17: Database not available for config query"); DEBUG_ERROR("NIP-17: Database not available for config query");
return NULL; return NULL;
} }
// Build comprehensive config text from database // Build comprehensive config text from database
char* config_text = malloc(8192); char* config_text = malloc(8192);
if (!config_text) { if (!config_text) {
log_error("NIP-17: Failed to allocate memory for config text"); DEBUG_ERROR("NIP-17: Failed to allocate memory for config text");
return NULL; return NULL;
} }
@@ -1146,7 +1141,7 @@ char* generate_config_text(void) {
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
} else { } else {
free(config_text); free(config_text);
log_error("NIP-17: Failed to query config from database"); DEBUG_ERROR("NIP-17: Failed to query config from database");
return NULL; return NULL;
} }
@@ -1161,7 +1156,7 @@ char* generate_config_text(void) {
char* generate_stats_text(void) { char* generate_stats_text(void) {
char* stats_json = generate_stats_json(); char* stats_json = generate_stats_json();
if (!stats_json) { if (!stats_json) {
log_error("NIP-17: Failed to generate stats for plain text command"); DEBUG_ERROR("NIP-17: Failed to generate stats for plain text command");
return NULL; return NULL;
} }
@@ -1345,7 +1340,7 @@ cJSON* process_nip17_admin_message(cJSON* gift_wrap_event, char* error_message,
// Convert hex private key to bytes // Convert hex private key to bytes
unsigned char relay_privkey[32]; unsigned char relay_privkey[32];
if (nostr_hex_to_bytes(relay_privkey_hex, relay_privkey, sizeof(relay_privkey)) != 0) { if (nostr_hex_to_bytes(relay_privkey_hex, relay_privkey, sizeof(relay_privkey)) != 0) {
log_error("NIP-17: Failed to convert relay private key from hex"); DEBUG_ERROR("NIP-17: Failed to convert relay private key from hex");
free(relay_privkey_hex); free(relay_privkey_hex);
strncpy(error_message, "NIP-17: Failed to convert relay private key", error_size - 1); strncpy(error_message, "NIP-17: Failed to convert relay private key", error_size - 1);
return NULL; return NULL;
@@ -1355,13 +1350,13 @@ cJSON* process_nip17_admin_message(cJSON* gift_wrap_event, char* error_message,
// Step 3: Decrypt and parse inner event using library function // Step 3: Decrypt and parse inner event using library function
cJSON* inner_dm = nostr_nip17_receive_dm(gift_wrap_event, relay_privkey); cJSON* inner_dm = nostr_nip17_receive_dm(gift_wrap_event, relay_privkey);
if (!inner_dm) { if (!inner_dm) {
log_error("NIP-17: nostr_nip17_receive_dm returned NULL"); DEBUG_ERROR("NIP-17: nostr_nip17_receive_dm returned NULL");
// Debug: Print the gift wrap event // Debug: Print the gift wrap event
char* gift_wrap_debug = cJSON_Print(gift_wrap_event); char* gift_wrap_debug = cJSON_Print(gift_wrap_event);
if (gift_wrap_debug) { if (gift_wrap_debug) {
char debug_msg[1024]; char debug_msg[1024];
snprintf(debug_msg, sizeof(debug_msg), "NIP-17: Gift wrap event: %.500s", gift_wrap_debug); snprintf(debug_msg, sizeof(debug_msg), "NIP-17: Gift wrap event: %.500s", gift_wrap_debug);
log_error(debug_msg); DEBUG_ERROR(debug_msg);
free(gift_wrap_debug); free(gift_wrap_debug);
} }
// Debug: Check if private key is valid // Debug: Check if private key is valid
@@ -1525,7 +1520,7 @@ int is_nip17_gift_wrap_for_relay(cJSON* event) {
const char* relay_pubkey = get_relay_pubkey_cached(); const char* relay_pubkey = get_relay_pubkey_cached();
if (!relay_pubkey) { if (!relay_pubkey) {
log_error("NIP-17: Could not get relay pubkey for validation"); DEBUG_ERROR("NIP-17: Could not get relay pubkey for validation");
return 0; return 0;
} }
@@ -1605,7 +1600,7 @@ int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t err
free(stats_text); free(stats_text);
if (result != 0) { if (result != 0) {
log_error(error_msg); DEBUG_ERROR(error_msg);
return -1; return -1;
} }
@@ -1623,7 +1618,7 @@ int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t err
free(config_text); free(config_text);
if (result != 0) { if (result != 0) {
log_error(error_msg); DEBUG_ERROR(error_msg);
return -1; return -1;
} }
@@ -1657,7 +1652,7 @@ int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t err
if (config_result > 0) { if (config_result > 0) {
return 1; // Return positive value to indicate response was handled return 1; // Return positive value to indicate response was handled
} else { } else {
log_error("NIP-17: Configuration change request failed"); DEBUG_ERROR("NIP-17: Configuration change request failed");
return -1; // Return error to prevent generic success response return -1; // Return error to prevent generic success response
} }
} }
@@ -1697,7 +1692,7 @@ int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t err
cJSON_Delete(command_array); cJSON_Delete(command_array);
if (result != 0) { if (result != 0) {
log_error(error_msg); DEBUG_ERROR(error_msg);
strncpy(error_message, error_msg, error_size - 1); strncpy(error_message, error_msg, error_size - 1);
return -1; return -1;
} }

File diff suppressed because one or more lines are too long

View File

@@ -24,6 +24,7 @@
#include "sql_schema.h" // Embedded database schema #include "sql_schema.h" // Embedded database schema
#include "websockets.h" // WebSocket protocol implementation #include "websockets.h" // WebSocket protocol implementation
#include "subscriptions.h" // Subscription management system #include "subscriptions.h" // Subscription management system
#include "debug.h" // Debug system
// Forward declarations for unified request validator // Forward declarations for unified request validator
int nostr_validate_unified_request(const char* json_string, size_t json_length); int nostr_validate_unified_request(const char* json_string, size_t json_length);
@@ -95,11 +96,7 @@ extern subscription_manager_t g_subscription_manager;
// Forward declarations for logging functions // Forward declarations for logging functions - REMOVED (replaced by debug system)
void log_info(const char* message);
void log_success(const char* message);
void log_error(const char* message);
void log_warning(const char* message);
// Forward declaration for subscription manager configuration // Forward declaration for subscription manager configuration
void update_subscription_manager_config(void); void update_subscription_manager_config(void);
@@ -189,41 +186,7 @@ int validate_event_expiration(cJSON* event, char* error_message, size_t error_si
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
// Helper function to get current timestamp string // Logging functions - REMOVED (replaced by debug system in debug.c)
static void get_timestamp_string(char* buffer, size_t buffer_size) {
time_t now = time(NULL);
struct tm* local_time = localtime(&now);
strftime(buffer, buffer_size, "%Y-%m-%d %H:%M:%S", local_time);
}
// Logging functions
void log_info(const char* message) {
char timestamp[32];
get_timestamp_string(timestamp, sizeof(timestamp));
printf("[%s] [INFO] %s\n", timestamp, message);
fflush(stdout);
}
void log_success(const char* message) {
char timestamp[32];
get_timestamp_string(timestamp, sizeof(timestamp));
printf("[%s] [SUCCESS] %s\n", timestamp, message);
fflush(stdout);
}
void log_error(const char* message) {
char timestamp[32];
get_timestamp_string(timestamp, sizeof(timestamp));
printf("[%s] [ERROR] %s\n", timestamp, message);
fflush(stdout);
}
void log_warning(const char* message) {
char timestamp[32];
get_timestamp_string(timestamp, sizeof(timestamp));
printf("[%s] [WARNING] %s\n", timestamp, message);
fflush(stdout);
}
// Update subscription manager configuration from config system // Update subscription manager configuration from config system
void update_subscription_manager_config(void) { void update_subscription_manager_config(void) {
@@ -280,8 +243,56 @@ void send_notice_message(struct lws* wsi, const char* message) {
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
// Clean up stale SQLite WAL files that may cause lock issues after unclean shutdown
static void cleanup_stale_wal_files(const char* db_path) {
if (!db_path) return;
// Check if database file exists
if (access(db_path, F_OK) != 0) {
return; // Database doesn't exist yet, nothing to clean
}
// Build paths for WAL and SHM files
char wal_path[1024];
char shm_path[1024];
snprintf(wal_path, sizeof(wal_path), "%s-wal", db_path);
snprintf(shm_path, sizeof(shm_path), "%s-shm", db_path);
// Check if WAL or SHM files exist
int has_wal = (access(wal_path, F_OK) == 0);
int has_shm = (access(shm_path, F_OK) == 0);
if (has_wal || has_shm) {
DEBUG_WARN("Detected stale SQLite WAL files from previous unclean shutdown");
// Try to remove WAL file
if (has_wal) {
if (unlink(wal_path) == 0) {
DEBUG_INFO("Removed stale WAL file");
} else {
char error_msg[256];
snprintf(error_msg, sizeof(error_msg), "Failed to remove WAL file: %s", strerror(errno));
DEBUG_WARN(error_msg);
}
}
// Try to remove SHM file
if (has_shm) {
if (unlink(shm_path) == 0) {
DEBUG_INFO("Removed stale SHM file");
} else {
char error_msg[256];
snprintf(error_msg, sizeof(error_msg), "Failed to remove SHM file: %s", strerror(errno));
DEBUG_WARN(error_msg);
}
}
}
}
// Initialize database connection and schema // Initialize database connection and schema
int init_database(const char* database_path_override) { int init_database(const char* database_path_override) {
DEBUG_TRACE("Entering init_database()");
// Priority 1: Command line database path override // Priority 1: Command line database path override
const char* db_path = database_path_override; const char* db_path = database_path_override;
@@ -295,9 +306,15 @@ int init_database(const char* database_path_override) {
db_path = DEFAULT_DATABASE_PATH; db_path = DEFAULT_DATABASE_PATH;
} }
DEBUG_LOG("Initializing database: %s", db_path);
// Clean up stale WAL files before opening database
cleanup_stale_wal_files(db_path);
int rc = sqlite3_open(db_path, &g_db); int rc = sqlite3_open(db_path, &g_db);
if (rc != SQLITE_OK) { if (rc != SQLITE_OK) {
log_error("Cannot open database"); DEBUG_ERROR("Cannot open database");
DEBUG_TRACE("Exiting init_database() - failed to open database");
return -1; return -1;
} }
@@ -330,7 +347,7 @@ int init_database(const char* database_path_override) {
} else { } else {
char warning_msg[256]; char warning_msg[256];
snprintf(warning_msg, sizeof(warning_msg), "Unknown database schema version: %s", db_version); snprintf(warning_msg, sizeof(warning_msg), "Unknown database schema version: %s", db_version);
log_warning(warning_msg); DEBUG_WARN(warning_msg);
} }
} else { } else {
needs_migration = 1; needs_migration = 1;
@@ -373,7 +390,7 @@ int init_database(const char* database_path_override) {
char error_log[512]; char error_log[512];
snprintf(error_log, sizeof(error_log), "Failed to create auth_rules table: %s", snprintf(error_log, sizeof(error_log), "Failed to create auth_rules table: %s",
error_msg ? error_msg : "unknown error"); error_msg ? error_msg : "unknown error");
log_error(error_log); DEBUG_ERROR(error_log);
if (error_msg) sqlite3_free(error_msg); if (error_msg) sqlite3_free(error_msg);
return -1; return -1;
} }
@@ -390,7 +407,7 @@ int init_database(const char* database_path_override) {
char index_error_log[512]; char index_error_log[512];
snprintf(index_error_log, sizeof(index_error_log), "Failed to create auth_rules indexes: %s", snprintf(index_error_log, sizeof(index_error_log), "Failed to create auth_rules indexes: %s",
index_error_msg ? index_error_msg : "unknown error"); index_error_msg ? index_error_msg : "unknown error");
log_error(index_error_log); DEBUG_ERROR(index_error_log);
if (index_error_msg) sqlite3_free(index_error_msg); if (index_error_msg) sqlite3_free(index_error_msg);
return -1; return -1;
} }
@@ -409,7 +426,7 @@ int init_database(const char* database_path_override) {
char error_log[512]; char error_log[512];
snprintf(error_log, sizeof(error_log), "Failed to update schema version: %s", snprintf(error_log, sizeof(error_log), "Failed to update schema version: %s",
error_msg ? error_msg : "unknown error"); error_msg ? error_msg : "unknown error");
log_error(error_log); DEBUG_ERROR(error_log);
if (error_msg) sqlite3_free(error_msg); if (error_msg) sqlite3_free(error_msg);
return -1; return -1;
} }
@@ -425,7 +442,7 @@ int init_database(const char* database_path_override) {
char error_log[512]; char error_log[512];
snprintf(error_log, sizeof(error_log), "Failed to initialize database schema: %s", snprintf(error_log, sizeof(error_log), "Failed to initialize database schema: %s",
error_msg ? error_msg : "unknown error"); error_msg ? error_msg : "unknown error");
log_error(error_log); DEBUG_ERROR(error_log);
if (error_msg) { if (error_msg) {
sqlite3_free(error_msg); sqlite3_free(error_msg);
} }
@@ -434,19 +451,51 @@ int init_database(const char* database_path_override) {
} }
} else { } else {
log_error("Failed to check existing database schema"); DEBUG_ERROR("Failed to check existing database schema");
return -1; return -1;
} }
// Enable WAL mode for better concurrency and crash recovery
char* wal_error = NULL;
rc = sqlite3_exec(g_db, "PRAGMA journal_mode=WAL;", NULL, NULL, &wal_error);
if (rc != SQLITE_OK) {
char error_msg[256];
snprintf(error_msg, sizeof(error_msg), "Failed to enable WAL mode: %s",
wal_error ? wal_error : "unknown error");
DEBUG_WARN(error_msg);
if (wal_error) sqlite3_free(wal_error);
// Continue anyway - WAL mode is optional
} else {
DEBUG_LOG("SQLite WAL mode enabled");
}
DEBUG_TRACE("Exiting init_database() - success");
return 0; return 0;
} }
// Close database connection // Close database connection with proper WAL checkpoint
void close_database() { void close_database() {
DEBUG_TRACE("Entering close_database()");
if (g_db) { if (g_db) {
// Perform WAL checkpoint to minimize stale files on next startup
DEBUG_LOG("Performing WAL checkpoint before database close");
char* checkpoint_error = NULL;
int rc = sqlite3_exec(g_db, "PRAGMA wal_checkpoint(TRUNCATE);", NULL, NULL, &checkpoint_error);
if (rc != SQLITE_OK) {
char error_msg[256];
snprintf(error_msg, sizeof(error_msg), "WAL checkpoint warning: %s",
checkpoint_error ? checkpoint_error : "unknown error");
DEBUG_WARN(error_msg);
if (checkpoint_error) sqlite3_free(checkpoint_error);
}
sqlite3_close(g_db); sqlite3_close(g_db);
g_db = NULL; g_db = NULL;
DEBUG_LOG("Database connection closed");
} }
DEBUG_TRACE("Exiting close_database()");
} }
// Event type classification // Event type classification
@@ -615,7 +664,7 @@ int store_event(cJSON* event) {
cJSON* tags = cJSON_GetObjectItem(event, "tags"); cJSON* tags = cJSON_GetObjectItem(event, "tags");
if (!id || !pubkey || !created_at || !kind || !content || !sig) { if (!id || !pubkey || !created_at || !kind || !content || !sig) {
log_error("Invalid event - missing required fields"); DEBUG_ERROR("Invalid event - missing required fields");
return -1; return -1;
} }
@@ -631,7 +680,7 @@ int store_event(cJSON* event) {
} }
if (!tags_json) { if (!tags_json) {
log_error("Failed to serialize tags to JSON"); DEBUG_ERROR("Failed to serialize tags to JSON");
return -1; return -1;
} }
@@ -643,7 +692,7 @@ int store_event(cJSON* event) {
sqlite3_stmt* stmt; sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL); int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) { if (rc != SQLITE_OK) {
log_error("Failed to prepare event insert statement"); DEBUG_ERROR("Failed to prepare event insert statement");
free(tags_json); free(tags_json);
return -1; return -1;
} }
@@ -664,13 +713,13 @@ int store_event(cJSON* event) {
if (rc != SQLITE_DONE) { if (rc != SQLITE_DONE) {
if (rc == SQLITE_CONSTRAINT) { if (rc == SQLITE_CONSTRAINT) {
log_warning("Event already exists in database"); DEBUG_WARN("Event already exists in database");
free(tags_json); free(tags_json);
return 0; // Not an error, just duplicate return 0; // Not an error, just duplicate
} }
char error_msg[256]; char error_msg[256];
snprintf(error_msg, sizeof(error_msg), "Failed to insert event: %s", sqlite3_errmsg(g_db)); snprintf(error_msg, sizeof(error_msg), "Failed to insert event: %s", sqlite3_errmsg(g_db));
log_error(error_msg); DEBUG_ERROR(error_msg);
free(tags_json); free(tags_json);
return -1; return -1;
} }
@@ -739,7 +788,7 @@ cJSON* retrieve_event(const char* event_id) {
int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, struct per_session_data *pss) { int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, struct per_session_data *pss) {
if (!cJSON_IsArray(filters)) { if (!cJSON_IsArray(filters)) {
log_error("REQ filters is not an array"); DEBUG_ERROR("REQ filters is not an array");
return 0; return 0;
} }
@@ -783,7 +832,7 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
// Check session subscription limits // Check session subscription limits
if (pss->subscription_count >= g_subscription_manager.max_subscriptions_per_client) { if (pss->subscription_count >= g_subscription_manager.max_subscriptions_per_client) {
log_error("Maximum subscriptions per client exceeded"); DEBUG_ERROR("Maximum subscriptions per client exceeded");
// Update rate limiting counters for failed attempt // Update rate limiting counters for failed attempt
pss->failed_subscription_attempts++; pss->failed_subscription_attempts++;
@@ -877,13 +926,13 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
// Create persistent subscription // Create persistent subscription
subscription_t* subscription = create_subscription(sub_id, wsi, filters, pss ? pss->client_ip : "unknown"); subscription_t* subscription = create_subscription(sub_id, wsi, filters, pss ? pss->client_ip : "unknown");
if (!subscription) { if (!subscription) {
log_error("Failed to create subscription"); DEBUG_ERROR("Failed to create subscription");
return has_config_request ? config_events_sent : 0; return has_config_request ? config_events_sent : 0;
} }
// Add to global manager // Add to global manager
if (add_subscription_to_manager(subscription) != 0) { if (add_subscription_to_manager(subscription) != 0) {
log_error("Failed to add subscription to global manager"); DEBUG_ERROR("Failed to add subscription to global manager");
free_subscription(subscription); free_subscription(subscription);
// Send CLOSED notice // Send CLOSED notice
@@ -931,7 +980,7 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
for (int i = 0; i < cJSON_GetArraySize(filters); i++) { for (int i = 0; i < cJSON_GetArraySize(filters); i++) {
cJSON* filter = cJSON_GetArrayItem(filters, i); cJSON* filter = cJSON_GetArrayItem(filters, i);
if (!filter || !cJSON_IsObject(filter)) { if (!filter || !cJSON_IsObject(filter)) {
log_warning("Invalid filter object"); DEBUG_WARN("Invalid filter object");
continue; continue;
} }
@@ -1174,7 +1223,7 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
if (rc != SQLITE_OK) { if (rc != SQLITE_OK) {
char error_msg[256]; char error_msg[256];
snprintf(error_msg, sizeof(error_msg), "Failed to prepare subscription query: %s", sqlite3_errmsg(g_db)); snprintf(error_msg, sizeof(error_msg), "Failed to prepare subscription query: %s", sqlite3_errmsg(g_db));
log_error(error_msg); DEBUG_ERROR(error_msg);
continue; continue;
} }
@@ -1267,6 +1316,7 @@ int is_authorized_admin_event(cJSON* event, char* error_buffer, size_t error_buf
return -1; return -1;
} }
// Step 1: Verify event kind is admin type // Step 1: Verify event kind is admin type
cJSON *kind_json = cJSON_GetObjectItem(event, "kind"); cJSON *kind_json = cJSON_GetObjectItem(event, "kind");
if (!kind_json || !cJSON_IsNumber(kind_json)) { if (!kind_json || !cJSON_IsNumber(kind_json)) {
@@ -1318,7 +1368,7 @@ int is_authorized_admin_event(cJSON* event, char* error_buffer, size_t error_buf
// Step 3: Verify admin signature authorization // Step 3: Verify admin signature authorization
cJSON *pubkey_json = cJSON_GetObjectItem(event, "pubkey"); cJSON *pubkey_json = cJSON_GetObjectItem(event, "pubkey");
if (!pubkey_json || !cJSON_IsString(pubkey_json)) { if (!pubkey_json || !cJSON_IsString(pubkey_json)) {
log_warning("Unauthorized admin event attempt: missing or invalid pubkey"); DEBUG_WARN("Unauthorized admin event attempt: missing or invalid pubkey");
snprintf(error_buffer, error_buffer_size, "Unauthorized admin event attempt: missing pubkey"); snprintf(error_buffer, error_buffer_size, "Unauthorized admin event attempt: missing pubkey");
return -1; return -1;
} }
@@ -1326,29 +1376,32 @@ int is_authorized_admin_event(cJSON* event, char* error_buffer, size_t error_buf
// Get admin pubkey from configuration // Get admin pubkey from configuration
const char* admin_pubkey = get_config_value("admin_pubkey"); const char* admin_pubkey = get_config_value("admin_pubkey");
if (!admin_pubkey || strlen(admin_pubkey) == 0) { if (!admin_pubkey || strlen(admin_pubkey) == 0) {
log_warning("Unauthorized admin event attempt: no admin pubkey configured"); DEBUG_WARN("Unauthorized admin event attempt: no admin pubkey configured");
snprintf(error_buffer, error_buffer_size, "Unauthorized admin event attempt: no admin configured"); snprintf(error_buffer, error_buffer_size, "Unauthorized admin event attempt: no admin configured");
return -1; return -1;
} }
// Compare pubkeys // Compare pubkeys
if (strcmp(pubkey_json->valuestring, admin_pubkey) != 0) { if (strcmp(pubkey_json->valuestring, admin_pubkey) != 0) {
log_warning("Unauthorized admin event attempt: pubkey mismatch"); DEBUG_WARN("Unauthorized admin event attempt: pubkey mismatch");
char warning_msg[256]; char warning_msg[256];
snprintf(warning_msg, sizeof(warning_msg), snprintf(warning_msg, sizeof(warning_msg),
"Unauthorized admin event attempt from pubkey: %.32s...", pubkey_json->valuestring); "Unauthorized admin event attempt from pubkey: %.32s...", pubkey_json->valuestring);
log_warning(warning_msg); DEBUG_WARN(warning_msg);
DEBUG_INFO("DEBUG: Pubkey comparison failed - event pubkey != admin pubkey");
snprintf(error_buffer, error_buffer_size, "Unauthorized admin event attempt: invalid admin pubkey"); snprintf(error_buffer, error_buffer_size, "Unauthorized admin event attempt: invalid admin pubkey");
return -1; return -1;
} }
// Step 4: Verify event signature // Step 4: Verify event signature
if (nostr_verify_event_signature(event) != 0) { if (nostr_verify_event_signature(event) != 0) {
log_warning("Unauthorized admin event attempt: invalid signature"); DEBUG_WARN("Unauthorized admin event attempt: invalid signature");
snprintf(error_buffer, error_buffer_size, "Unauthorized admin event attempt: signature verification failed"); snprintf(error_buffer, error_buffer_size, "Unauthorized admin event attempt: signature verification failed");
return -1; return -1;
} }
// All checks passed - authorized admin event // All checks passed - authorized admin event
return 0; return 0;
} }
@@ -1375,6 +1428,8 @@ void print_usage(const char* program_name) {
printf(" --strict-port Fail if exact port is unavailable (no port increment)\n"); printf(" --strict-port Fail if exact port is unavailable (no port increment)\n");
printf(" -a, --admin-pubkey KEY Override admin public key (64-char hex or npub)\n"); printf(" -a, --admin-pubkey KEY Override admin public key (64-char hex or npub)\n");
printf(" -r, --relay-privkey KEY Override relay private key (64-char hex or nsec)\n"); printf(" -r, --relay-privkey KEY Override relay private key (64-char hex or nsec)\n");
printf(" --debug-level=N Set debug output level (0-5, default: 0)\n");
printf(" 0=none, 1=errors, 2=warnings, 3=info, 4=debug, 5=trace\n");
printf("\n"); printf("\n");
printf("Configuration:\n"); printf("Configuration:\n");
printf(" This relay uses event-based configuration stored in the database.\n"); printf(" This relay uses event-based configuration stored in the database.\n");
@@ -1412,7 +1467,8 @@ int main(int argc, char* argv[]) {
.port_override = -1, // -1 = not set .port_override = -1, // -1 = not set
.admin_pubkey_override = {0}, // Empty string = not set .admin_pubkey_override = {0}, // Empty string = not set
.relay_privkey_override = {0}, // Empty string = not set .relay_privkey_override = {0}, // Empty string = not set
.strict_port = 0 // 0 = allow port increment (default) .strict_port = 0, // 0 = allow port increment (default)
.debug_level = 0 // 0 = no debug output (default)
}; };
// Parse command line arguments // Parse command line arguments
@@ -1426,7 +1482,7 @@ int main(int argc, char* argv[]) {
} else if (strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--port") == 0) { } else if (strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--port") == 0) {
// Port override option // Port override option
if (i + 1 >= argc) { if (i + 1 >= argc) {
log_error("Port option requires a value. Use --help for usage information."); DEBUG_ERROR("Port option requires a value. Use --help for usage information.");
print_usage(argv[0]); print_usage(argv[0]);
return 1; return 1;
} }
@@ -1436,7 +1492,7 @@ int main(int argc, char* argv[]) {
long port = strtol(argv[i + 1], &endptr, 10); long port = strtol(argv[i + 1], &endptr, 10);
if (endptr == argv[i + 1] || *endptr != '\0' || port < 1 || port > 65535) { if (endptr == argv[i + 1] || *endptr != '\0' || port < 1 || port > 65535) {
log_error("Invalid port number. Port must be between 1 and 65535."); DEBUG_ERROR("Invalid port number. Port must be between 1 and 65535.");
print_usage(argv[0]); print_usage(argv[0]);
return 1; return 1;
} }
@@ -1449,7 +1505,7 @@ int main(int argc, char* argv[]) {
} else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--admin-pubkey") == 0) { } else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--admin-pubkey") == 0) {
// Admin public key override option // Admin public key override option
if (i + 1 >= argc) { if (i + 1 >= argc) {
log_error("Admin pubkey option requires a value. Use --help for usage information."); DEBUG_ERROR("Admin pubkey option requires a value. Use --help for usage information.");
print_usage(argv[0]); print_usage(argv[0]);
return 1; return 1;
} }
@@ -1467,7 +1523,7 @@ int main(int argc, char* argv[]) {
hex_ptr += 2; hex_ptr += 2;
} }
} else { } else {
log_error("Invalid admin public key format. Must be 64 hex characters or valid npub format."); DEBUG_ERROR("Invalid admin public key format. Must be 64 hex characters or valid npub format.");
print_usage(argv[0]); print_usage(argv[0]);
return 1; return 1;
} }
@@ -1479,7 +1535,7 @@ int main(int argc, char* argv[]) {
} else if (strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "--relay-privkey") == 0) { } else if (strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "--relay-privkey") == 0) {
// Relay private key override option // Relay private key override option
if (i + 1 >= argc) { if (i + 1 >= argc) {
log_error("Relay privkey option requires a value. Use --help for usage information."); DEBUG_ERROR("Relay privkey option requires a value. Use --help for usage information.");
print_usage(argv[0]); print_usage(argv[0]);
return 1; return 1;
} }
@@ -1497,7 +1553,7 @@ int main(int argc, char* argv[]) {
hex_ptr += 2; hex_ptr += 2;
} }
} else { } else {
log_error("Invalid relay private key format. Must be 64 hex characters or valid nsec format."); DEBUG_ERROR("Invalid relay private key format. Must be 64 hex characters or valid nsec format.");
print_usage(argv[0]); print_usage(argv[0]);
return 1; return 1;
} }
@@ -1509,13 +1565,28 @@ int main(int argc, char* argv[]) {
} else if (strcmp(argv[i], "--strict-port") == 0) { } else if (strcmp(argv[i], "--strict-port") == 0) {
// Strict port mode option // Strict port mode option
cli_options.strict_port = 1; cli_options.strict_port = 1;
} else if (strncmp(argv[i], "--debug-level=", 14) == 0) {
// Debug level option
char* endptr;
int debug_level = (int)strtol(argv[i] + 14, &endptr, 10);
if (endptr == argv[i] + 14 || *endptr != '\0' || debug_level < 0 || debug_level > 5) {
DEBUG_ERROR("Invalid debug level. Debug level must be between 0 and 5.");
print_usage(argv[0]);
return 1;
}
cli_options.debug_level = debug_level;
} else { } else {
log_error("Unknown argument. Use --help for usage information."); DEBUG_ERROR("Unknown argument. Use --help for usage information.");
print_usage(argv[0]); print_usage(argv[0]);
return 1; return 1;
} }
} }
// Initialize debug system
debug_init(cli_options.debug_level);
// Set up signal handlers // Set up signal handlers
signal(SIGINT, signal_handler); signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler); signal(SIGTERM, signal_handler);
@@ -1523,49 +1594,56 @@ int main(int argc, char* argv[]) {
printf(BLUE BOLD "=== C Nostr Relay Server ===" RESET "\n"); printf(BLUE BOLD "=== C Nostr Relay Server ===" RESET "\n");
DEBUG_TRACE("Starting main initialization sequence");
// Initialize nostr library FIRST (required for key generation and event creation) // Initialize nostr library FIRST (required for key generation and event creation)
if (nostr_init() != 0) { if (nostr_init() != 0) {
log_error("Failed to initialize nostr library"); DEBUG_ERROR("Failed to initialize nostr library");
return 1; return 1;
} }
DEBUG_LOG("Nostr library initialized");
// Check if this is first-time startup or existing relay // Check if this is first-time startup or existing relay
if (is_first_time_startup()) { if (is_first_time_startup()) {
DEBUG_LOG("First-time startup detected");
// Initialize event-based configuration system // Initialize event-based configuration system
if (init_configuration_system(NULL, NULL) != 0) { if (init_configuration_system(NULL, NULL) != 0) {
log_error("Failed to initialize event-based configuration system"); DEBUG_ERROR("Failed to initialize event-based configuration system");
nostr_cleanup(); nostr_cleanup();
return 1; return 1;
} }
// Run first-time startup sequence (generates keys, sets up database path, but doesn't store private key yet) // Run first-time startup sequence (generates keys, sets up database path, but doesn't store private key yet)
if (first_time_startup_sequence(&cli_options) != 0) { if (first_time_startup_sequence(&cli_options) != 0) {
log_error("Failed to complete first-time startup sequence"); DEBUG_ERROR("Failed to complete first-time startup sequence");
cleanup_configuration_system(); cleanup_configuration_system();
nostr_cleanup(); nostr_cleanup();
return 1; return 1;
} }
// Initialize database with the generated relay pubkey // Initialize database with the generated relay pubkey
DEBUG_TRACE("Initializing database for first-time startup");
if (init_database(g_database_path) != 0) { if (init_database(g_database_path) != 0) {
log_error("Failed to initialize database after first-time setup"); DEBUG_ERROR("Failed to initialize database after first-time setup");
cleanup_configuration_system(); cleanup_configuration_system();
nostr_cleanup(); nostr_cleanup();
return 1; return 1;
} }
DEBUG_LOG("Database initialized for first-time startup");
// Now that database is available, store the relay private key securely // Now that database is available, store the relay private key securely
const char* relay_privkey = get_temp_relay_private_key(); const char* relay_privkey = get_temp_relay_private_key();
if (relay_privkey) { if (relay_privkey) {
if (store_relay_private_key(relay_privkey) != 0) { if (store_relay_private_key(relay_privkey) != 0) {
log_error("Failed to store relay private key securely after database initialization"); DEBUG_ERROR("Failed to store relay private key securely after database initialization");
cleanup_configuration_system(); cleanup_configuration_system();
nostr_cleanup(); nostr_cleanup();
return 1; return 1;
} }
} else { } else {
log_error("Relay private key not available from first-time startup"); DEBUG_ERROR("Relay private key not available from first-time startup");
cleanup_configuration_system(); cleanup_configuration_system();
nostr_cleanup(); nostr_cleanup();
return 1; return 1;
@@ -1576,7 +1654,7 @@ int main(int argc, char* argv[]) {
// Populate default config values in table // Populate default config values in table
if (populate_default_config_values() != 0) { if (populate_default_config_values() != 0) {
log_error("Failed to populate default config values"); DEBUG_ERROR("Failed to populate default config values");
cleanup_configuration_system(); cleanup_configuration_system();
nostr_cleanup(); nostr_cleanup();
close_database(); close_database();
@@ -1588,7 +1666,7 @@ int main(int argc, char* argv[]) {
char port_str[16]; char port_str[16];
snprintf(port_str, sizeof(port_str), "%d", cli_options.port_override); snprintf(port_str, sizeof(port_str), "%d", cli_options.port_override);
if (update_config_in_table("relay_port", port_str) != 0) { if (update_config_in_table("relay_port", port_str) != 0) {
log_error("Failed to update relay port override in config table"); DEBUG_ERROR("Failed to update relay port override in config table");
cleanup_configuration_system(); cleanup_configuration_system();
nostr_cleanup(); nostr_cleanup();
close_database(); close_database();
@@ -1597,30 +1675,19 @@ int main(int argc, char* argv[]) {
printf(" Port: %d (overriding default)\n", cli_options.port_override); printf(" Port: %d (overriding default)\n", cli_options.port_override);
} }
// Add pubkeys to config table // Add pubkeys to config table (single authoritative call)
if (add_pubkeys_to_config_table() != 0) { if (add_pubkeys_to_config_table() != 0) {
log_error("Failed to add pubkeys to config table"); DEBUG_ERROR("Failed to add pubkeys to config table");
cleanup_configuration_system(); cleanup_configuration_system();
nostr_cleanup(); nostr_cleanup();
close_database(); close_database();
return 1; return 1;
} }
// Now store the pubkeys in config table since database is available
const char* admin_pubkey = get_admin_pubkey_cached();
const char* relay_pubkey_from_cache = get_relay_pubkey_cached();
if (admin_pubkey && strlen(admin_pubkey) == 64) {
set_config_value_in_table("admin_pubkey", admin_pubkey, "string", "Administrator public key", "authentication", 0);
}
if (relay_pubkey_from_cache && strlen(relay_pubkey_from_cache) == 64) {
set_config_value_in_table("relay_pubkey", relay_pubkey_from_cache, "string", "Relay public key", "relay", 0);
}
} else { } else {
// Find existing database file // Find existing database file
char** existing_files = find_existing_db_files(); char** existing_files = find_existing_db_files();
if (!existing_files || !existing_files[0]) { if (!existing_files || !existing_files[0]) {
log_error("No existing relay database found"); DEBUG_ERROR("No existing relay database found");
nostr_cleanup(); nostr_cleanup();
return 1; return 1;
} }
@@ -1628,7 +1695,7 @@ int main(int argc, char* argv[]) {
// Extract relay pubkey from filename // Extract relay pubkey from filename
char* relay_pubkey = extract_pubkey_from_filename(existing_files[0]); char* relay_pubkey = extract_pubkey_from_filename(existing_files[0]);
if (!relay_pubkey) { if (!relay_pubkey) {
log_error("Failed to extract relay pubkey from database filename"); DEBUG_ERROR("Failed to extract relay pubkey from database filename");
// Free the files array // Free the files array
for (int i = 0; existing_files[i]; i++) { for (int i = 0; existing_files[i]; i++) {
free(existing_files[i]); free(existing_files[i]);
@@ -1640,7 +1707,7 @@ int main(int argc, char* argv[]) {
// Initialize event-based configuration system // Initialize event-based configuration system
if (init_configuration_system(NULL, NULL) != 0) { if (init_configuration_system(NULL, NULL) != 0) {
log_error("Failed to initialize event-based configuration system"); DEBUG_ERROR("Failed to initialize event-based configuration system");
free(relay_pubkey); free(relay_pubkey);
for (int i = 0; existing_files[i]; i++) { for (int i = 0; existing_files[i]; i++) {
free(existing_files[i]); free(existing_files[i]);
@@ -1652,7 +1719,7 @@ int main(int argc, char* argv[]) {
// Setup existing relay (sets database path and loads config) // Setup existing relay (sets database path and loads config)
if (startup_existing_relay(relay_pubkey) != 0) { if (startup_existing_relay(relay_pubkey) != 0) {
log_error("Failed to setup existing relay"); DEBUG_ERROR("Failed to setup existing relay");
cleanup_configuration_system(); cleanup_configuration_system();
free(relay_pubkey); free(relay_pubkey);
for (int i = 0; existing_files[i]; i++) { for (int i = 0; existing_files[i]; i++) {
@@ -1663,9 +1730,26 @@ int main(int argc, char* argv[]) {
return 1; return 1;
} }
// Check config table row count before database initialization
{
sqlite3* temp_db = NULL;
if (sqlite3_open(g_database_path, &temp_db) == SQLITE_OK) {
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(temp_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
int row_count = sqlite3_column_int(stmt, 0);
printf(" Config table row count before database initialization: %d\n", row_count);
}
sqlite3_finalize(stmt);
}
sqlite3_close(temp_db);
}
}
// Initialize database with existing database path // Initialize database with existing database path
DEBUG_TRACE("Initializing existing database");
if (init_database(g_database_path) != 0) { if (init_database(g_database_path) != 0) {
log_error("Failed to initialize existing database"); DEBUG_ERROR("Failed to initialize existing database");
cleanup_configuration_system(); cleanup_configuration_system();
free(relay_pubkey); free(relay_pubkey);
for (int i = 0; existing_files[i]; i++) { for (int i = 0; existing_files[i]; i++) {
@@ -1675,30 +1759,69 @@ int main(int argc, char* argv[]) {
nostr_cleanup(); nostr_cleanup();
return 1; return 1;
} }
DEBUG_LOG("Existing database initialized");
// Check config table row count after database initialization
{
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
int row_count = sqlite3_column_int(stmt, 0);
printf(" Config table row count after database initialization: %d\n", row_count);
}
sqlite3_finalize(stmt);
}
}
// Ensure default configuration values are populated (for any missing keys)
// This must be done AFTER database initialization
// COMMENTED OUT: Don't modify existing database config on restart
// if (populate_default_config_values() != 0) {
// DEBUG_WARN("Failed to populate default config values for existing relay - continuing");
// }
// Load configuration from database // Load configuration from database
cJSON* config_event = load_config_event_from_database(relay_pubkey); cJSON* config_event = load_config_event_from_database(relay_pubkey);
if (config_event) { if (config_event) {
if (apply_configuration_from_event(config_event) != 0) { if (apply_configuration_from_event(config_event) != 0) {
log_warning("Failed to apply configuration from database"); DEBUG_WARN("Failed to apply configuration from database");
} else {
// Extract admin pubkey from the config event and store in config table for unified cache access
cJSON* pubkey_obj = cJSON_GetObjectItem(config_event, "pubkey");
const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
// Store both admin and relay pubkeys in config table for unified cache
if (admin_pubkey && strlen(admin_pubkey) == 64) {
set_config_value_in_table("admin_pubkey", admin_pubkey, "string", "Administrator public key", "authentication", 0);
}
if (relay_pubkey && strlen(relay_pubkey) == 64) {
set_config_value_in_table("relay_pubkey", relay_pubkey, "string", "Relay public key", "relay", 0);
}
} }
cJSON_Delete(config_event); cJSON_Delete(config_event);
} else { } else {
log_warning("No configuration event found in existing database"); // This is expected for relays using table-based configuration
// No longer a warning - just informational
}
// Ensure pubkeys are in config table for existing relay
// This handles migration from old event-based config to table-based config
const char* admin_pubkey_from_table = get_config_value_from_table("admin_pubkey");
const char* relay_pubkey_from_table = get_config_value_from_table("relay_pubkey");
int need_to_add_pubkeys = 0;
// Check if admin_pubkey is missing or invalid
if (!admin_pubkey_from_table || strlen(admin_pubkey_from_table) != 64) {
DEBUG_WARN("Admin pubkey missing or invalid in config table - will regenerate from cache");
need_to_add_pubkeys = 1;
}
if (admin_pubkey_from_table) free((char*)admin_pubkey_from_table);
// Check if relay_pubkey is missing or invalid
if (!relay_pubkey_from_table || strlen(relay_pubkey_from_table) != 64) {
DEBUG_WARN("Relay pubkey missing or invalid in config table - will regenerate from cache");
need_to_add_pubkeys = 1;
}
if (relay_pubkey_from_table) free((char*)relay_pubkey_from_table);
// If either pubkey is missing, call add_pubkeys_to_config_table to populate both
if (need_to_add_pubkeys) {
if (add_pubkeys_to_config_table() != 0) {
DEBUG_ERROR("Failed to add pubkeys to config table for existing relay");
cleanup_configuration_system();
nostr_cleanup();
close_database();
return 1;
}
} }
// Apply CLI overrides for existing relay (port override should work even for existing relays) // Apply CLI overrides for existing relay (port override should work even for existing relays)
@@ -1706,7 +1829,7 @@ int main(int argc, char* argv[]) {
char port_str[16]; char port_str[16];
snprintf(port_str, sizeof(port_str), "%d", cli_options.port_override); snprintf(port_str, sizeof(port_str), "%d", cli_options.port_override);
if (update_config_in_table("relay_port", port_str) != 0) { if (update_config_in_table("relay_port", port_str) != 0) {
log_error("Failed to update relay port override in config table for existing relay"); DEBUG_ERROR("Failed to update relay port override in config table for existing relay");
cleanup_configuration_system(); cleanup_configuration_system();
nostr_cleanup(); nostr_cleanup();
close_database(); close_database();
@@ -1725,7 +1848,7 @@ int main(int argc, char* argv[]) {
// Verify database is now available // Verify database is now available
if (!g_db) { if (!g_db) {
log_error("Database not available after initialization"); DEBUG_ERROR("Database not available after initialization");
cleanup_configuration_system(); cleanup_configuration_system();
nostr_cleanup(); nostr_cleanup();
return 1; return 1;
@@ -1736,7 +1859,7 @@ int main(int argc, char* argv[]) {
// Initialize unified request validator system // Initialize unified request validator system
if (ginxsom_request_validator_init(g_database_path, "c-relay") != 0) { if (ginxsom_request_validator_init(g_database_path, "c-relay") != 0) {
log_error("Failed to initialize unified request validator"); DEBUG_ERROR("Failed to initialize unified request validator");
cleanup_configuration_system(); cleanup_configuration_system();
nostr_cleanup(); nostr_cleanup();
close_database(); close_database();
@@ -1756,7 +1879,7 @@ int main(int argc, char* argv[]) {
// Initialize subscription manager mutexes // Initialize subscription manager mutexes
if (pthread_mutex_init(&g_subscription_manager.subscriptions_lock, NULL) != 0) { if (pthread_mutex_init(&g_subscription_manager.subscriptions_lock, NULL) != 0) {
log_error("Failed to initialize subscription manager subscriptions lock"); DEBUG_ERROR("Failed to initialize subscription manager subscriptions lock");
cleanup_configuration_system(); cleanup_configuration_system();
nostr_cleanup(); nostr_cleanup();
close_database(); close_database();
@@ -1764,7 +1887,7 @@ int main(int argc, char* argv[]) {
} }
if (pthread_mutex_init(&g_subscription_manager.ip_tracking_lock, NULL) != 0) { if (pthread_mutex_init(&g_subscription_manager.ip_tracking_lock, NULL) != 0) {
log_error("Failed to initialize subscription manager IP tracking lock"); DEBUG_ERROR("Failed to initialize subscription manager IP tracking lock");
pthread_mutex_destroy(&g_subscription_manager.subscriptions_lock); pthread_mutex_destroy(&g_subscription_manager.subscriptions_lock);
cleanup_configuration_system(); cleanup_configuration_system();
nostr_cleanup(); nostr_cleanup();
@@ -1791,7 +1914,7 @@ int main(int argc, char* argv[]) {
if (result == 0) { if (result == 0) {
} else { } else {
log_error("Server shutdown with errors"); DEBUG_ERROR("Server shutdown with errors");
} }
return result; return result;

View File

@@ -6,15 +6,13 @@
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
#include <cjson/cJSON.h> #include <cjson/cJSON.h>
#include "debug.h"
#include <sqlite3.h> #include <sqlite3.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <time.h> #include <time.h>
#include <stdio.h> #include <stdio.h>
// Forward declarations for logging functions
void log_warning(const char* message);
void log_info(const char* message);
// Forward declaration for database functions // Forward declaration for database functions
int store_event(cJSON* event); int store_event(cJSON* event);
@@ -139,7 +137,7 @@ int handle_deletion_request(cJSON* event, char* error_message, size_t error_size
// Store the deletion request itself (it should be kept according to NIP-09) // Store the deletion request itself (it should be kept according to NIP-09)
if (store_event(event) != 0) { if (store_event(event) != 0) {
log_warning("Failed to store deletion request event"); DEBUG_WARN("Failed to store deletion request event");
} }
error_message[0] = '\0'; // Success - empty error message error_message[0] = '\0'; // Success - empty error message
@@ -198,7 +196,7 @@ int delete_events_by_id(const char* requester_pubkey, cJSON* event_ids) {
sqlite3_finalize(check_stmt); sqlite3_finalize(check_stmt);
char warning_msg[128]; char warning_msg[128];
snprintf(warning_msg, sizeof(warning_msg), "Unauthorized deletion attempt for event: %.16s...", id); snprintf(warning_msg, sizeof(warning_msg), "Unauthorized deletion attempt for event: %.16s...", id);
log_warning(warning_msg); DEBUG_WARN(warning_msg);
} }
} else { } else {
sqlite3_finalize(check_stmt); sqlite3_finalize(check_stmt);
@@ -244,7 +242,7 @@ int delete_events_by_address(const char* requester_pubkey, cJSON* addresses, lon
free(addr_copy); free(addr_copy);
char warning_msg[128]; char warning_msg[128];
snprintf(warning_msg, sizeof(warning_msg), "Unauthorized deletion attempt for address: %.32s...", addr); snprintf(warning_msg, sizeof(warning_msg), "Unauthorized deletion attempt for address: %.32s...", addr);
log_warning(warning_msg); DEBUG_WARN(warning_msg);
continue; continue;
} }

View File

@@ -1,6 +1,7 @@
// NIP-11 Relay Information Document module // NIP-11 Relay Information Document module
#define _GNU_SOURCE #define _GNU_SOURCE
#include <stdio.h> #include <stdio.h>
#include "debug.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <pthread.h> #include <pthread.h>
@@ -8,11 +9,6 @@
#include "../nostr_core_lib/cjson/cJSON.h" #include "../nostr_core_lib/cjson/cJSON.h"
#include "config.h" #include "config.h"
// Forward declarations for logging functions
void log_info(const char* message);
void log_success(const char* message);
void log_error(const char* message);
void log_warning(const char* message);
// Forward declarations for configuration functions // Forward declarations for configuration functions
const char* get_config_value(const char* key); const char* get_config_value(const char* key);
@@ -264,7 +260,7 @@ void cleanup_relay_info() {
cJSON* generate_relay_info_json() { cJSON* generate_relay_info_json() {
cJSON* info = cJSON_CreateObject(); cJSON* info = cJSON_CreateObject();
if (!info) { if (!info) {
log_error("Failed to create relay info JSON object"); DEBUG_ERROR("Failed to create relay info JSON object");
return NULL; return NULL;
} }
@@ -274,7 +270,7 @@ cJSON* generate_relay_info_json() {
if (strlen(g_unified_cache.relay_info.name) == 0 && if (strlen(g_unified_cache.relay_info.name) == 0 &&
strlen(g_unified_cache.relay_info.description) == 0 && strlen(g_unified_cache.relay_info.description) == 0 &&
strlen(g_unified_cache.relay_info.software) == 0) { strlen(g_unified_cache.relay_info.software) == 0) {
log_warning("NIP-11 relay_info appears empty, rebuilding directly from config table"); DEBUG_WARN("NIP-11 relay_info appears empty, rebuilding directly from config table");
// Rebuild relay_info directly from config table to avoid circular cache dependency // Rebuild relay_info directly from config table to avoid circular cache dependency
// Get values directly from table (similar to init_relay_info but without cache calls) // Get values directly from table (similar to init_relay_info but without cache calls)
@@ -534,7 +530,7 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
} }
if (!accepts_nostr_json) { if (!accepts_nostr_json) {
log_warning("HTTP request without proper Accept header for NIP-11"); DEBUG_WARN("HTTP request without proper Accept header for NIP-11");
// Return 406 Not Acceptable // Return 406 Not Acceptable
unsigned char buf[LWS_PRE + 256]; unsigned char buf[LWS_PRE + 256];
unsigned char *p = &buf[LWS_PRE]; unsigned char *p = &buf[LWS_PRE];
@@ -560,7 +556,7 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
// Generate relay information JSON // Generate relay information JSON
cJSON* info_json = generate_relay_info_json(); cJSON* info_json = generate_relay_info_json();
if (!info_json) { if (!info_json) {
log_error("Failed to generate relay info JSON"); DEBUG_ERROR("Failed to generate relay info JSON");
unsigned char buf[LWS_PRE + 256]; unsigned char buf[LWS_PRE + 256];
unsigned char *p = &buf[LWS_PRE]; unsigned char *p = &buf[LWS_PRE];
unsigned char *start = p; unsigned char *start = p;
@@ -586,7 +582,7 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
cJSON_Delete(info_json); cJSON_Delete(info_json);
if (!json_string) { if (!json_string) {
log_error("Failed to serialize relay info JSON"); DEBUG_ERROR("Failed to serialize relay info JSON");
unsigned char buf[LWS_PRE + 256]; unsigned char buf[LWS_PRE + 256];
unsigned char *p = &buf[LWS_PRE]; unsigned char *p = &buf[LWS_PRE];
unsigned char *start = p; unsigned char *start = p;
@@ -613,7 +609,7 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
// Allocate session data to manage buffer lifetime across callbacks // Allocate session data to manage buffer lifetime across callbacks
struct nip11_session_data* session_data = malloc(sizeof(struct nip11_session_data)); struct nip11_session_data* session_data = malloc(sizeof(struct nip11_session_data));
if (!session_data) { if (!session_data) {
log_error("Failed to allocate NIP-11 session data"); DEBUG_ERROR("Failed to allocate NIP-11 session data");
free(json_string); free(json_string);
return -1; return -1;
} }

View File

@@ -1,5 +1,6 @@
// NIP-13 Proof of Work validation module // NIP-13 Proof of Work validation module
#include <stdio.h> #include <stdio.h>
#include "debug.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <pthread.h> #include <pthread.h>
@@ -8,11 +9,6 @@
#include "../nostr_core_lib/nostr_core/nip013.h" #include "../nostr_core_lib/nostr_core/nip013.h"
#include "config.h" #include "config.h"
// Forward declarations for logging functions
void log_info(const char* message);
void log_success(const char* message);
void log_error(const char* message);
void log_warning(const char* message);
// NIP-13 PoW configuration structure // NIP-13 PoW configuration structure
struct pow_config { struct pow_config {
@@ -121,39 +117,39 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
snprintf(error_message, error_size, snprintf(error_message, error_size,
"pow: insufficient difficulty: %d < %d", "pow: insufficient difficulty: %d < %d",
pow_result.actual_difficulty, min_pow_difficulty); pow_result.actual_difficulty, min_pow_difficulty);
log_warning("Event rejected: insufficient PoW difficulty"); DEBUG_WARN("Event rejected: insufficient PoW difficulty");
break; break;
case NOSTR_ERROR_NIP13_NO_NONCE_TAG: case NOSTR_ERROR_NIP13_NO_NONCE_TAG:
// This should not happen with min_difficulty=0 after our check above // This should not happen with min_difficulty=0 after our check above
if (min_pow_difficulty > 0) { if (min_pow_difficulty > 0) {
snprintf(error_message, error_size, "pow: missing required nonce tag"); snprintf(error_message, error_size, "pow: missing required nonce tag");
log_warning("Event rejected: missing nonce tag"); DEBUG_WARN("Event rejected: missing nonce tag");
} else { } else {
return 0; // Allow when min_difficulty=0 return 0; // Allow when min_difficulty=0
} }
break; break;
case NOSTR_ERROR_NIP13_INVALID_NONCE_TAG: case NOSTR_ERROR_NIP13_INVALID_NONCE_TAG:
snprintf(error_message, error_size, "pow: invalid nonce tag format"); snprintf(error_message, error_size, "pow: invalid nonce tag format");
log_warning("Event rejected: invalid nonce tag format"); DEBUG_WARN("Event rejected: invalid nonce tag format");
break; break;
case NOSTR_ERROR_NIP13_TARGET_MISMATCH: case NOSTR_ERROR_NIP13_TARGET_MISMATCH:
snprintf(error_message, error_size, snprintf(error_message, error_size,
"pow: committed target (%d) lower than minimum (%d)", "pow: committed target (%d) lower than minimum (%d)",
pow_result.committed_target, min_pow_difficulty); pow_result.committed_target, min_pow_difficulty);
log_warning("Event rejected: committed target too low (anti-spam protection)"); DEBUG_WARN("Event rejected: committed target too low (anti-spam protection)");
break; break;
case NOSTR_ERROR_NIP13_CALCULATION: case NOSTR_ERROR_NIP13_CALCULATION:
snprintf(error_message, error_size, "pow: difficulty calculation failed"); snprintf(error_message, error_size, "pow: difficulty calculation failed");
log_error("PoW difficulty calculation error"); DEBUG_ERROR("PoW difficulty calculation error");
break; break;
case NOSTR_ERROR_EVENT_INVALID_ID: case NOSTR_ERROR_EVENT_INVALID_ID:
snprintf(error_message, error_size, "pow: invalid event ID format"); snprintf(error_message, error_size, "pow: invalid event ID format");
log_warning("Event rejected: invalid event ID for PoW calculation"); DEBUG_WARN("Event rejected: invalid event ID for PoW calculation");
break; break;
default: default:
snprintf(error_message, error_size, "pow: validation failed - %s", snprintf(error_message, error_size, "pow: validation failed - %s",
strlen(pow_result.error_detail) > 0 ? pow_result.error_detail : "unknown error"); strlen(pow_result.error_detail) > 0 ? pow_result.error_detail : "unknown error");
log_warning("Event rejected: PoW validation failed"); DEBUG_WARN("Event rejected: PoW validation failed");
} }
return validation_result; return validation_result;
} }

View File

@@ -1,5 +1,6 @@
#define _GNU_SOURCE #define _GNU_SOURCE
#include <stdio.h> #include <stdio.h>
#include "debug.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
@@ -28,9 +29,6 @@ struct expiration_config g_expiration_config = {
.grace_period = 1 // 1 second grace period for testing (was 300) .grace_period = 1 // 1 second grace period for testing (was 300)
}; };
// Forward declarations for logging functions
void log_info(const char* message);
void log_warning(const char* message);
// Initialize expiration configuration using configuration system // Initialize expiration configuration using configuration system
void init_expiration_config() { void init_expiration_config() {
@@ -51,7 +49,7 @@ void init_expiration_config() {
// Validate grace period bounds // Validate grace period bounds
if (g_expiration_config.grace_period < 0 || g_expiration_config.grace_period > 86400) { if (g_expiration_config.grace_period < 0 || g_expiration_config.grace_period > 86400) {
log_warning("Invalid grace period, using default of 300 seconds"); DEBUG_WARN("Invalid grace period, using default of 300 seconds");
g_expiration_config.grace_period = 300; g_expiration_config.grace_period = 300;
} }
@@ -94,7 +92,7 @@ long extract_expiration_timestamp(cJSON* tags) {
char debug_msg[256]; char debug_msg[256];
snprintf(debug_msg, sizeof(debug_msg), snprintf(debug_msg, sizeof(debug_msg),
"Ignoring malformed expiration tag value: '%.32s'", value); "Ignoring malformed expiration tag value: '%.32s'", value);
log_warning(debug_msg); DEBUG_WARN(debug_msg);
continue; // Ignore malformed expiration tag continue; // Ignore malformed expiration tag
} }
@@ -148,7 +146,7 @@ int validate_event_expiration(cJSON* event, char* error_message, size_t error_si
snprintf(error_message, error_size, snprintf(error_message, error_size,
"invalid: event expired (expiration=%ld, current=%ld, grace=%ld)", "invalid: event expired (expiration=%ld, current=%ld, grace=%ld)",
expiration_ts, (long)current_time, g_expiration_config.grace_period); expiration_ts, (long)current_time, g_expiration_config.grace_period);
log_warning("Event rejected: expired timestamp"); DEBUG_WARN("Event rejected: expired timestamp");
return -1; return -1;
} else { } else {
// In non-strict mode, allow expired events // In non-strict mode, allow expired events

View File

@@ -6,17 +6,13 @@
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
#include <pthread.h> #include <pthread.h>
#include "debug.h"
#include <cjson/cJSON.h> #include <cjson/cJSON.h>
#include <libwebsockets.h> #include <libwebsockets.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <time.h> #include <time.h>
// Forward declarations for logging functions
void log_error(const char* message);
void log_info(const char* message);
void log_warning(const char* message);
void log_success(const char* message);
// Forward declaration for notice message function // Forward declaration for notice message function
void send_notice_message(struct lws* wsi, const char* message); void send_notice_message(struct lws* wsi, const char* message);
@@ -52,7 +48,7 @@ void send_nip42_auth_challenge(struct lws* wsi, struct per_session_data* pss) {
// Generate challenge using existing request_validator function // Generate challenge using existing request_validator function
char challenge[65]; char challenge[65];
if (nostr_nip42_generate_challenge(challenge, sizeof(challenge)) != 0) { if (nostr_nip42_generate_challenge(challenge, sizeof(challenge)) != 0) {
log_error("Failed to generate NIP-42 challenge"); DEBUG_ERROR("Failed to generate NIP-42 challenge");
send_notice_message(wsi, "Authentication temporarily unavailable"); send_notice_message(wsi, "Authentication temporarily unavailable");
return; return;
} }
@@ -108,7 +104,7 @@ void handle_nip42_auth_signed_event(struct lws* wsi, struct per_session_data* ps
if (current_time > challenge_expires) { if (current_time > challenge_expires) {
free(event_json); free(event_json);
send_notice_message(wsi, "Authentication challenge expired, please retry"); send_notice_message(wsi, "Authentication challenge expired, please retry");
log_warning("NIP-42 authentication failed: challenge expired"); DEBUG_WARN("NIP-42 authentication failed: challenge expired");
return; return;
} }
@@ -154,7 +150,7 @@ void handle_nip42_auth_signed_event(struct lws* wsi, struct per_session_data* ps
char error_msg[256]; char error_msg[256];
snprintf(error_msg, sizeof(error_msg), snprintf(error_msg, sizeof(error_msg),
"NIP-42 authentication failed (error code: %d)", result); "NIP-42 authentication failed (error code: %d)", result);
log_warning(error_msg); DEBUG_WARN(error_msg);
send_notice_message(wsi, "NIP-42 authentication failed - invalid signature or challenge"); send_notice_message(wsi, "NIP-42 authentication failed - invalid signature or challenge");
} }
@@ -166,6 +162,6 @@ void handle_nip42_auth_challenge_response(struct lws* wsi, struct per_session_da
// NIP-42 doesn't typically use challenge responses from client to server // NIP-42 doesn't typically use challenge responses from client to server
// This is reserved for potential future use or protocol extensions // This is reserved for potential future use or protocol extensions
log_warning("Received unexpected challenge response from client (not part of standard NIP-42 flow)"); DEBUG_WARN("Received unexpected challenge response from client (not part of standard NIP-42 flow)");
send_notice_message(wsi, "Challenge responses are not supported - please send signed authentication event"); send_notice_message(wsi, "Challenge responses are not supported - please send signed authentication event");
} }

View File

@@ -53,6 +53,7 @@ extern struct expiration_config {
// Configuration functions from C-relay // Configuration functions from C-relay
extern int get_config_bool(const char* key, int default_value); extern int get_config_bool(const char* key, int default_value);
extern int get_config_int(const char* key, int default_value); extern int get_config_int(const char* key, int default_value);
extern const char* get_admin_pubkey_cached(void);
// NIP-42 constants (from nostr_core_lib) // NIP-42 constants (from nostr_core_lib)
#define NOSTR_NIP42_AUTH_EVENT_KIND 22242 #define NOSTR_NIP42_AUTH_EVENT_KIND 22242
@@ -294,10 +295,26 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
} }
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
// PHASE 3: EVENT KIND SPECIFIC VALIDATION // PHASE 3: ADMIN EVENT BYPASS CHECK
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
// 8. Handle NIP-42 authentication challenge events (kind 22242) // 8. Check if this is a kind 23456 admin event from authorized admin
// This must happen AFTER signature validation but BEFORE auth rules
if (event_kind == 23456) {
const char* admin_pubkey = get_admin_pubkey_cached();
if (admin_pubkey && strcmp(event_pubkey, admin_pubkey) == 0) {
// Valid admin event - bypass remaining validation
cJSON_Delete(event);
return NOSTR_SUCCESS;
}
// Not from admin - continue with normal validation
}
/////////////////////////////////////////////////////////////////////
// PHASE 4: EVENT KIND SPECIFIC VALIDATION
/////////////////////////////////////////////////////////////////////
// 9. Handle NIP-42 authentication challenge events (kind 22242)
if (event_kind == 22242) { if (event_kind == 22242) {
// Check NIP-42 mode using unified cache // Check NIP-42 mode using unified cache
const char* nip42_enabled = get_config_value("nip42_auth_enabled"); const char* nip42_enabled = get_config_value("nip42_auth_enabled");
@@ -315,13 +332,13 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
} }
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
// PHASE 4: AUTHENTICATION RULES (Database Queries) // PHASE 5: AUTHENTICATION RULES (Database Queries)
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
// 9. Check if authentication rules are enabled // 10. Check if authentication rules are enabled
if (!auth_required) { if (!auth_required) {
} else { } else {
// 10. Check database authentication rules (only if auth enabled) // 11. Check database authentication rules (only if auth enabled)
// Create operation string with event kind for more specific rule matching // Create operation string with event kind for more specific rule matching
char operation_str[64]; char operation_str[64];
@@ -340,10 +357,10 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
} }
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
// PHASE 5: ADDITIONAL VALIDATIONS (C-relay specific) // PHASE 6: ADDITIONAL VALIDATIONS (C-relay specific)
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
// 11. NIP-13 Proof of Work validation // 12. NIP-13 Proof of Work validation
pthread_mutex_lock(&g_unified_cache.cache_lock); pthread_mutex_lock(&g_unified_cache.cache_lock);
int pow_enabled = g_unified_cache.pow_config.enabled; int pow_enabled = g_unified_cache.pow_config.enabled;
int pow_min_difficulty = g_unified_cache.pow_config.min_pow_difficulty; int pow_min_difficulty = g_unified_cache.pow_config.min_pow_difficulty;
@@ -361,7 +378,7 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
} }
} }
// 12. NIP-40 Expiration validation // 13. NIP-40 Expiration validation
// Always check expiration tags if present (following NIP-40 specification) // Always check expiration tags if present (following NIP-40 specification)
cJSON *expiration_tag = NULL; cJSON *expiration_tag = NULL;

View File

@@ -1,5 +1,6 @@
#define _GNU_SOURCE #define _GNU_SOURCE
#include <cjson/cJSON.h> #include <cjson/cJSON.h>
#include "debug.h"
#include <sqlite3.h> #include <sqlite3.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
@@ -10,9 +11,6 @@
#include "subscriptions.h" #include "subscriptions.h"
// Forward declarations for logging functions // Forward declarations for logging functions
void log_info(const char* message);
void log_error(const char* message);
void log_warning(const char* message);
// Forward declarations for configuration functions // Forward declarations for configuration functions
const char* get_config_value(const char* key); const char* get_config_value(const char* key);
@@ -52,7 +50,7 @@ subscription_filter_t* create_subscription_filter(cJSON* filter_json) {
// Validate filter values before creating the filter // Validate filter values before creating the filter
char error_message[512] = {0}; char error_message[512] = {0};
if (!validate_filter_values(filter_json, error_message, sizeof(error_message))) { if (!validate_filter_values(filter_json, error_message, sizeof(error_message))) {
log_warning(error_message); DEBUG_WARN(error_message);
return NULL; return NULL;
} }
@@ -135,11 +133,11 @@ static int validate_subscription_id(const char* sub_id) {
return 0; // Empty or too long return 0; // Empty or too long
} }
// Check for valid characters (alphanumeric, underscore, hyphen) // Check for valid characters (alphanumeric, underscore, hyphen, colon)
for (size_t i = 0; i < len; i++) { for (size_t i = 0; i < len; i++) {
char c = sub_id[i]; char c = sub_id[i];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '_' || c == '-')) { (c >= '0' && c <= '9') || c == '_' || c == '-' || c == ':')) {
return 0; // Invalid character return 0; // Invalid character
} }
} }
@@ -150,19 +148,19 @@ static int validate_subscription_id(const char* sub_id) {
// Create a new subscription // Create a new subscription
subscription_t* create_subscription(const char* sub_id, struct lws* wsi, cJSON* filters_array, const char* client_ip) { subscription_t* create_subscription(const char* sub_id, struct lws* wsi, cJSON* filters_array, const char* client_ip) {
if (!sub_id || !wsi || !filters_array) { if (!sub_id || !wsi || !filters_array) {
log_error("create_subscription: NULL parameter(s)"); DEBUG_ERROR("create_subscription: NULL parameter(s)");
return NULL; return NULL;
} }
// Validate subscription ID // Validate subscription ID
if (!validate_subscription_id(sub_id)) { if (!validate_subscription_id(sub_id)) {
log_error("create_subscription: invalid subscription ID format or length"); DEBUG_ERROR("create_subscription: invalid subscription ID format or length");
return NULL; return NULL;
} }
subscription_t* sub = calloc(1, sizeof(subscription_t)); subscription_t* sub = calloc(1, sizeof(subscription_t));
if (!sub) { if (!sub) {
log_error("create_subscription: failed to allocate subscription"); DEBUG_ERROR("create_subscription: failed to allocate subscription");
return NULL; return NULL;
} }
@@ -199,7 +197,7 @@ subscription_t* create_subscription(const char* sub_id, struct lws* wsi, cJSON*
cJSON* filter_json = NULL; cJSON* filter_json = NULL;
cJSON_ArrayForEach(filter_json, filters_array) { cJSON_ArrayForEach(filter_json, filters_array) {
if (filter_count >= MAX_FILTERS_PER_SUBSCRIPTION) { if (filter_count >= MAX_FILTERS_PER_SUBSCRIPTION) {
log_warning("Maximum filters per subscription exceeded, ignoring excess filters"); DEBUG_WARN("Maximum filters per subscription exceeded, ignoring excess filters");
break; break;
} }
@@ -218,7 +216,7 @@ subscription_t* create_subscription(const char* sub_id, struct lws* wsi, cJSON*
} }
if (filter_count == 0) { if (filter_count == 0) {
log_error("No valid filters found for subscription"); DEBUG_ERROR("No valid filters found for subscription");
free(sub); free(sub);
return NULL; return NULL;
} }
@@ -246,7 +244,7 @@ int add_subscription_to_manager(subscription_t* sub) {
// Check global limits // Check global limits
if (g_subscription_manager.total_subscriptions >= g_subscription_manager.max_total_subscriptions) { if (g_subscription_manager.total_subscriptions >= g_subscription_manager.max_total_subscriptions) {
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock); pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
log_error("Maximum total subscriptions reached"); DEBUG_ERROR("Maximum total subscriptions reached");
return -1; return -1;
} }
@@ -267,13 +265,13 @@ int add_subscription_to_manager(subscription_t* sub) {
// Remove subscription from global manager (thread-safe) // Remove subscription from global manager (thread-safe)
int remove_subscription_from_manager(const char* sub_id, struct lws* wsi) { int remove_subscription_from_manager(const char* sub_id, struct lws* wsi) {
if (!sub_id) { if (!sub_id) {
log_error("remove_subscription_from_manager: NULL subscription ID"); DEBUG_ERROR("remove_subscription_from_manager: NULL subscription ID");
return -1; return -1;
} }
// Validate subscription ID format // Validate subscription ID format
if (!validate_subscription_id(sub_id)) { if (!validate_subscription_id(sub_id)) {
log_error("remove_subscription_from_manager: invalid subscription ID format"); DEBUG_ERROR("remove_subscription_from_manager: invalid subscription ID format");
return -1; return -1;
} }
@@ -319,14 +317,17 @@ int remove_subscription_from_manager(const char* sub_id, struct lws* wsi) {
char debug_msg[256]; char debug_msg[256];
snprintf(debug_msg, sizeof(debug_msg), "Subscription '%s' not found for removal", sub_id); snprintf(debug_msg, sizeof(debug_msg), "Subscription '%s' not found for removal", sub_id);
log_warning(debug_msg); DEBUG_WARN(debug_msg);
return -1; return -1;
} }
// Check if an event matches a subscription filter // Check if an event matches a subscription filter
int event_matches_filter(cJSON* event, subscription_filter_t* filter) { int event_matches_filter(cJSON* event, subscription_filter_t* filter) {
DEBUG_TRACE("Checking event against subscription filter");
if (!event || !filter) { if (!event || !filter) {
DEBUG_TRACE("Exiting event_matches_filter - null parameters");
return 0; return 0;
} }
@@ -502,6 +503,7 @@ int event_matches_filter(cJSON* event, subscription_filter_t* filter) {
} }
} }
DEBUG_TRACE("Exiting event_matches_filter - match found");
return 1; // All filters passed return 1; // All filters passed
} }
@@ -524,7 +526,10 @@ int event_matches_subscription(cJSON* event, subscription_t* subscription) {
// Broadcast event to all matching subscriptions (thread-safe) // Broadcast event to all matching subscriptions (thread-safe)
int broadcast_event_to_subscriptions(cJSON* event) { int broadcast_event_to_subscriptions(cJSON* event) {
DEBUG_TRACE("Broadcasting event to subscriptions");
if (!event) { if (!event) {
DEBUG_TRACE("Exiting broadcast_event_to_subscriptions - null event");
return 0; return 0;
} }
@@ -584,7 +589,7 @@ int broadcast_event_to_subscriptions(cJSON* event) {
matching_subs = temp; matching_subs = temp;
matching_count++; matching_count++;
} else { } else {
log_error("broadcast_event_to_subscriptions: failed to allocate temp subscription"); DEBUG_ERROR("broadcast_event_to_subscriptions: failed to allocate temp subscription");
} }
} }
sub = sub->next; sub = sub->next;
@@ -655,6 +660,8 @@ int broadcast_event_to_subscriptions(cJSON* event) {
g_subscription_manager.total_events_broadcast += broadcasts; g_subscription_manager.total_events_broadcast += broadcasts;
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock); pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
DEBUG_LOG("Event broadcast complete: %d subscriptions matched", broadcasts);
DEBUG_TRACE("Exiting broadcast_event_to_subscriptions");
return broadcasts; return broadcasts;
} }

View File

@@ -3,6 +3,7 @@
// Includes // Includes
#include <stdio.h> #include <stdio.h>
#include "debug.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
@@ -31,10 +32,6 @@
#include "dm_admin.h" // DM admin functions including NIP-17 #include "dm_admin.h" // DM admin functions including NIP-17
// Forward declarations for logging functions // Forward declarations for logging functions
void log_info(const char* message);
void log_success(const char* message);
void log_error(const char* message);
void log_warning(const char* message);
// Forward declarations for configuration functions // Forward declarations for configuration functions
const char* get_config_value(const char* key); const char* get_config_value(const char* key);
@@ -201,7 +198,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Allocate buffer for JSON body transmission (no LWS_PRE needed for body) // Allocate buffer for JSON body transmission (no LWS_PRE needed for body)
unsigned char *json_buf = malloc(session_data->json_length); unsigned char *json_buf = malloc(session_data->json_length);
if (!json_buf) { if (!json_buf) {
log_error("Failed to allocate buffer for NIP-11 body transmission"); DEBUG_ERROR("Failed to allocate buffer for NIP-11 body transmission");
// Clean up session data // Clean up session data
free(session_data->json_buffer); free(session_data->json_buffer);
free(session_data); free(session_data);
@@ -219,7 +216,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
free(json_buf); free(json_buf);
if (write_result < 0) { if (write_result < 0) {
log_error("Failed to write NIP-11 JSON body"); DEBUG_ERROR("Failed to write NIP-11 JSON body");
// Clean up session data // Clean up session data
free(session_data->json_buffer); free(session_data->json_buffer);
free(session_data); free(session_data);
@@ -244,6 +241,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
break; break;
case LWS_CALLBACK_ESTABLISHED: case LWS_CALLBACK_ESTABLISHED:
DEBUG_TRACE("WebSocket connection established");
memset(pss, 0, sizeof(*pss)); memset(pss, 0, sizeof(*pss));
pthread_mutex_init(&pss->session_lock, NULL); pthread_mutex_init(&pss->session_lock, NULL);
@@ -258,6 +256,8 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
memcpy(pss->client_ip, client_ip, copy_len); memcpy(pss->client_ip, client_ip, copy_len);
pss->client_ip[copy_len] = '\0'; pss->client_ip[copy_len] = '\0';
DEBUG_LOG("WebSocket connection established from %s", pss->client_ip);
// Initialize NIP-42 authentication state // Initialize NIP-42 authentication state
pss->authenticated = 0; pss->authenticated = 0;
pss->nip42_auth_required_events = get_config_bool("nip42_auth_required_events", 0); pss->nip42_auth_required_events = get_config_bool("nip42_auth_required_events", 0);
@@ -267,6 +267,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
memset(pss->active_challenge, 0, sizeof(pss->active_challenge)); memset(pss->active_challenge, 0, sizeof(pss->active_challenge));
pss->challenge_created = 0; pss->challenge_created = 0;
pss->challenge_expires = 0; pss->challenge_expires = 0;
DEBUG_TRACE("WebSocket connection initialization complete");
break; break;
case LWS_CALLBACK_RECEIVE: case LWS_CALLBACK_RECEIVE:
@@ -298,11 +299,9 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
cJSON* kind_obj = cJSON_GetObjectItem(event_obj, "kind"); cJSON* kind_obj = cJSON_GetObjectItem(event_obj, "kind");
int event_kind = kind_obj && cJSON_IsNumber(kind_obj) ? (int)cJSON_GetNumberValue(kind_obj) : -1; int event_kind = kind_obj && cJSON_IsNumber(kind_obj) ? (int)cJSON_GetNumberValue(kind_obj) : -1;
// Extract pubkey and event ID for debugging // Extract pubkey for debugging
cJSON* pubkey_obj = cJSON_GetObjectItem(event_obj, "pubkey"); cJSON* pubkey_obj = cJSON_GetObjectItem(event_obj, "pubkey");
cJSON* id_obj = cJSON_GetObjectItem(event_obj, "id");
const char* event_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : "unknown"; const char* event_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : "unknown";
const char* event_id = id_obj ? cJSON_GetStringValue(id_obj) : "unknown";
// Check if NIP-42 authentication is required for this event kind or globally // Check if NIP-42 authentication is required for this event kind or globally
int auth_required = is_nip42_auth_globally_required() || is_nip42_auth_required_for_kind(event_kind); int auth_required = is_nip42_auth_globally_required() || is_nip42_auth_required_for_kind(event_kind);
@@ -332,6 +331,16 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
} }
} }
// Special case: allow kind 23456 admin events from authorized admin to bypass auth
if (event_kind == 23456 && event_pubkey) {
const char* admin_pubkey = get_admin_pubkey_cached();
if (admin_pubkey && strcmp(event_pubkey, admin_pubkey) == 0) {
bypass_auth = 1;
} else {
DEBUG_INFO("DEBUG: Kind 23456 event but pubkey mismatch or no admin pubkey");
}
}
if (pss && auth_required && !pss->authenticated && !bypass_auth) { if (pss && auth_required && !pss->authenticated && !bypass_auth) {
if (!pss->auth_challenge_sent) { if (!pss->auth_challenge_sent) {
send_nip42_auth_challenge(wsi, pss); send_nip42_auth_challenge(wsi, pss);
@@ -345,57 +354,13 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
"NIP-42 authentication required for event kind %d", event_kind); "NIP-42 authentication required for event kind %d", event_kind);
} }
send_notice_message(wsi, auth_msg); send_notice_message(wsi, auth_msg);
log_warning("Event rejected: NIP-42 authentication required for kind"); DEBUG_WARN("Event rejected: NIP-42 authentication required for kind");
} }
cJSON_Delete(json); cJSON_Delete(json);
free(message); free(message);
return 0; return 0;
} }
// Check blacklist/whitelist rules regardless of NIP-42 auth settings
// Blacklist should always be enforced
if (event_pubkey) {
// Forward declaration for auth rules checking function
extern int check_database_auth_rules(const char *pubkey, const char *operation, const char *resource_hash);
int auth_rules_result = check_database_auth_rules(event_pubkey, "event", NULL);
if (auth_rules_result != 0) { // 0 = NOSTR_SUCCESS, non-zero = blocked
char auth_rules_msg[256];
if (auth_rules_result == -101) { // NOSTR_ERROR_AUTH_REQUIRED
snprintf(auth_rules_msg, sizeof(auth_rules_msg),
"blocked: pubkey not authorized (blacklist/whitelist violation)");
} else {
snprintf(auth_rules_msg, sizeof(auth_rules_msg),
"blocked: authorization check failed (error %d)", auth_rules_result);
}
send_notice_message(wsi, auth_rules_msg);
log_warning("Event rejected: blacklist/whitelist violation");
// Send OK response with false status
cJSON* response = cJSON_CreateArray();
cJSON_AddItemToArray(response, cJSON_CreateString("OK"));
cJSON_AddItemToArray(response, cJSON_CreateString(event_id));
cJSON_AddItemToArray(response, cJSON_CreateBool(0)); // false = rejected
cJSON_AddItemToArray(response, cJSON_CreateString(auth_rules_msg));
char *response_str = cJSON_Print(response);
if (response_str) {
size_t response_len = strlen(response_str);
unsigned char *buf = malloc(LWS_PRE + response_len);
if (buf) {
memcpy(buf + LWS_PRE, response_str, response_len);
lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT);
free(buf);
}
free(response_str);
}
cJSON_Delete(response);
cJSON_Delete(json);
free(message);
return 0;
}
}
} }
// Handle EVENT message // Handle EVENT message
@@ -404,7 +369,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Extract event JSON string for unified validator // Extract event JSON string for unified validator
char *event_json_str = cJSON_Print(event); char *event_json_str = cJSON_Print(event);
if (!event_json_str) { if (!event_json_str) {
log_error("Failed to serialize event JSON for validation"); DEBUG_ERROR("Failed to serialize event JSON for validation");
cJSON* error_response = cJSON_CreateArray(); cJSON* error_response = cJSON_CreateArray();
cJSON_AddItemToArray(error_response, cJSON_CreateString("OK")); cJSON_AddItemToArray(error_response, cJSON_CreateString("OK"));
cJSON_AddItemToArray(error_response, cJSON_CreateString("unknown")); cJSON_AddItemToArray(error_response, cJSON_CreateString("unknown"));
@@ -430,6 +395,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
size_t event_json_len = strlen(event_json_str); size_t event_json_len = strlen(event_json_str);
int validation_result = nostr_validate_unified_request(event_json_str, event_json_len); int validation_result = nostr_validate_unified_request(event_json_str, event_json_len);
// Map validation result to old result format (0 = success, -1 = failure) // Map validation result to old result format (0 = success, -1 = failure)
int result = (validation_result == NOSTR_SUCCESS) ? 0 : -1; int result = (validation_result == NOSTR_SUCCESS) ? 0 : -1;
@@ -495,7 +461,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
result = -1; result = -1;
strncpy(error_message, "blocked: protected events not supported", sizeof(error_message) - 1); strncpy(error_message, "blocked: protected events not supported", sizeof(error_message) - 1);
error_message[sizeof(error_message) - 1] = '\0'; error_message[sizeof(error_message) - 1] = '\0';
log_warning("Protected event rejected: protected events not enabled"); DEBUG_WARN("Protected event rejected: protected events not enabled");
} else { } else {
// Protected events enabled - check authentication // Protected events enabled - check authentication
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey"); cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
@@ -507,7 +473,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
result = -1; result = -1;
strncpy(error_message, "auth-required: protected event requires authentication", sizeof(error_message) - 1); strncpy(error_message, "auth-required: protected event requires authentication", sizeof(error_message) - 1);
error_message[sizeof(error_message) - 1] = '\0'; error_message[sizeof(error_message) - 1] = '\0';
log_warning("Protected event rejected: authentication required"); DEBUG_WARN("Protected event rejected: authentication required");
} }
} }
} }
@@ -519,8 +485,11 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
if (kind_obj && cJSON_IsNumber(kind_obj)) { if (kind_obj && cJSON_IsNumber(kind_obj)) {
int event_kind = (int)cJSON_GetNumberValue(kind_obj); int event_kind = (int)cJSON_GetNumberValue(kind_obj);
DEBUG_TRACE("Processing event kind %d", event_kind);
// Log reception of Kind 23456 events // Log reception of Kind 23456 events
if (event_kind == 23456) { if (event_kind == 23456) {
DEBUG_LOG("Admin event (kind 23456) received");
} }
if (event_kind == 23456) { if (event_kind == 23456) {
@@ -531,7 +500,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
if (auth_result != 0) { if (auth_result != 0) {
// Authorization failed - log and reject // Authorization failed - log and reject
log_warning("Admin event authorization failed"); DEBUG_WARN("Admin event authorization failed");
result = -1; result = -1;
size_t error_len = strlen(auth_error); size_t error_len = strlen(auth_error);
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1; size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
@@ -549,12 +518,12 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
char error_result_msg[512]; char error_result_msg[512];
snprintf(error_result_msg, sizeof(error_result_msg), snprintf(error_result_msg, sizeof(error_result_msg),
"ERROR: Kind %d event processing failed: %s", event_kind, admin_error); "ERROR: Kind %d event processing failed: %s", event_kind, admin_error);
log_error(error_result_msg); DEBUG_ERROR(error_result_msg);
} }
} }
if (admin_result != 0) { if (admin_result != 0) {
log_error("Failed to process admin event"); DEBUG_ERROR("Failed to process admin event");
result = -1; result = -1;
size_t error_len = strlen(admin_error); size_t error_len = strlen(admin_error);
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1; size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
@@ -574,7 +543,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Check if this is an error or if the command was already handled // Check if this is an error or if the command was already handled
if (strlen(nip17_error) > 0) { if (strlen(nip17_error) > 0) {
// There was an actual error // There was an actual error
log_error("NIP-17 admin message processing failed"); DEBUG_ERROR("NIP-17 admin message processing failed");
result = -1; result = -1;
size_t error_len = strlen(nip17_error); size_t error_len = strlen(nip17_error);
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1; size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
@@ -585,7 +554,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// No error message means the command was already handled (plain text commands) // No error message means the command was already handled (plain text commands)
// Store the original gift wrap event in database // Store the original gift wrap event in database
if (store_event(event) != 0) { if (store_event(event) != 0) {
log_error("Failed to store gift wrap event in database"); DEBUG_ERROR("Failed to store gift wrap event in database");
result = -1; result = -1;
strncpy(error_message, "error: failed to store gift wrap event", sizeof(error_message) - 1); strncpy(error_message, "error: failed to store gift wrap event", sizeof(error_message) - 1);
} }
@@ -593,7 +562,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
} else { } else {
// Store the original gift wrap event in database (unlike kind 23456) // Store the original gift wrap event in database (unlike kind 23456)
if (store_event(event) != 0) { if (store_event(event) != 0) {
log_error("Failed to store gift wrap event in database"); DEBUG_ERROR("Failed to store gift wrap event in database");
result = -1; result = -1;
strncpy(error_message, "error: failed to store gift wrap event", sizeof(error_message) - 1); strncpy(error_message, "error: failed to store gift wrap event", sizeof(error_message) - 1);
cJSON_Delete(response_event); cJSON_Delete(response_event);
@@ -612,7 +581,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
int dm_result = process_dm_stats_command(event, dm_error, sizeof(dm_error), wsi); int dm_result = process_dm_stats_command(event, dm_error, sizeof(dm_error), wsi);
if (dm_result != 0) { if (dm_result != 0) {
log_error("DM stats command processing failed"); DEBUG_ERROR("DM stats command processing failed");
result = -1; result = -1;
size_t error_len = strlen(dm_error); size_t error_len = strlen(dm_error);
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1; size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
@@ -622,7 +591,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
} else { } else {
// Store the DM event in database // Store the DM event in database
if (store_event(event) != 0) { if (store_event(event) != 0) {
log_error("Failed to store DM event in database"); DEBUG_ERROR("Failed to store DM event in database");
result = -1; result = -1;
strncpy(error_message, "error: failed to store DM event", sizeof(error_message) - 1); strncpy(error_message, "error: failed to store DM event", sizeof(error_message) - 1);
} else { } else {
@@ -631,21 +600,23 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
} }
} }
} else { } else {
DEBUG_TRACE("Storing regular event in database");
// Regular event - store in database and broadcast // Regular event - store in database and broadcast
if (store_event(event) != 0) { if (store_event(event) != 0) {
log_error("Failed to store event in database"); DEBUG_ERROR("Failed to store event in database");
result = -1; result = -1;
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1); strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
} else { } else {
DEBUG_LOG("Event stored and broadcast (kind %d)", event_kind);
// Broadcast event to matching persistent subscriptions // Broadcast event to matching persistent subscriptions
broadcast_event_to_subscriptions(event); broadcast_event_to_subscriptions(event);
} }
} }
} else { } else {
// Event without valid kind - try normal storage // Event without valid kind - try normal storage
log_warning("Event without valid kind - trying normal storage"); DEBUG_WARN("Event without valid kind - trying normal storage");
if (store_event(event) != 0) { if (store_event(event) != 0) {
log_error("Failed to store event without kind in database"); DEBUG_ERROR("Failed to store event without kind in database");
result = -1; result = -1;
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1); strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
} else { } else {
@@ -678,13 +649,15 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
} }
} }
} else if (strcmp(msg_type, "REQ") == 0) { } else if (strcmp(msg_type, "REQ") == 0) {
// Log the full REQ message for debugging
// Check NIP-42 authentication for REQ subscriptions if required // Check NIP-42 authentication for REQ subscriptions if required
if (pss && pss->nip42_auth_required_subscriptions && !pss->authenticated) { if (pss && pss->nip42_auth_required_subscriptions && !pss->authenticated) {
if (!pss->auth_challenge_sent) { if (!pss->auth_challenge_sent) {
send_nip42_auth_challenge(wsi, pss); send_nip42_auth_challenge(wsi, pss);
} else { } else {
send_notice_message(wsi, "NIP-42 authentication required for subscriptions"); send_notice_message(wsi, "NIP-42 authentication required for subscriptions");
log_warning("REQ rejected: NIP-42 authentication required"); DEBUG_WARN("REQ rejected: NIP-42 authentication required");
} }
cJSON_Delete(json); cJSON_Delete(json);
free(message); free(message);
@@ -697,10 +670,12 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
if (sub_id && cJSON_IsString(sub_id)) { if (sub_id && cJSON_IsString(sub_id)) {
const char* subscription_id = cJSON_GetStringValue(sub_id); const char* subscription_id = cJSON_GetStringValue(sub_id);
DEBUG_TRACE("Processing REQ message for subscription %s", subscription_id);
// Validate subscription ID before processing // Validate subscription ID before processing
if (!subscription_id) { if (!subscription_id) {
send_notice_message(wsi, "error: invalid subscription ID"); send_notice_message(wsi, "error: invalid subscription ID");
log_warning("REQ rejected: NULL subscription ID"); DEBUG_WARN("REQ rejected: NULL subscription ID");
record_malformed_request(pss); record_malformed_request(pss);
cJSON_Delete(json); cJSON_Delete(json);
free(message); free(message);
@@ -711,7 +686,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
size_t id_len = strlen(subscription_id); size_t id_len = strlen(subscription_id);
if (id_len == 0 || id_len >= SUBSCRIPTION_ID_MAX_LENGTH) { if (id_len == 0 || id_len >= SUBSCRIPTION_ID_MAX_LENGTH) {
send_notice_message(wsi, "error: subscription ID too long or empty"); send_notice_message(wsi, "error: subscription ID too long or empty");
log_warning("REQ rejected: invalid subscription ID length"); DEBUG_WARN("REQ rejected: invalid subscription ID length");
cJSON_Delete(json); cJSON_Delete(json);
free(message); free(message);
return 0; return 0;
@@ -719,18 +694,26 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Validate characters in subscription ID // Validate characters in subscription ID
int valid_id = 1; int valid_id = 1;
char invalid_char = '\0';
size_t invalid_pos = 0;
for (size_t i = 0; i < id_len; i++) { for (size_t i = 0; i < id_len; i++) {
char c = subscription_id[i]; char c = subscription_id[i];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '_' || c == '-')) { (c >= '0' && c <= '9') || c == '_' || c == '-' || c == ':')) {
valid_id = 0; valid_id = 0;
invalid_char = c;
invalid_pos = i;
break; break;
} }
} }
if (!valid_id) { if (!valid_id) {
char debug_msg[512];
snprintf(debug_msg, sizeof(debug_msg),
"REQ rejected: invalid character '%c' (0x%02X) at position %zu in subscription ID: '%s'",
invalid_char, (unsigned char)invalid_char, invalid_pos, subscription_id);
DEBUG_WARN(debug_msg);
send_notice_message(wsi, "error: invalid characters in subscription ID"); send_notice_message(wsi, "error: invalid characters in subscription ID");
log_warning("REQ rejected: invalid characters in subscription ID");
cJSON_Delete(json); cJSON_Delete(json);
free(message); free(message);
return 0; return 0;
@@ -740,7 +723,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
cJSON* filters = cJSON_CreateArray(); cJSON* filters = cJSON_CreateArray();
if (!filters) { if (!filters) {
send_notice_message(wsi, "error: failed to process filters"); send_notice_message(wsi, "error: failed to process filters");
log_error("REQ failed: could not create filters array"); DEBUG_ERROR("REQ failed: could not create filters array");
cJSON_Delete(json); cJSON_Delete(json);
free(message); free(message);
return 0; return 0;
@@ -758,7 +741,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
char filter_error[512] = {0}; char filter_error[512] = {0};
if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) { if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) {
send_notice_message(wsi, filter_error); send_notice_message(wsi, filter_error);
log_warning("REQ rejected: invalid filters"); DEBUG_WARN("REQ rejected: invalid filters");
record_malformed_request(pss); record_malformed_request(pss);
cJSON_Delete(filters); cJSON_Delete(filters);
cJSON_Delete(json); cJSON_Delete(json);
@@ -771,6 +754,8 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Clean up the filters array we created // Clean up the filters array we created
cJSON_Delete(filters); cJSON_Delete(filters);
DEBUG_LOG("REQ subscription %s processed, sending EOSE", subscription_id);
// Send EOSE (End of Stored Events) // Send EOSE (End of Stored Events)
cJSON* eose_response = cJSON_CreateArray(); cJSON* eose_response = cJSON_CreateArray();
if (eose_response) { if (eose_response) {
@@ -792,7 +777,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
} }
} else { } else {
send_notice_message(wsi, "error: missing or invalid subscription ID in REQ"); send_notice_message(wsi, "error: missing or invalid subscription ID in REQ");
log_warning("REQ rejected: missing or invalid subscription ID"); DEBUG_WARN("REQ rejected: missing or invalid subscription ID");
} }
} else if (strcmp(msg_type, "COUNT") == 0) { } else if (strcmp(msg_type, "COUNT") == 0) {
// Check NIP-42 authentication for COUNT requests if required // Check NIP-42 authentication for COUNT requests if required
@@ -801,7 +786,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
send_nip42_auth_challenge(wsi, pss); send_nip42_auth_challenge(wsi, pss);
} else { } else {
send_notice_message(wsi, "NIP-42 authentication required for count requests"); send_notice_message(wsi, "NIP-42 authentication required for count requests");
log_warning("COUNT rejected: NIP-42 authentication required"); DEBUG_WARN("COUNT rejected: NIP-42 authentication required");
} }
cJSON_Delete(json); cJSON_Delete(json);
free(message); free(message);
@@ -828,7 +813,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
char filter_error[512] = {0}; char filter_error[512] = {0};
if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) { if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) {
send_notice_message(wsi, filter_error); send_notice_message(wsi, filter_error);
log_warning("COUNT rejected: invalid filters"); DEBUG_WARN("COUNT rejected: invalid filters");
record_malformed_request(pss); record_malformed_request(pss);
cJSON_Delete(filters); cJSON_Delete(filters);
cJSON_Delete(json); cJSON_Delete(json);
@@ -850,7 +835,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Validate subscription ID before processing // Validate subscription ID before processing
if (!subscription_id) { if (!subscription_id) {
send_notice_message(wsi, "error: invalid subscription ID in CLOSE"); send_notice_message(wsi, "error: invalid subscription ID in CLOSE");
log_warning("CLOSE rejected: NULL subscription ID"); DEBUG_WARN("CLOSE rejected: NULL subscription ID");
cJSON_Delete(json); cJSON_Delete(json);
free(message); free(message);
return 0; return 0;
@@ -860,7 +845,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
size_t id_len = strlen(subscription_id); size_t id_len = strlen(subscription_id);
if (id_len == 0 || id_len >= SUBSCRIPTION_ID_MAX_LENGTH) { if (id_len == 0 || id_len >= SUBSCRIPTION_ID_MAX_LENGTH) {
send_notice_message(wsi, "error: subscription ID too long or empty in CLOSE"); send_notice_message(wsi, "error: subscription ID too long or empty in CLOSE");
log_warning("CLOSE rejected: invalid subscription ID length"); DEBUG_WARN("CLOSE rejected: invalid subscription ID length");
cJSON_Delete(json); cJSON_Delete(json);
free(message); free(message);
return 0; return 0;
@@ -871,7 +856,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
for (size_t i = 0; i < id_len; i++) { for (size_t i = 0; i < id_len; i++) {
char c = subscription_id[i]; char c = subscription_id[i];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '_' || c == '-')) { (c >= '0' && c <= '9') || c == '_' || c == '-' || c == ':')) {
valid_id = 0; valid_id = 0;
break; break;
} }
@@ -879,7 +864,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
if (!valid_id) { if (!valid_id) {
send_notice_message(wsi, "error: invalid characters in subscription ID for CLOSE"); send_notice_message(wsi, "error: invalid characters in subscription ID for CLOSE");
log_warning("CLOSE rejected: invalid characters in subscription ID"); DEBUG_WARN("CLOSE rejected: invalid characters in subscription ID");
cJSON_Delete(json); cJSON_Delete(json);
free(message); free(message);
return 0; return 0;
@@ -909,7 +894,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Subscription closed // Subscription closed
} else { } else {
send_notice_message(wsi, "error: missing or invalid subscription ID in CLOSE"); send_notice_message(wsi, "error: missing or invalid subscription ID in CLOSE");
log_warning("CLOSE rejected: missing or invalid subscription ID"); DEBUG_WARN("CLOSE rejected: missing or invalid subscription ID");
} }
} else if (strcmp(msg_type, "AUTH") == 0) { } else if (strcmp(msg_type, "AUTH") == 0) {
// Handle NIP-42 AUTH message // Handle NIP-42 AUTH message
@@ -924,17 +909,17 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
handle_nip42_auth_signed_event(wsi, pss, auth_payload); handle_nip42_auth_signed_event(wsi, pss, auth_payload);
} else { } else {
send_notice_message(wsi, "Invalid AUTH message format"); send_notice_message(wsi, "Invalid AUTH message format");
log_warning("Received AUTH message with invalid payload type"); DEBUG_WARN("Received AUTH message with invalid payload type");
} }
} else { } else {
send_notice_message(wsi, "AUTH message requires payload"); send_notice_message(wsi, "AUTH message requires payload");
log_warning("Received AUTH message without payload"); DEBUG_WARN("Received AUTH message without payload");
} }
} else { } else {
// Unknown message type // Unknown message type
char unknown_msg[128]; char unknown_msg[128];
snprintf(unknown_msg, sizeof(unknown_msg), "Unknown message type: %.32s", msg_type); snprintf(unknown_msg, sizeof(unknown_msg), "Unknown message type: %.32s", msg_type);
log_warning(unknown_msg); DEBUG_WARN(unknown_msg);
send_notice_message(wsi, "Unknown message type"); send_notice_message(wsi, "Unknown message type");
} }
} }
@@ -947,6 +932,8 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
break; break;
case LWS_CALLBACK_CLOSED: case LWS_CALLBACK_CLOSED:
DEBUG_TRACE("WebSocket connection closed");
DEBUG_LOG("WebSocket connection closed from %s", pss ? pss->client_ip : "unknown");
// Clean up session subscriptions // Clean up session subscriptions
if (pss) { if (pss) {
@@ -965,6 +952,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
pthread_mutex_unlock(&pss->session_lock); pthread_mutex_unlock(&pss->session_lock);
pthread_mutex_destroy(&pss->session_lock); pthread_mutex_destroy(&pss->session_lock);
} }
DEBUG_TRACE("WebSocket connection cleanup complete");
break; break;
default: default:
@@ -1028,6 +1016,9 @@ int start_websocket_relay(int port_override, int strict_port) {
// Starting libwebsockets-based Nostr relay server // Starting libwebsockets-based Nostr relay server
// Set libwebsockets log level to errors only
lws_set_log_level(LLL_USER | LLL_ERR, NULL);
memset(&info, 0, sizeof(info)); memset(&info, 0, sizeof(info));
// Use port override if provided, otherwise use configuration // Use port override if provided, otherwise use configuration
int configured_port = (port_override > 0) ? port_override : get_config_int("relay_port", DEFAULT_PORT); int configured_port = (port_override > 0) ? port_override : get_config_int("relay_port", DEFAULT_PORT);
@@ -1063,13 +1054,13 @@ int start_websocket_relay(int port_override, int strict_port) {
char error_msg[256]; char error_msg[256];
snprintf(error_msg, sizeof(error_msg), snprintf(error_msg, sizeof(error_msg),
"Strict port mode: port %d is not available", actual_port); "Strict port mode: port %d is not available", actual_port);
log_error(error_msg); DEBUG_ERROR(error_msg);
return -1; return -1;
} else if (port_attempts < max_port_attempts) { } else if (port_attempts < max_port_attempts) {
char retry_msg[256]; char retry_msg[256];
snprintf(retry_msg, sizeof(retry_msg), "Port %d is in use, trying port %d (attempt %d/%d)", snprintf(retry_msg, sizeof(retry_msg), "Port %d is in use, trying port %d (attempt %d/%d)",
actual_port, actual_port + 1, port_attempts + 1, max_port_attempts); actual_port, actual_port + 1, port_attempts + 1, max_port_attempts);
log_warning(retry_msg); DEBUG_WARN(retry_msg);
actual_port++; actual_port++;
continue; continue;
} else { } else {
@@ -1077,7 +1068,7 @@ int start_websocket_relay(int port_override, int strict_port) {
snprintf(error_msg, sizeof(error_msg), snprintf(error_msg, sizeof(error_msg),
"Failed to find available port after %d attempts (tried ports %d-%d)", "Failed to find available port after %d attempts (tried ports %d-%d)",
max_port_attempts, configured_port, actual_port); max_port_attempts, configured_port, actual_port);
log_error(error_msg); DEBUG_ERROR(error_msg);
return -1; return -1;
} }
} }
@@ -1099,14 +1090,14 @@ int start_websocket_relay(int port_override, int strict_port) {
char lws_error_msg[256]; char lws_error_msg[256];
snprintf(lws_error_msg, sizeof(lws_error_msg), snprintf(lws_error_msg, sizeof(lws_error_msg),
"libwebsockets failed to bind to port %d (errno: %d)", actual_port, errno_saved); "libwebsockets failed to bind to port %d (errno: %d)", actual_port, errno_saved);
log_warning(lws_error_msg); DEBUG_WARN(lws_error_msg);
port_attempts++; port_attempts++;
if (strict_port) { if (strict_port) {
char error_msg[256]; char error_msg[256];
snprintf(error_msg, sizeof(error_msg), snprintf(error_msg, sizeof(error_msg),
"Strict port mode: failed to bind to port %d", actual_port); "Strict port mode: failed to bind to port %d", actual_port);
log_error(error_msg); DEBUG_ERROR(error_msg);
break; break;
} else if (port_attempts < max_port_attempts) { } else if (port_attempts < max_port_attempts) {
actual_port++; actual_port++;
@@ -1122,7 +1113,7 @@ int start_websocket_relay(int port_override, int strict_port) {
snprintf(error_msg, sizeof(error_msg), snprintf(error_msg, sizeof(error_msg),
"Failed to create libwebsockets context after %d attempts. Last attempted port: %d", "Failed to create libwebsockets context after %d attempts. Last attempted port: %d",
port_attempts, actual_port); port_attempts, actual_port);
log_error(error_msg); DEBUG_ERROR(error_msg);
perror("libwebsockets creation error"); perror("libwebsockets creation error");
return -1; return -1;
} }
@@ -1132,7 +1123,7 @@ int start_websocket_relay(int port_override, int strict_port) {
snprintf(startup_msg, sizeof(startup_msg), snprintf(startup_msg, sizeof(startup_msg),
"WebSocket relay started on ws://127.0.0.1:%d (configured port %d was unavailable)", "WebSocket relay started on ws://127.0.0.1:%d (configured port %d was unavailable)",
actual_port, configured_port); actual_port, configured_port);
log_warning(startup_msg); DEBUG_WARN(startup_msg);
} else { } else {
snprintf(startup_msg, sizeof(startup_msg), "WebSocket relay started on ws://127.0.0.1:%d", actual_port); snprintf(startup_msg, sizeof(startup_msg), "WebSocket relay started on ws://127.0.0.1:%d", actual_port);
} }
@@ -1142,7 +1133,7 @@ int start_websocket_relay(int port_override, int strict_port) {
int result = lws_service(ws_context, 1000); int result = lws_service(ws_context, 1000);
if (result < 0) { if (result < 0) {
log_error("libwebsockets service error"); DEBUG_ERROR("libwebsockets service error");
break; break;
} }
} }
@@ -1326,7 +1317,7 @@ int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, st
(void)pss; // Suppress unused parameter warning (void)pss; // Suppress unused parameter warning
if (!cJSON_IsArray(filters)) { if (!cJSON_IsArray(filters)) {
log_error("COUNT filters is not an array"); DEBUG_ERROR("COUNT filters is not an array");
return 0; return 0;
} }
@@ -1341,7 +1332,7 @@ int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, st
for (int i = 0; i < cJSON_GetArraySize(filters); i++) { for (int i = 0; i < cJSON_GetArraySize(filters); i++) {
cJSON* filter = cJSON_GetArrayItem(filters, i); cJSON* filter = cJSON_GetArrayItem(filters, i);
if (!filter || !cJSON_IsObject(filter)) { if (!filter || !cJSON_IsObject(filter)) {
log_warning("Invalid filter object in COUNT"); DEBUG_WARN("Invalid filter object in COUNT");
continue; continue;
} }
@@ -1588,7 +1579,7 @@ int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, st
if (rc != SQLITE_OK) { if (rc != SQLITE_OK) {
char error_msg[256]; char error_msg[256];
snprintf(error_msg, sizeof(error_msg), "Failed to prepare COUNT query: %s", sqlite3_errmsg(g_db)); snprintf(error_msg, sizeof(error_msg), "Failed to prepare COUNT query: %s", sqlite3_errmsg(g_db));
log_error(error_msg); DEBUG_ERROR(error_msg);
continue; continue;
} }
@@ -1683,7 +1674,7 @@ int is_client_rate_limited_for_malformed_requests(struct per_session_data *pss)
if (pss->malformed_request_count >= MAX_MALFORMED_REQUESTS_PER_HOUR) { if (pss->malformed_request_count >= MAX_MALFORMED_REQUESTS_PER_HOUR) {
// Block for the specified duration // Block for the specified duration
pss->malformed_request_blocked_until = now + MALFORMED_REQUEST_BLOCK_DURATION; pss->malformed_request_blocked_until = now + MALFORMED_REQUEST_BLOCK_DURATION;
log_warning("Client rate limited for malformed requests"); DEBUG_WARN("Client rate limited for malformed requests");
return 1; return 1;
} }

View File

@@ -9,7 +9,8 @@ Type=simple
User=c-relay User=c-relay
Group=c-relay Group=c-relay
WorkingDirectory=/opt/c-relay WorkingDirectory=/opt/c-relay
ExecStart=/opt/c-relay/c_relay_x86 Environment=DEBUG_LEVEL=0
ExecStart=/opt/c-relay/c_relay_x86 --debug-level=$DEBUG_LEVEL
Restart=always Restart=always
RestartSec=5 RestartSec=5
StandardOutput=journal StandardOutput=journal

View File

@@ -1,28 +0,0 @@
2025-10-11 10:56:27 - ==========================================
2025-10-11 10:56:27 - C-Relay Comprehensive Test Suite Runner
2025-10-11 10:56:27 - ==========================================
2025-10-11 10:56:27 - Relay URL: ws://127.0.0.1:8888
2025-10-11 10:56:27 - Log file: test_results_20251011_105627.log
2025-10-11 10:56:27 - Report file: test_report_20251011_105627.html
2025-10-11 10:56:27 -
2025-10-11 10:56:27 - Checking relay status at ws://127.0.0.1:8888...
2025-10-11 10:56:27 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m
2025-10-11 10:56:27 -
2025-10-11 10:56:27 - Starting comprehensive test execution...
2025-10-11 10:56:27 -
2025-10-11 10:56:27 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m
2025-10-11 10:56:27 - ==========================================
2025-10-11 10:56:27 - Running Test Suite: SQL Injection Tests
2025-10-11 10:56:27 - Description: Comprehensive SQL injection vulnerability testing
2025-10-11 10:56:27 - ==========================================
==========================================
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; --... UNCERTAIN - Connection timeout (may indicate crash)
2025-10-11 10:56:32 - \033[0;31m✗ SQL Injection Tests FAILED\033[0m (Duration: 5s)

View File

@@ -28,7 +28,7 @@ echo "✓ nak command found"
# Check if relay is running by testing connection # Check if relay is running by testing connection
echo "Testing relay connection..." echo "Testing relay connection..."
if ! timeout 5 bash -c "</dev/tcp/localhost/8888" 2>/dev/null; then if ! timeout 5 nc -z localhost 8888 2>/dev/null; then
echo "ERROR: Relay does not appear to be running on localhost:8888" echo "ERROR: Relay does not appear to be running on localhost:8888"
echo "Please start the relay first with: ./make_and_restart_relay.sh" echo "Please start the relay first with: ./make_and_restart_relay.sh"
exit 1 exit 1

View File

@@ -32,9 +32,7 @@ test_auth_challenge() {
# Send a REQ message that should trigger auth # Send a REQ message that should trigger auth
local response local response
response=$(timeout 10 bash -c " response=$(echo "[\"REQ\",\"auth_test_$(date +%s)\",{}]" | timeout 10 websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -3 || echo 'TIMEOUT')
echo '[\"REQ\",\"auth_test_'$(date +%s)'\",{}]' | websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -3
" 2>/dev/null || echo 'TIMEOUT')
if [[ "$response" == *"TIMEOUT"* ]]; then if [[ "$response" == *"TIMEOUT"* ]]; then
echo -e "${RED}FAILED${NC} - Connection timeout" echo -e "${RED}FAILED${NC} - Connection timeout"
@@ -80,9 +78,7 @@ EOF
# Send auth list modification # Send auth list modification
local response local response
response=$(timeout 10 bash -c " response=$(echo "$admin_event" | timeout 10 websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1 || echo 'TIMEOUT')
echo '$admin_event' | websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1
" 2>/dev/null || echo 'TIMEOUT')
if [[ "$response" == *"TIMEOUT"* ]]; then if [[ "$response" == *"TIMEOUT"* ]]; then
echo -e "${RED}FAILED${NC} - Connection timeout" echo -e "${RED}FAILED${NC} - Connection timeout"

View File

@@ -47,9 +47,7 @@ EOF
# Send config query event # Send config query event
local response local response
response=$(timeout 10 bash -c " response=$(echo "$admin_event" | timeout 10 websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -3 || echo 'TIMEOUT')
echo '$admin_event' | websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -3
" 2>/dev/null || echo 'TIMEOUT')
if [[ "$response" == *"TIMEOUT"* ]]; then if [[ "$response" == *"TIMEOUT"* ]]; then
echo -e "${RED}FAILED${NC} - Connection timeout" echo -e "${RED}FAILED${NC} - Connection timeout"
@@ -94,9 +92,7 @@ EOF
# Send config setting event # Send config setting event
local response local response
response=$(timeout 10 bash -c " response=$(echo "$admin_event" | timeout 10 websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -3 || echo 'TIMEOUT')
echo '$admin_event' | websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -3
" 2>/dev/null || echo 'TIMEOUT')
if [[ "$response" == *"TIMEOUT"* ]]; then if [[ "$response" == *"TIMEOUT"* ]]; then
echo -e "${RED}FAILED${NC} - Connection timeout" echo -e "${RED}FAILED${NC} - Connection timeout"

48
tests/debug_perf.sh Executable file
View File

@@ -0,0 +1,48 @@
#!/bin/bash
# Debug script for performance_benchmarks.sh
source ./performance_benchmarks.sh
echo "Testing benchmark_request function..."
result=$(benchmark_request '["REQ","test",{}]')
echo "Result: $result"
echo "Testing full client subprocess..."
(
client_start=$(date +%s)
client_requests=0
client_total_response_time=0
client_successful_requests=0
client_min_time=999999
client_max_time=0
while [[ $(($(date +%s) - client_start)) -lt 3 ]]; do
result=$(benchmark_request '["REQ","test",{}]')
IFS=':' read -r response_time success <<< "$result"
client_total_response_time=$((client_total_response_time + response_time))
client_requests=$((client_requests + 1))
if [[ "$success" == "1" ]]; then
client_successful_requests=$((client_successful_requests + 1))
fi
if [[ $response_time -lt client_min_time ]]; then
client_min_time=$response_time
fi
if [[ $response_time -gt client_max_time ]]; then
client_max_time=$response_time
fi
echo "Request $client_requests: ${response_time}ms, success=$success"
sleep 0.1
done
echo "$client_requests:$client_successful_requests:$client_total_response_time:$client_min_time:$client_max_time"
) &
pid=$!
echo "Waiting for client..."
wait "$pid"
echo "Client finished."

View File

@@ -34,9 +34,7 @@ test_websocket_message() {
# Send message via websocat and capture response # Send message via websocat and capture response
local response local response
response=$(timeout $TEST_TIMEOUT bash -c " response=$(echo "$message" | timeout $TEST_TIMEOUT websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null || echo 'CONNECTION_FAILED')
echo '$message' | websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null || echo 'CONNECTION_FAILED'
" 2>/dev/null || echo 'TIMEOUT')
if [[ "$response" == "CONNECTION_FAILED" ]]; then if [[ "$response" == "CONNECTION_FAILED" ]]; then
echo -e "${RED}FAILED${NC} - Could not connect to relay" echo -e "${RED}FAILED${NC} - Could not connect to relay"
@@ -73,9 +71,7 @@ test_valid_message() {
# Send message via websocat and capture response # Send message via websocat and capture response
local response local response
response=$(timeout $TEST_TIMEOUT bash -c " response=$(echo "$message" | timeout $TEST_TIMEOUT websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1 || echo 'TIMEOUT')
echo '$message' | websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1
" 2>/dev/null || echo 'TIMEOUT')
if [[ "$response" == "TIMEOUT" ]]; then if [[ "$response" == "TIMEOUT" ]]; then
echo -e "${RED}FAILED${NC} - Connection timeout" echo -e "${RED}FAILED${NC} - Connection timeout"

File diff suppressed because one or more lines are too long

View File

@@ -50,20 +50,13 @@ run_client() {
done done
# Send CLOSE message # Send CLOSE message
echo '["CLOSE","load_test_'"$client_id"'_*"]' echo '["CLOSE","load_test_'"$client_id"'_*"]'
) | timeout 60 websocat -B 1048576 "ws://$RELAY_HOST:$RELAY_PORT" > "$temp_file" 2>/dev/null & ) | timeout 30 websocat -B 1048576 "ws://$RELAY_HOST:$RELAY_PORT" > "$temp_file" 2>/dev/null
local client_pid=$! local exit_code=$?
# Wait a bit for the client to complete # Check if connection was successful (exit code 0 means successful)
sleep 2 if [[ $exit_code -eq 0 ]]; then
# Check if client is still running (good sign)
if kill -0 "$client_pid" 2>/dev/null; then
connection_successful=true connection_successful=true
((SUCCESSFUL_CONNECTIONS++))
else
wait "$client_pid" 2>/dev/null || true
((FAILED_CONNECTIONS++))
fi fi
# Count messages sent # Count messages sent
@@ -131,52 +124,61 @@ run_load_test() {
TOTAL_MESSAGES_SENT=0 TOTAL_MESSAGES_SENT=0
TOTAL_MESSAGES_RECEIVED=0 TOTAL_MESSAGES_RECEIVED=0
# Start resource monitoring in background # Launch clients sequentially for now (simpler debugging)
monitor_resources 30 &
local monitor_pid=$!
# Launch clients
local client_pids=()
local client_results=() local client_results=()
echo "Launching $concurrent_clients concurrent clients..." echo "Launching $concurrent_clients clients..."
for i in $(seq 1 "$concurrent_clients"); do for i in $(seq 1 "$concurrent_clients"); do
run_client "$i" "$messages_per_client" & local result
client_pids+=($!) result=$(run_client "$i" "$messages_per_client")
client_results+=("$result")
TOTAL_CONNECTIONS=$((TOTAL_CONNECTIONS + 1))
done done
# Wait for all clients to complete echo "All clients completed. Processing results..."
echo "Waiting for clients to complete..."
for pid in "${client_pids[@]}"; do
wait "$pid" 2>/dev/null || true
done
# Stop monitoring
kill "$monitor_pid" 2>/dev/null || true
wait "$monitor_pid" 2>/dev/null || true
END_TIME=$(date +%s) END_TIME=$(date +%s)
local duration=$((END_TIME - START_TIME)) local duration=$((END_TIME - START_TIME))
# Process client results
local successful_connections=0
local failed_connections=0
local total_messages_sent=0
local total_messages_received=0
for result in "${client_results[@]}"; do
messages_sent=$(echo "$result" | cut -d: -f1)
messages_received=$(echo "$result" | cut -d: -f2)
connection_successful=$(echo "$result" | cut -d: -f3)
if [[ "$connection_successful" == "true" ]]; then
successful_connections=$((successful_connections + 1))
else
failed_connections=$((failed_connections + 1))
fi
total_messages_sent=$((total_messages_sent + messages_sent))
total_messages_received=$((total_messages_received + messages_received))
done
# Calculate metrics # Calculate metrics
local total_messages_expected=$((concurrent_clients * messages_per_client)) local total_messages_expected=$((concurrent_clients * messages_per_client))
local connection_success_rate=0 local connection_success_rate=0
local total_connections=$((SUCCESSFUL_CONNECTIONS + FAILED_CONNECTIONS))
if [[ $total_connections -gt 0 ]]; then if [[ $TOTAL_CONNECTIONS -gt 0 ]]; then
connection_success_rate=$((SUCCESSFUL_CONNECTIONS * 100 / total_connections)) connection_success_rate=$((successful_connections * 100 / TOTAL_CONNECTIONS))
fi fi
# Report results # Report results
echo "" echo ""
echo "=== Load Test Results ===" echo "=== Load Test Results ==="
echo "Test duration: ${duration}s" echo "Test duration: ${duration}s"
echo "Total connections attempted: $total_connections" echo "Total connections attempted: $TOTAL_CONNECTIONS"
echo "Successful connections: $SUCCESSFUL_CONNECTIONS" echo "Successful connections: $successful_connections"
echo "Failed connections: $FAILED_CONNECTIONS" echo "Failed connections: $failed_connections"
echo "Connection success rate: ${connection_success_rate}%" echo "Connection success rate: ${connection_success_rate}%"
echo "Messages expected: $total_messages_expected" echo "Messages expected: $total_messages_expected"
echo "Messages sent: $total_messages_sent"
echo "Messages received: $total_messages_received"
# Performance assessment # Performance assessment
if [[ $connection_success_rate -ge 95 ]]; then if [[ $connection_success_rate -ge 95 ]]; then
@@ -190,9 +192,7 @@ run_load_test() {
# Check if relay is still responsive # Check if relay is still responsive
echo "" echo ""
echo -n "Checking relay responsiveness... " echo -n "Checking relay responsiveness... "
if timeout 5 bash -c " if echo 'ping' | timeout 5 websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT >/dev/null 2>&1; then
echo 'ping' | websocat -n1 ws://$RELAY_HOST:$RELAY_PORT >/dev/null 2>&1
" 2>/dev/null; then
echo -e "${GREEN}✓ Relay is still responsive${NC}" echo -e "${GREEN}✓ Relay is still responsive${NC}"
else else
echo -e "${RED}✗ Relay became unresponsive after load test${NC}" echo -e "${RED}✗ Relay became unresponsive after load test${NC}"
@@ -200,39 +200,40 @@ run_load_test() {
fi fi
} }
echo "==========================================" # Only run main code if script is executed directly (not sourced)
echo "C-Relay Load Testing Suite" if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
echo "==========================================" echo "=========================================="
echo "Testing against relay at ws://$RELAY_HOST:$RELAY_PORT" echo "C-Relay Load Testing Suite"
echo "" echo "=========================================="
echo "Testing against relay at ws://$RELAY_HOST:$RELAY_PORT"
echo ""
# Test basic connectivity first # Test basic connectivity first
echo "=== Basic Connectivity Test ===" echo "=== Basic Connectivity Test ==="
if timeout 5 bash -c " if echo 'ping' | timeout 5 websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT >/dev/null 2>&1; then
echo 'ping' | websocat -n1 ws://$RELAY_HOST:$RELAY_PORT >/dev/null 2>&1
" 2>/dev/null; then
echo -e "${GREEN}✓ Relay is accessible${NC}" echo -e "${GREEN}✓ Relay is accessible${NC}"
else else
echo -e "${RED}✗ Cannot connect to relay. Aborting tests.${NC}" echo -e "${RED}✗ Cannot connect to relay. Aborting tests.${NC}"
exit 1 exit 1
fi
echo ""
# Run different load scenarios
run_load_test "Light Load Test" "Basic load test with moderate concurrent connections" 10 5
echo ""
run_load_test "Medium Load Test" "Moderate load test with higher concurrency" 25 10
echo ""
run_load_test "Heavy Load Test" "Heavy load test with high concurrency" 50 20
echo ""
run_load_test "Stress Test" "Maximum load test to find breaking point" 100 50
echo ""
echo "=========================================="
echo "Load Testing Complete"
echo "=========================================="
echo "All load tests completed. Check individual test results above."
echo "If any tests failed, the relay may need optimization or have resource limits."
fi fi
echo ""
# Run different load scenarios
run_load_test "Light Load Test" "Basic load test with moderate concurrent connections" 10 5
echo ""
run_load_test "Medium Load Test" "Moderate load test with higher concurrency" 25 10
echo ""
run_load_test "Heavy Load Test" "Heavy load test with high concurrency" 50 20
echo ""
run_load_test "Stress Test" "Maximum load test to find breaking point" 100 50
echo ""
echo "=========================================="
echo "Load Testing Complete"
echo "=========================================="
echo "All load tests completed. Check individual test results above."
echo "If any tests failed, the relay may need optimization or have resource limits."

View File

@@ -35,16 +35,17 @@ test_memory_safety() {
# Send message and monitor for crashes or memory issues # Send message and monitor for crashes or memory issues
local start_time=$(date +%s%N) local start_time=$(date +%s%N)
local response local response
response=$(timeout $TEST_TIMEOUT bash -c " response=$(echo "$message" | timeout $TEST_TIMEOUT websocat ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1 || echo 'CONNECTION_FAILED')
echo '$message' | websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null
" 2>/dev/null || echo 'CONNECTION_FAILED')
local end_time=$(date +%s%N) local end_time=$(date +%s%N)
# Check if relay is still responsive after the test # Check if relay is still responsive after the test
local relay_status local relay_status
relay_status=$(timeout 2 bash -c " local ping_response=$(echo '["REQ","ping_test_'$RANDOM'",{}]' | timeout 2 websocat ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1)
echo 'ping' | websocat -n1 ws://$RELAY_HOST:$RELAY_PORT >/dev/null 2>&1 && echo 'OK' || echo 'DOWN' if [[ -n "$ping_response" ]]; then
" 2>/dev/null || echo 'DOWN') relay_status="OK"
else
relay_status="DOWN"
fi
# Calculate response time (rough indicator of processing issues) # Calculate response time (rough indicator of processing issues)
local response_time=$(( (end_time - start_time) / 1000000 )) # Convert to milliseconds local response_time=$(( (end_time - start_time) / 1000000 )) # Convert to milliseconds
@@ -97,9 +98,7 @@ test_concurrent_access() {
for i in $(seq 1 $concurrent_count); do for i in $(seq 1 $concurrent_count); do
( (
local response local response
response=$(timeout $TEST_TIMEOUT bash -c " response=$(echo "$message" | timeout $TEST_TIMEOUT websocat ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1 || echo 'FAILED')
echo '$message' | websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1
" 2>/dev/null || echo 'FAILED')
echo "$response" echo "$response"
) & ) &
pids+=($!) pids+=($!)
@@ -113,9 +112,12 @@ test_concurrent_access() {
# Check if relay is still responsive # Check if relay is still responsive
local relay_status local relay_status
relay_status=$(timeout 2 bash -c " local ping_response=$(echo '["REQ","ping_test_'$RANDOM'",{}]' | timeout 2 websocat ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1)
echo 'ping' | websocat -n1 ws://$RELAY_HOST:$RELAY_PORT >/dev/null 2>&1 && echo 'OK' || echo 'DOWN' if [[ -n "$ping_response" ]]; then
" 2>/dev/null || echo 'DOWN') relay_status="OK"
else
relay_status="DOWN"
fi
if [[ "$relay_status" != "OK" ]]; then if [[ "$relay_status" != "OK" ]]; then
echo -e "${RED}FAILED${NC} - Relay crashed during concurrent access" echo -e "${RED}FAILED${NC} - Relay crashed during concurrent access"

View File

@@ -31,32 +31,21 @@ benchmark_request() {
local start_time local start_time
local end_time local end_time
local response_time local response_time
local success=0
start_time=$(date +%s%N) start_time=$(date +%s%N)
local response local response
response=$(timeout 5 bash -c " response=$(echo "$message" | timeout 5 websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1 || echo 'TIMEOUT')
echo '$message' | websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1
" 2>/dev/null || echo 'TIMEOUT')
end_time=$(date +%s%N) end_time=$(date +%s%N)
response_time=$(( (end_time - start_time) / 1000000 )) # Convert to milliseconds response_time=$(( (end_time - start_time) / 1000000 )) # Convert to milliseconds
TOTAL_REQUESTS=$((TOTAL_REQUESTS + 1))
TOTAL_RESPONSE_TIME=$((TOTAL_RESPONSE_TIME + response_time))
if [[ $response_time -lt MIN_RESPONSE_TIME ]]; then
MIN_RESPONSE_TIME=$response_time
fi
if [[ $response_time -gt MAX_RESPONSE_TIME ]]; then
MAX_RESPONSE_TIME=$response_time
fi
if [[ "$response" == *"EOSE"* ]] || [[ "$response" == *"EVENT"* ]] || [[ "$response" == *"OK"* ]]; then if [[ "$response" == *"EOSE"* ]] || [[ "$response" == *"EVENT"* ]] || [[ "$response" == *"OK"* ]]; then
SUCCESSFUL_REQUESTS=$((SUCCESSFUL_REQUESTS + 1)) success=1
else
FAILED_REQUESTS=$((FAILED_REQUESTS + 1))
fi fi
# Return: response_time:success
echo "$response_time:$success"
} }
# Function to run throughput benchmark # Function to run throughput benchmark
@@ -84,62 +73,113 @@ run_throughput_benchmark() {
local start_time local start_time
start_time=$(date +%s) start_time=$(date +%s)
# Launch concurrent clients # Launch concurrent clients and collect results
local pids=() local pids=()
local client_results=()
for i in $(seq 1 "$concurrent_clients"); do for i in $(seq 1 "$concurrent_clients"); do
( (
local client_start local client_start
client_start=$(date +%s) client_start=$(date +%s)
local client_requests=0 local client_requests=0
local client_total_response_time=0
local client_successful_requests=0
local client_min_time=999999
local client_max_time=0
while [[ $(($(date +%s) - client_start)) -lt test_duration ]]; do while [[ $(($(date +%s) - client_start)) -lt test_duration ]]; do
benchmark_request "$message" local result
((client_requests++)) result=$(benchmark_request "$message")
local response_time success
IFS=':' read -r response_time success <<< "$result"
client_total_response_time=$((client_total_response_time + response_time))
client_requests=$((client_requests + 1))
if [[ "$success" == "1" ]]; then
client_successful_requests=$((client_successful_requests + 1))
fi
if [[ $response_time -lt client_min_time ]]; then
client_min_time=$response_time
fi
if [[ $response_time -gt client_max_time ]]; then
client_max_time=$response_time
fi
# Small delay to prevent overwhelming # Small delay to prevent overwhelming
sleep 0.01 sleep 0.01
done done
echo "client_${i}_requests:$client_requests" # Return client results: requests:successful:total_response_time:min_time:max_time
echo "$client_requests:$client_successful_requests:$client_total_response_time:$client_min_time:$client_max_time"
) & ) &
pids+=($!) pids+=($!)
done done
# Wait for all clients to complete # Wait for all clients to complete and collect results
local client_results=()
for pid in "${pids[@]}"; do for pid in "${pids[@]}"; do
client_results+=("$(wait "$pid")") local result
result=$(wait "$pid")
client_results+=("$result")
done done
local end_time local end_time
end_time=$(date +%s) end_time=$(date +%s)
local actual_duration=$((end_time - start_time)) local actual_duration=$((end_time - start_time))
# Process client results
local total_requests=0
local successful_requests=0
local total_response_time=0
local min_response_time=999999
local max_response_time=0
for client_result in "${client_results[@]}"; do
IFS=':' read -r client_requests client_successful client_total_time client_min_time client_max_time <<< "$client_result"
total_requests=$((total_requests + client_requests))
successful_requests=$((successful_requests + client_successful))
total_response_time=$((total_response_time + client_total_time))
if [[ $client_min_time -lt min_response_time ]]; then
min_response_time=$client_min_time
fi
if [[ $client_max_time -gt max_response_time ]]; then
max_response_time=$client_max_time
fi
done
# Calculate metrics # Calculate metrics
local avg_response_time="N/A" local avg_response_time="N/A"
if [[ $SUCCESSFUL_REQUESTS -gt 0 ]]; then if [[ $successful_requests -gt 0 ]]; then
avg_response_time="$((TOTAL_RESPONSE_TIME / SUCCESSFUL_REQUESTS))ms" avg_response_time="$((total_response_time / successful_requests))ms"
fi fi
local requests_per_second="N/A" local requests_per_second="N/A"
if [[ $actual_duration -gt 0 ]]; then if [[ $actual_duration -gt 0 ]]; then
requests_per_second="$((TOTAL_REQUESTS / actual_duration))" requests_per_second="$((total_requests / actual_duration))"
fi fi
local success_rate="N/A" local success_rate="N/A"
if [[ $TOTAL_REQUESTS -gt 0 ]]; then if [[ $total_requests -gt 0 ]]; then
success_rate="$((SUCCESSFUL_REQUESTS * 100 / TOTAL_REQUESTS))%" success_rate="$((successful_requests * 100 / total_requests))%"
fi fi
local failed_requests=$((total_requests - successful_requests))
# Report results # Report results
echo "=== Benchmark Results ===" echo "=== Benchmark Results ==="
echo "Total requests: $TOTAL_REQUESTS" echo "Total requests: $total_requests"
echo "Successful requests: $SUCCESSFUL_REQUESTS" echo "Successful requests: $successful_requests"
echo "Failed requests: $FAILED_REQUESTS" echo "Failed requests: $failed_requests"
echo "Success rate: $success_rate" echo "Success rate: $success_rate"
echo "Requests per second: $requests_per_second" echo "Requests per second: $requests_per_second"
echo "Average response time: $avg_response_time" echo "Average response time: $avg_response_time"
echo "Min response time: ${MIN_RESPONSE_TIME}ms" echo "Min response time: ${min_response_time}ms"
echo "Max response time: ${MAX_RESPONSE_TIME}ms" echo "Max response time: ${max_response_time}ms"
echo "Actual duration: ${actual_duration}s" echo "Actual duration: ${actual_duration}s"
echo "" echo ""
@@ -172,9 +212,7 @@ benchmark_memory_usage() {
# Create subscriptions # Create subscriptions
for j in $(seq 1 "$i"); do for j in $(seq 1 "$i"); do
timeout 2 bash -c " echo "[\"REQ\",\"mem_test_${j}\",{}]" | timeout 2 websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT >/dev/null 2>&1 &
echo '[\"REQ\",\"mem_test_'${j}'\",{}]' | websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT >/dev/null 2>&1
" 2>/dev/null &
done done
sleep 2 sleep 2
@@ -187,9 +225,7 @@ benchmark_memory_usage() {
# Clean up subscriptions # Clean up subscriptions
for j in $(seq 1 "$i"); do for j in $(seq 1 "$i"); do
timeout 2 bash -c " echo "[\"CLOSE\",\"mem_test_${j}\"]" | timeout 2 websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT >/dev/null 2>&1 &
echo '[\"CLOSE\",\"mem_test_'${j}'\"]' | websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT >/dev/null 2>&1
" 2>/dev/null &
done done
sleep 1 sleep 1
@@ -200,40 +236,44 @@ benchmark_memory_usage() {
echo "Final memory usage: ${final_memory}KB" echo "Final memory usage: ${final_memory}KB"
} }
echo "==========================================" # Only run main code if script is executed directly (not sourced)
echo "C-Relay Performance Benchmarking Suite" if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
echo "==========================================" echo "=========================================="
echo "Benchmarking relay at ws://$RELAY_HOST:$RELAY_PORT" echo "C-Relay Performance Benchmarking Suite"
echo "" echo "=========================================="
echo "Benchmarking relay at ws://$RELAY_HOST:$RELAY_PORT"
echo ""
# Test basic connectivity # Test basic connectivity
echo "=== Connectivity Test ===" echo "=== Connectivity Test ==="
benchmark_request '["REQ","bench_test",{}]' connectivity_result=$(benchmark_request '["REQ","bench_test",{}]')
if [[ $SUCCESSFUL_REQUESTS -eq 0 ]]; then IFS=':' read -r response_time success <<< "$connectivity_result"
if [[ "$success" != "1" ]]; then
echo -e "${RED}Cannot connect to relay. Aborting benchmarks.${NC}" echo -e "${RED}Cannot connect to relay. Aborting benchmarks.${NC}"
exit 1 exit 1
fi
echo -e "${GREEN}✓ Relay is accessible${NC}"
echo ""
# Run throughput benchmarks
run_throughput_benchmark "Simple REQ Throughput" '["REQ","throughput_'$(date +%s%N)'",{}]' 10 15
echo ""
run_throughput_benchmark "Complex Filter Throughput" '["REQ","complex_'$(date +%s%N)'",{"kinds":[1,2,3],"#e":["test"],"limit":10}]' 10 15
echo ""
run_throughput_benchmark "COUNT Message Throughput" '["REQ","count_'$(date +%s%N)'",{}]' 10 15
echo ""
run_throughput_benchmark "High Load Throughput" '["REQ","high_load_'$(date +%s%N)'",{}]' 25 20
echo ""
# Memory usage benchmark
benchmark_memory_usage
echo ""
echo "=========================================="
echo "Benchmarking Complete"
echo "=========================================="
echo "Performance benchmarks completed. Review results above for optimization opportunities."
fi fi
echo -e "${GREEN}✓ Relay is accessible${NC}"
echo ""
# Run throughput benchmarks
run_throughput_benchmark "Simple REQ Throughput" '["REQ","throughput_'$(date +%s%N)'",{}]' 10 15
echo ""
run_throughput_benchmark "Complex Filter Throughput" '["REQ","complex_'$(date +%s%N)'",{"kinds":[1,2,3],"#e":["test"],"limit":10}]' 10 15
echo ""
run_throughput_benchmark "COUNT Message Throughput" '["COUNT","count_'$(date +%s%N)'",{}]' 10 15
echo ""
run_throughput_benchmark "High Load Throughput" '["REQ","high_load_'$(date +%s%N)'",{}]' 25 20
echo ""
# Memory usage benchmark
benchmark_memory_usage
echo ""
echo "=========================================="
echo "Benchmarking Complete"
echo "=========================================="
echo "Performance benchmarks completed. Review results above for optimization opportunities."

View File

@@ -40,9 +40,7 @@ test_rate_limiting() {
# Send burst of messages # Send burst of messages
for i in $(seq 1 "$burst_count"); do for i in $(seq 1 "$burst_count"); do
local response local response
response=$(timeout 2 bash -c " response=$(echo "$message" | timeout 2 websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1 || echo 'TIMEOUT')
echo '$message' | websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1
" 2>/dev/null || echo 'TIMEOUT')
if [[ "$response" == *"rate limit"* ]] || [[ "$response" == *"too many"* ]] || [[ "$response" == *"TOO_MANY"* ]]; then if [[ "$response" == *"rate limit"* ]] || [[ "$response" == *"too many"* ]] || [[ "$response" == *"TOO_MANY"* ]]; then
rate_limited=true rate_limited=true
@@ -98,9 +96,7 @@ test_sustained_load() {
while [[ $(($(date +%s) - start_time)) -lt duration ]]; do while [[ $(($(date +%s) - start_time)) -lt duration ]]; do
((total_requests++)) ((total_requests++))
local response local response
response=$(timeout 1 bash -c " response=$(echo "$message" | timeout 1 websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1 || echo 'TIMEOUT')
echo '$message' | websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1
" 2>/dev/null || echo 'TIMEOUT')
if [[ "$response" == *"rate limit"* ]] || [[ "$response" == *"too many"* ]] || [[ "$response" == *"TOO_MANY"* ]]; then if [[ "$response" == *"rate limit"* ]] || [[ "$response" == *"too many"* ]] || [[ "$response" == *"TOO_MANY"* ]]; then
rate_limited=true rate_limited=true
@@ -171,22 +167,16 @@ echo -n "Testing subscription churn... "
local churn_test_passed=true local churn_test_passed=true
for i in $(seq 1 25); do for i in $(seq 1 25); do
# Create subscription # Create subscription
timeout 1 bash -c " echo "[\"REQ\",\"churn_${i}_$(date +%s%N)\",{}]" | timeout 1 websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT >/dev/null 2>&1 || true
echo '[\"REQ\",\"churn_'${i}'_'$(date +%s%N)'\",{}]' | websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT >/dev/null 2>&1
" 2>/dev/null || true
# Close subscription # Close subscription
timeout 1 bash -c " echo "[\"CLOSE\",\"churn_${i}_*\"]" | timeout 1 websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT >/dev/null 2>&1 || true
echo '[\"CLOSE\",\"churn_'${i}'_*\"]' | websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT >/dev/null 2>&1
" 2>/dev/null || true
sleep 0.05 sleep 0.05
done done
# Check if relay is still responsive # Check if relay is still responsive
if timeout 2 bash -c " if echo 'ping' | timeout 2 websocat -n1 ws://$RELAY_HOST:$RELAY_PORT >/dev/null 2>&1; then
echo 'ping' | websocat -n1 ws://$RELAY_HOST:$RELAY_PORT >/dev/null 2>&1
" 2>/dev/null; then
echo -e "${GREEN}PASSED${NC} - Subscription churn handled" echo -e "${GREEN}PASSED${NC} - Subscription churn handled"
TOTAL_TESTS=$((TOTAL_TESTS + 1)) TOTAL_TESTS=$((TOTAL_TESTS + 1))
PASSED_TESTS=$((PASSED_TESTS + 1)) PASSED_TESTS=$((PASSED_TESTS + 1))

View File

@@ -211,9 +211,7 @@ run_monitored_load_test() {
# Run a simple load test (create multiple subscriptions) # Run a simple load test (create multiple subscriptions)
echo "Running load test..." echo "Running load test..."
for i in {1..20}; do for i in {1..20}; do
timeout 3 bash -c " echo "[\"REQ\",\"monitor_test_${i}\",{}]" | timeout 3 websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT >/dev/null 2>&1 &
echo '[\"REQ\",\"monitor_test_'${i}'\",{}]' | websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT >/dev/null 2>&1
" 2>/dev/null &
done done
# Let the load run for a bit # Let the load run for a bit
@@ -222,9 +220,7 @@ run_monitored_load_test() {
# Clean up subscriptions # Clean up subscriptions
echo "Cleaning up test subscriptions..." echo "Cleaning up test subscriptions..."
for i in {1..20}; do for i in {1..20}; do
timeout 3 bash -c " echo "[\"CLOSE\",\"monitor_test_${i}\"]" | timeout 3 websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT >/dev/null 2>&1 &
echo '[\"CLOSE\",\"monitor_test_'${i}'\"]' | websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT >/dev/null 2>&1
" 2>/dev/null &
done done
# Wait for monitoring to complete # Wait for monitoring to complete

View File

@@ -112,9 +112,7 @@ check_relay_status() {
fi fi
# Fallback: Try WebSocket connection # Fallback: Try WebSocket connection
if timeout 5 bash -c " if echo '["REQ","status_check",{}]' | timeout 5 websocat -B 1048576 --no-close "$RELAY_URL" >/dev/null 2>&1; then
echo '[\"REQ\",\"status_check\",{}]' | websocat -B 1048576 --no-close '$RELAY_URL' >/dev/null 2>&1
" 2>/dev/null; then
log "${GREEN}✓ Relay WebSocket endpoint is accessible${NC}" log "${GREEN}✓ Relay WebSocket endpoint is accessible${NC}"
return 0 return 0
else else
@@ -236,35 +234,35 @@ OVERALL_START_TIME=$(date +%s)
# Run Security Test Suites # Run Security Test Suites
log "${BLUE}=== SECURITY TEST SUITES ===${NC}" log "${BLUE}=== SECURITY TEST SUITES ===${NC}"
run_test_suite "SQL Injection Tests" "tests/sql_injection_tests.sh" "Comprehensive SQL injection vulnerability testing" run_test_suite "SQL Injection Tests" "sql_injection_tests.sh" "Comprehensive SQL injection vulnerability testing"
run_test_suite "Filter Validation Tests" "tests/filter_validation_test.sh" "Input validation for REQ and COUNT messages" run_test_suite "Filter Validation Tests" "filter_validation_test.sh" "Input validation for REQ and COUNT messages"
run_test_suite "Subscription Validation Tests" "tests/subscription_validation.sh" "Subscription ID and message validation" run_test_suite "Subscription Validation Tests" "subscription_validation.sh" "Subscription ID and message validation"
run_test_suite "Memory Corruption Tests" "tests/memory_corruption_tests.sh" "Buffer overflow and memory safety testing" run_test_suite "Memory Corruption Tests" "memory_corruption_tests.sh" "Buffer overflow and memory safety testing"
run_test_suite "Input Validation Tests" "tests/input_validation_tests.sh" "Comprehensive input boundary testing" run_test_suite "Input Validation Tests" "input_validation_tests.sh" "Comprehensive input boundary testing"
# Run Performance Test Suites # Run Performance Test Suites
log "" log ""
log "${BLUE}=== PERFORMANCE TEST SUITES ===${NC}" log "${BLUE}=== PERFORMANCE TEST SUITES ===${NC}"
run_test_suite "Subscription Limit Tests" "tests/subscription_limits.sh" "Subscription limit enforcement testing" run_test_suite "Subscription Limit Tests" "subscription_limits.sh" "Subscription limit enforcement testing"
run_test_suite "Load Testing" "tests/load_tests.sh" "High concurrent connection testing" run_test_suite "Load Testing" "load_tests.sh" "High concurrent connection testing"
run_test_suite "Stress Testing" "tests/stress_tests.sh" "Resource usage and stability testing" run_test_suite "Stress Testing" "stress_tests.sh" "Resource usage and stability testing"
run_test_suite "Rate Limiting Tests" "tests/rate_limiting_tests.sh" "Rate limiting and abuse prevention" run_test_suite "Rate Limiting Tests" "rate_limiting_tests.sh" "Rate limiting and abuse prevention"
# Run Integration Test Suites # Run Integration Test Suites
log "" log ""
log "${BLUE}=== INTEGRATION TEST SUITES ===${NC}" log "${BLUE}=== INTEGRATION TEST SUITES ===${NC}"
run_test_suite "NIP Protocol Tests" "tests/run_nip_tests.sh" "All NIP protocol compliance tests" run_test_suite "NIP Protocol Tests" "run_nip_tests.sh" "All NIP protocol compliance tests"
run_test_suite "Configuration Tests" "tests/config_tests.sh" "Configuration management and persistence" run_test_suite "Configuration Tests" "config_tests.sh" "Configuration management and persistence"
run_test_suite "Authentication Tests" "tests/auth_tests.sh" "NIP-42 authentication testing" run_test_suite "Authentication Tests" "auth_tests.sh" "NIP-42 authentication testing"
# Run Benchmarking Suites # Run Benchmarking Suites
log "" log ""
log "${BLUE}=== BENCHMARKING SUITES ===${NC}" log "${BLUE}=== BENCHMARKING SUITES ===${NC}"
run_test_suite "Performance Benchmarks" "tests/performance_benchmarks.sh" "Performance metrics and benchmarking" run_test_suite "Performance Benchmarks" "performance_benchmarks.sh" "Performance metrics and benchmarking"
run_test_suite "Resource Monitoring" "tests/resource_monitoring.sh" "Memory and CPU usage monitoring" run_test_suite "Resource Monitoring" "resource_monitoring.sh" "Memory and CPU usage monitoring"
# Calculate total duration # Calculate total duration
OVERALL_END_TIME=$(date +%s) OVERALL_END_TIME=$(date +%s)

View File

@@ -32,27 +32,40 @@ test_sql_injection() {
echo -n "Testing $description... " echo -n "Testing $description... "
# Send message via websocat and capture response # Send message via websocat and capture response
# For now, we'll test without authentication since the relay may not require it for basic queries
local response local response
response=$(timeout 5 bash -c " response=$(echo "$message" | timeout 2 websocat ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1 || echo 'TIMEOUT')
echo '$message' | websocat -B 1048576 --no-close ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -3
" 2>/dev/null || echo 'TIMEOUT')
# Check if the response indicates successful query execution (which would be bad) # Check if the response indicates successful query execution (which would be bad)
# Look for signs that SQL injection worked (like database errors or unexpected results) # Look for signs that SQL injection worked (like database errors or unexpected results)
if [[ "$response" == *"SQL"* ]] || [[ "$response" == *"syntax"* ]] || [[ "$response" == *"error"* && ! "$response" == *"error: "* ]]; then if [[ "$response" == "TIMEOUT" ]]; then
echo -e "${RED}FAILED${NC} - Potential SQL injection vulnerability detected"
echo " Response: $response"
FAILED_TESTS=$((FAILED_TESTS + 1))
return 1
elif [[ "$response" == "TIMEOUT" ]]; then
echo -e "${YELLOW}UNCERTAIN${NC} - Connection timeout (may indicate crash)" echo -e "${YELLOW}UNCERTAIN${NC} - Connection timeout (may indicate crash)"
FAILED_TESTS=$((FAILED_TESTS + 1)) FAILED_TESTS=$((FAILED_TESTS + 1))
return 1 return 1
else elif [[ -z "$response" ]]; then
echo -e "${GREEN}PASSED${NC} - SQL injection blocked" # Empty response - relay silently rejected malformed input
echo -e "${GREEN}PASSED${NC} - SQL injection blocked (silently rejected)"
PASSED_TESTS=$((PASSED_TESTS + 1)) PASSED_TESTS=$((PASSED_TESTS + 1))
return 0 return 0
elif [[ "$response" == *"NOTICE"* ]] && [[ "$response" == *"error:"* ]]; then
# Relay properly rejected the input with a NOTICE error message
echo -e "${GREEN}PASSED${NC} - SQL injection blocked (rejected with error)"
PASSED_TESTS=$((PASSED_TESTS + 1))
return 0
elif [[ "$response" == *"EOSE"* ]] || [[ "$response" == *"COUNT"* ]] || [[ "$response" == *"EVENT"* ]]; then
# Query completed normally - this is expected for properly sanitized input
echo -e "${GREEN}PASSED${NC} - SQL injection blocked (query sanitized)"
PASSED_TESTS=$((PASSED_TESTS + 1))
return 0
elif [[ "$response" == *"SQL"* ]] || [[ "$response" == *"syntax"* ]]; then
# Database error leaked - potential vulnerability
echo -e "${RED}FAILED${NC} - SQL error leaked: $response"
FAILED_TESTS=$((FAILED_TESTS + 1))
return 1
else
# Unknown response
echo -e "${YELLOW}UNCERTAIN${NC} - Unexpected response: $response"
FAILED_TESTS=$((FAILED_TESTS + 1))
return 1
fi fi
} }
@@ -66,9 +79,7 @@ test_valid_query() {
echo -n "Testing $description... " echo -n "Testing $description... "
local response local response
response=$(timeout 5 bash -c " response=$(echo "$message" | timeout 2 websocat ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1 || echo 'TIMEOUT')
echo '$message' | websocat -B 1048576 --no-close ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -3
" 2>/dev/null || echo 'TIMEOUT')
if [[ "$response" == *"EOSE"* ]] || [[ "$response" == *"EVENT"* ]]; then if [[ "$response" == *"EOSE"* ]] || [[ "$response" == *"EVENT"* ]]; then
echo -e "${GREEN}PASSED${NC} - Valid query works" echo -e "${GREEN}PASSED${NC} - Valid query works"
@@ -160,9 +171,10 @@ done
echo echo
echo "=== Kinds Filter SQL Injection Tests ===" echo "=== Kinds Filter SQL Injection Tests ==="
# Test numeric kinds with SQL injection # Test numeric kinds with SQL injection attempts (these will fail JSON parsing, which is expected)
test_sql_injection "Kinds filter with UNION injection" "[\"REQ\",\"sql_test_kinds_$RANDOM\",{\"kinds\":[0 UNION SELECT 1,2,3]}]" test_sql_injection "Kinds filter with string injection" "[\"REQ\",\"sql_test_kinds_$RANDOM\",{\"kinds\":[\"1' OR '1'='1\"]}]"
test_sql_injection "Kinds filter with stacked query" "[\"REQ\",\"sql_test_kinds_$RANDOM\",{\"kinds\":[0; DROP TABLE events; --]}]" test_sql_injection "Kinds filter with negative value" "[\"REQ\",\"sql_test_kinds_$RANDOM\",{\"kinds\":[-1]}]"
test_sql_injection "Kinds filter with very large value" "[\"REQ\",\"sql_test_kinds_$RANDOM\",{\"kinds\":[999999999]}]"
echo echo
echo "=== Search Filter SQL Injection Tests ===" echo "=== Search Filter SQL Injection Tests ==="

View File

@@ -34,24 +34,36 @@ echo "[INFO] Testing subscription limits by creating multiple subscriptions..."
success_count=0 success_count=0
limit_hit=false limit_hit=false
# Create multiple subscriptions in sequence (each in its own connection) # Create multiple subscriptions within a single WebSocket connection
for i in {1..30}; do echo "[INFO] Creating multiple subscriptions within a single connection..."
echo "[INFO] Creating subscription $i..."
sub_id="limit_test_$i_$(date +%s%N)"
response=$(echo "[\"REQ\",\"$sub_id\",{}]" | timeout 5 websocat -n1 "$RELAY_URL" 2>/dev/null || echo "TIMEOUT")
if echo "$response" | grep -q "CLOSED.*$sub_id.*exceeded"; then # Build a sequence of REQ messages
echo "[INFO] Hit subscription limit at subscription $i" req_messages=""
for i in {1..30}; do
sub_id="limit_test_$i"
req_messages="${req_messages}[\"REQ\",\"$sub_id\",{}]\n"
done
# Send all messages through a single websocat connection and save to temp file
temp_file=$(mktemp)
echo -e "$req_messages" | timeout 10 websocat -B 1048576 "$RELAY_URL" 2>/dev/null > "$temp_file" || echo "TIMEOUT" >> "$temp_file"
# Parse the response to check for subscription limit enforcement
subscription_count=0
while read -r line; do
if [[ "$line" == *"CLOSED"* && "$line" == *"exceeded"* ]]; then
echo "[INFO] Hit subscription limit at subscription $((subscription_count + 1))"
limit_hit=true limit_hit=true
break break
elif echo "$response" | grep -q "EOSE\|EVENT"; then elif [[ "$line" == *"EOSE"* ]]; then
((success_count++)) subscription_count=$((subscription_count + 1))
else
echo "[WARN] Unexpected response for subscription $i: $response"
fi fi
done < "$temp_file"
sleep 0.1 success_count=$subscription_count
done
# Clean up temp file
rm -f "$temp_file"
if [ "$limit_hit" = true ]; then if [ "$limit_hit" = true ]; then
echo "[PASS] Subscription limit enforcement working (limit hit after $success_count subscriptions)" echo "[PASS] Subscription limit enforcement working (limit hit after $success_count subscriptions)"

View File

@@ -0,0 +1,18 @@
2025-10-11 13:46:17 - ==========================================
2025-10-11 13:46:17 - C-Relay Comprehensive Test Suite Runner
2025-10-11 13:46:17 - ==========================================
2025-10-11 13:46:17 - Relay URL: ws://127.0.0.1:8888
2025-10-11 13:46:17 - Log file: test_results_20251011_134617.log
2025-10-11 13:46:17 - Report file: test_report_20251011_134617.html
2025-10-11 13:46:17 -
2025-10-11 13:46:17 - Checking relay status at ws://127.0.0.1:8888...
2025-10-11 13:46:17 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m
2025-10-11 13:46:17 -
2025-10-11 13:46:17 - Starting comprehensive test execution...
2025-10-11 13:46:17 -
2025-10-11 13:46:17 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m
2025-10-11 13:46:17 - ==========================================
2025-10-11 13:46:17 - Running Test Suite: SQL Injection Tests
2025-10-11 13:46:17 - Description: Comprehensive SQL injection vulnerability testing
2025-10-11 13:46:17 - ==========================================
2025-10-11 13:46:17 - \033[0;31mERROR: Test script tests/sql_injection_tests.sh not found\033[0m

View File

@@ -0,0 +1,629 @@
2025-10-11 13:48:07 - ==========================================
2025-10-11 13:48:07 - C-Relay Comprehensive Test Suite Runner
2025-10-11 13:48:07 - ==========================================
2025-10-11 13:48:07 - Relay URL: ws://127.0.0.1:8888
2025-10-11 13:48:07 - Log file: test_results_20251011_134807.log
2025-10-11 13:48:07 - Report file: test_report_20251011_134807.html
2025-10-11 13:48:07 -
2025-10-11 13:48:07 - Checking relay status at ws://127.0.0.1:8888...
2025-10-11 13:48:07 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m
2025-10-11 13:48:07 -
2025-10-11 13:48:07 - Starting comprehensive test execution...
2025-10-11 13:48:07 -
2025-10-11 13:48:07 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m
2025-10-11 13:48:07 - ==========================================
2025-10-11 13:48:07 - Running Test Suite: SQL Injection Tests
2025-10-11 13:48:07 - Description: Comprehensive SQL injection vulnerability testing
2025-10-11 13:48:07 - ==========================================
==========================================
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.
2025-10-11 13:48:30 - \033[0;32m✓ SQL Injection Tests PASSED\033[0m (Duration: 23s)
2025-10-11 13:48:30 - ==========================================
2025-10-11 13:48:30 - Running Test Suite: Filter Validation Tests
2025-10-11 13:48:30 - Description: Input validation for REQ and COUNT messages
2025-10-11 13:48:30 - ==========================================
=== 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!
2025-10-11 13:48:35 - \033[0;32m✓ Filter Validation Tests PASSED\033[0m (Duration: 5s)
2025-10-11 13:48:35 - ==========================================
2025-10-11 13:48:35 - Running Test Suite: Subscription Validation Tests
2025-10-11 13:48:35 - Description: Subscription ID and message validation
2025-10-11 13:48:35 - ==========================================
Testing subscription ID validation fixes...
Testing malformed subscription IDs...
Valid ID test: Success
Testing CLOSE message validation...
CLOSE valid ID test: Success
Subscription validation tests completed.
2025-10-11 13:48:36 - \033[0;32m✓ Subscription Validation Tests PASSED\033[0m (Duration: 1s)
2025-10-11 13:48:36 - ==========================================
2025-10-11 13:48:36 - Running Test Suite: Memory Corruption Tests
2025-10-11 13:48:36 - Description: Buffer overflow and memory safety testing
2025-10-11 13:48:36 - ==========================================
==========================================
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_1760204917502714788", { "id": "b3a2a79b768c304a8ad315a97319e3c6fd9d521844fc9f1e4228c75c453dd882", "pubkey": "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", "created_at": 1760196143, "kind": 30001, "content": "Updated addressable event", "sig": "795671a831de31fbbdd6282585529f274f61bb6e8c974e597560d70989355f24c8ecfe70caf043e8fbc24ce65d9b0d562297c682af958cfcdd2ee137dd9bccb4", "tags": [["d", "test-article"], ["type", "addressable"], ["updated", "true"]] }]
["EVENT", "concurrent_1760204917502714788", { "id": "b3a2a79b768c304a8ad315a97319e3c6fd9d521844fc9f1e4228c75c453dd882", "pubkey": "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", "created_at": 1760196143, "kind": 30001, "content": "Updated addressable event", "sig": "795671a831de31fbbdd6282585529f274f61bb6e8c974e597560d70989355f24c8ecfe70caf043e8fbc24ce65d9b0d562297c682af958cfcdd2ee137dd9bccb4", "tags": [["d", "test-article"], ["type", "addressable"], ["updated", "true"]] }]
["EVENT", "concurrent_1760204917502714788", { "id": "b3a2a79b768c304a8ad315a97319e3c6fd9d521844fc9f1e4228c75c453dd882", "pubkey": "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", "created_at": 1760196143, "kind": 30001, "content": "Updated addressable event", "sig": "795671a831de31fbbdd6282585529f274f61bb6e8c974e597560d70989355f24c8ecfe70caf043e8fbc24ce65d9b0d562297c682af958cfcdd2ee137dd9bccb4", "tags": [["d", "test-article"], ["type", "addressable"], ["updated", "true"]] }]
["EVENT", "concurrent_1760204917502714788", { "id": "b3a2a79b768c304a8ad315a97319e3c6fd9d521844fc9f1e4228c75c453dd882", "pubkey": "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", "created_at": 1760196143, "kind": 30001, "content": "Updated addressable event", "sig": "795671a831de31fbbdd6282585529f274f61bb6e8c974e597560d70989355f24c8ecfe70caf043e8fbc24ce65d9b0d562297c682af958cfcdd2ee137dd9bccb4", "tags": [["d", "test-article"], ["type", "addressable"], ["updated", "true"]] }]
["EVENT", "concurrent_1760204917502714788", { "id": "b3a2a79b768c304a8ad315a97319e3c6fd9d521844fc9f1e4228c75c453dd882", "pubkey": "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", "created_at": 1760196143, "kind": 30001, "content": "Updated addressable event", "sig": "795671a831de31fbbdd6282585529f274f61bb6e8c974e597560d70989355f24c8ecfe70caf043e8fbc24ce65d9b0d562297c682af958cfcdd2ee137dd9bccb4", "tags": [["d", "test-article"], ["type", "addressable"], ["updated", "true"]] }]
["EVENT", "concurrent_1760204917502714788", { "id": "b3a2a79b768c304a8ad315a97319e3c6fd9d521844fc9f1e4228c75c453dd882", "pubkey": "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", "created_at": 1760196143, "kind": 30001, "content": "Updated addressable event", "sig": "795671a831de31fbbdd6282585529f274f61bb6e8c974e597560d70989355f24c8ecfe70caf043e8fbc24ce65d9b0d562297c682af958cfcdd2ee137dd9bccb4", "tags": [["d", "test-article"], ["type", "addressable"], ["updated", "true"]] }]
["EVENT", "concurrent_1760204917502714788", { "id": "b3a2a79b768c304a8ad315a97319e3c6fd9d521844fc9f1e4228c75c453dd882", "pubkey": "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", "created_at": 1760196143, "kind": 30001, "content": "Updated addressable event", "sig": "795671a831de31fbbdd6282585529f274f61bb6e8c974e597560d70989355f24c8ecfe70caf043e8fbc24ce65d9b0d562297c682af958cfcdd2ee137dd9bccb4", "tags": [["d", "test-article"], ["type", "addressable"], ["updated", "true"]] }]
["EVENT", "concurrent_1760204917502714788", { "id": "b3a2a79b768c304a8ad315a97319e3c6fd9d521844fc9f1e4228c75c453dd882", "pubkey": "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", "created_at": 1760196143, "kind": 30001, "content": "Updated addressable event", "sig": "795671a831de31fbbdd6282585529f274f61bb6e8c974e597560d70989355f24c8ecfe70caf043e8fbc24ce65d9b0d562297c682af958cfcdd2ee137dd9bccb4", "tags": [["d", "test-article"], ["type", "addressable"], ["updated", "true"]] }]
["EVENT", "concurrent_1760204917502714788", { "id": "b3a2a79b768c304a8ad315a97319e3c6fd9d521844fc9f1e4228c75c453dd882", "pubkey": "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", "created_at": 1760196143, "kind": 30001, "content": "Updated addressable event", "sig": "795671a831de31fbbdd6282585529f274f61bb6e8c974e597560d70989355f24c8ecfe70caf043e8fbc24ce65d9b0d562297c682af958cfcdd2ee137dd9bccb4", "tags": [["d", "test-article"], ["type", "addressable"], ["updated", "true"]] }]
["EVENT", "concurrent_1760204917502714788", { "id": "b3a2a79b768c304a8ad315a97319e3c6fd9d521844fc9f1e4228c75c453dd882", "pubkey": "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", "created_at": 1760196143, "kind": 30001, "content": "Updated addressable event", "sig": "795671a831de31fbbdd6282585529f274f61bb6e8c974e597560d70989355f24c8ecfe70caf043e8fbc24ce65d9b0d562297c682af958cfcdd2ee137dd9bccb4", "tags": [["d", "test-article"], ["type", "addressable"], ["updated", "true"]] }]
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.
2025-10-11 13:48:38 - \033[0;32m✓ Memory Corruption Tests PASSED\033[0m (Duration: 2s)
2025-10-11 13:48:38 - ==========================================
2025-10-11 13:48:38 - Running Test Suite: Input Validation Tests
2025-10-11 13:48:38 - Description: Comprehensive input boundary testing
2025-10-11 13:48:38 - ==========================================
==========================================
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.
2025-10-11 13:48:42 - \033[0;32m✓ Input Validation Tests PASSED\033[0m (Duration: 4s)
2025-10-11 13:48:42 -
2025-10-11 13:48:42 - \033[0;34m=== PERFORMANCE TEST SUITES ===\033[0m
2025-10-11 13:48:42 - ==========================================
2025-10-11 13:48:42 - Running Test Suite: Subscription Limit Tests
2025-10-11 13:48:42 - Description: Subscription limit enforcement testing
2025-10-11 13:48:42 - ==========================================
=== 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 26
[PASS] Subscription limit enforcement working (limit hit after 25 subscriptions)
=== Test Complete ===
2025-10-11 13:48:42 - \033[0;32m✓ Subscription Limit Tests PASSED\033[0m (Duration: 0s)
2025-10-11 13:48:42 - ==========================================
2025-10-11 13:48:42 - Running Test Suite: Load Testing
2025-10-11 13:48:42 - Description: High concurrent connection testing
2025-10-11 13:48:42 - ==========================================
==========================================
C-Relay Load Testing Suite
==========================================
Testing against relay at ws://127.0.0.1:8888
=== Basic Connectivity Test ===
✗ Cannot connect to relay. Aborting tests.
2025-10-11 13:48:47 - \033[0;31m✗ Load Testing FAILED\033[0m (Duration: 5s)

View File

@@ -0,0 +1,728 @@
2025-10-11 14:11:34 - ==========================================
2025-10-11 14:11:34 - C-Relay Comprehensive Test Suite Runner
2025-10-11 14:11:34 - ==========================================
2025-10-11 14:11:34 - Relay URL: ws://127.0.0.1:8888
2025-10-11 14:11:34 - Log file: test_results_20251011_141134.log
2025-10-11 14:11:34 - Report file: test_report_20251011_141134.html
2025-10-11 14:11:34 -
2025-10-11 14:11:34 - Checking relay status at ws://127.0.0.1:8888...
2025-10-11 14:11:34 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m
2025-10-11 14:11:34 -
2025-10-11 14:11:34 - Starting comprehensive test execution...
2025-10-11 14:11:34 -
2025-10-11 14:11:34 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m
2025-10-11 14:11:34 - ==========================================
2025-10-11 14:11:34 - Running Test Suite: SQL Injection Tests
2025-10-11 14:11:34 - Description: Comprehensive SQL injection vulnerability testing
2025-10-11 14:11:34 - ==========================================
==========================================
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.
2025-10-11 14:11:56 - \033[0;32m✓ SQL Injection Tests PASSED\033[0m (Duration: 22s)
2025-10-11 14:11:56 - ==========================================
2025-10-11 14:11:56 - Running Test Suite: Filter Validation Tests
2025-10-11 14:11:56 - Description: Input validation for REQ and COUNT messages
2025-10-11 14:11:56 - ==========================================
=== 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!
2025-10-11 14:12:02 - \033[0;32m✓ Filter Validation Tests PASSED\033[0m (Duration: 6s)
2025-10-11 14:12:02 - ==========================================
2025-10-11 14:12:02 - Running Test Suite: Subscription Validation Tests
2025-10-11 14:12:02 - Description: Subscription ID and message validation
2025-10-11 14:12:02 - ==========================================
Testing subscription ID validation fixes...
Testing malformed subscription IDs...
Valid ID test: Success
Testing CLOSE message validation...
CLOSE valid ID test: Success
Subscription validation tests completed.
2025-10-11 14:12:02 - \033[0;32m✓ Subscription Validation Tests PASSED\033[0m (Duration: 0s)
2025-10-11 14:12:02 - ==========================================
2025-10-11 14:12:02 - Running Test Suite: Memory Corruption Tests
2025-10-11 14:12:02 - Description: Buffer overflow and memory safety testing
2025-10-11 14:12:02 - ==========================================
==========================================
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_1760206323991056473", { "id": "b3a2a79b768c304a8ad315a97319e3c6fd9d521844fc9f1e4228c75c453dd882", "pubkey": "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", "created_at": 1760196143, "kind": 30001, "content": "Updated addressable event", "sig": "795671a831de31fbbdd6282585529f274f61bb6e8c974e597560d70989355f24c8ecfe70caf043e8fbc24ce65d9b0d562297c682af958cfcdd2ee137dd9bccb4", "tags": [["d", "test-article"], ["type", "addressable"], ["updated", "true"]] }]
["EVENT", "concurrent_1760206323991056473", { "id": "b3a2a79b768c304a8ad315a97319e3c6fd9d521844fc9f1e4228c75c453dd882", "pubkey": "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", "created_at": 1760196143, "kind": 30001, "content": "Updated addressable event", "sig": "795671a831de31fbbdd6282585529f274f61bb6e8c974e597560d70989355f24c8ecfe70caf043e8fbc24ce65d9b0d562297c682af958cfcdd2ee137dd9bccb4", "tags": [["d", "test-article"], ["type", "addressable"], ["updated", "true"]] }]
["EVENT", "concurrent_1760206323991056473", { "id": "b3a2a79b768c304a8ad315a97319e3c6fd9d521844fc9f1e4228c75c453dd882", "pubkey": "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", "created_at": 1760196143, "kind": 30001, "content": "Updated addressable event", "sig": "795671a831de31fbbdd6282585529f274f61bb6e8c974e597560d70989355f24c8ecfe70caf043e8fbc24ce65d9b0d562297c682af958cfcdd2ee137dd9bccb4", "tags": [["d", "test-article"], ["type", "addressable"], ["updated", "true"]] }]
["EVENT", "concurrent_1760206323991056473", { "id": "b3a2a79b768c304a8ad315a97319e3c6fd9d521844fc9f1e4228c75c453dd882", "pubkey": "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", "created_at": 1760196143, "kind": 30001, "content": "Updated addressable event", "sig": "795671a831de31fbbdd6282585529f274f61bb6e8c974e597560d70989355f24c8ecfe70caf043e8fbc24ce65d9b0d562297c682af958cfcdd2ee137dd9bccb4", "tags": [["d", "test-article"], ["type", "addressable"], ["updated", "true"]] }]
["EVENT", "concurrent_1760206323991056473", { "id": "b3a2a79b768c304a8ad315a97319e3c6fd9d521844fc9f1e4228c75c453dd882", "pubkey": "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", "created_at": 1760196143, "kind": 30001, "content": "Updated addressable event", "sig": "795671a831de31fbbdd6282585529f274f61bb6e8c974e597560d70989355f24c8ecfe70caf043e8fbc24ce65d9b0d562297c682af958cfcdd2ee137dd9bccb4", "tags": [["d", "test-article"], ["type", "addressable"], ["updated", "true"]] }]
["EVENT", "concurrent_1760206323991056473", { "id": "b3a2a79b768c304a8ad315a97319e3c6fd9d521844fc9f1e4228c75c453dd882", "pubkey": "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", "created_at": 1760196143, "kind": 30001, "content": "Updated addressable event", "sig": "795671a831de31fbbdd6282585529f274f61bb6e8c974e597560d70989355f24c8ecfe70caf043e8fbc24ce65d9b0d562297c682af958cfcdd2ee137dd9bccb4", "tags": [["d", "test-article"], ["type", "addressable"], ["updated", "true"]] }]
["EVENT", "concurrent_1760206323991056473", { "id": "b3a2a79b768c304a8ad315a97319e3c6fd9d521844fc9f1e4228c75c453dd882", "pubkey": "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", "created_at": 1760196143, "kind": 30001, "content": "Updated addressable event", "sig": "795671a831de31fbbdd6282585529f274f61bb6e8c974e597560d70989355f24c8ecfe70caf043e8fbc24ce65d9b0d562297c682af958cfcdd2ee137dd9bccb4", "tags": [["d", "test-article"], ["type", "addressable"], ["updated", "true"]] }]
["EVENT", "concurrent_1760206323991056473", { "id": "b3a2a79b768c304a8ad315a97319e3c6fd9d521844fc9f1e4228c75c453dd882", "pubkey": "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", "created_at": 1760196143, "kind": 30001, "content": "Updated addressable event", "sig": "795671a831de31fbbdd6282585529f274f61bb6e8c974e597560d70989355f24c8ecfe70caf043e8fbc24ce65d9b0d562297c682af958cfcdd2ee137dd9bccb4", "tags": [["d", "test-article"], ["type", "addressable"], ["updated", "true"]] }]
["EVENT", "concurrent_1760206323991056473", { "id": "b3a2a79b768c304a8ad315a97319e3c6fd9d521844fc9f1e4228c75c453dd882", "pubkey": "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", "created_at": 1760196143, "kind": 30001, "content": "Updated addressable event", "sig": "795671a831de31fbbdd6282585529f274f61bb6e8c974e597560d70989355f24c8ecfe70caf043e8fbc24ce65d9b0d562297c682af958cfcdd2ee137dd9bccb4", "tags": [["d", "test-article"], ["type", "addressable"], ["updated", "true"]] }]
["EVENT", "concurrent_1760206323991056473", { "id": "b3a2a79b768c304a8ad315a97319e3c6fd9d521844fc9f1e4228c75c453dd882", "pubkey": "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", "created_at": 1760196143, "kind": 30001, "content": "Updated addressable event", "sig": "795671a831de31fbbdd6282585529f274f61bb6e8c974e597560d70989355f24c8ecfe70caf043e8fbc24ce65d9b0d562297c682af958cfcdd2ee137dd9bccb4", "tags": [["d", "test-article"], ["type", "addressable"], ["updated", "true"]] }]
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.
2025-10-11 14:12:05 - \033[0;32m✓ Memory Corruption Tests PASSED\033[0m (Duration: 3s)
2025-10-11 14:12:05 - ==========================================
2025-10-11 14:12:05 - Running Test Suite: Input Validation Tests
2025-10-11 14:12:05 - Description: Comprehensive input boundary testing
2025-10-11 14:12:05 - ==========================================
==========================================
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.
2025-10-11 14:12:08 - \033[0;32m✓ Input Validation Tests PASSED\033[0m (Duration: 3s)
2025-10-11 14:12:08 -
2025-10-11 14:12:08 - \033[0;34m=== PERFORMANCE TEST SUITES ===\033[0m
2025-10-11 14:12:08 - ==========================================
2025-10-11 14:12:08 - Running Test Suite: Subscription Limit Tests
2025-10-11 14:12:08 - Description: Subscription limit enforcement testing
2025-10-11 14:12:08 - ==========================================
=== 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 26
[PASS] Subscription limit enforcement working (limit hit after 25 subscriptions)
=== Test Complete ===
2025-10-11 14:12:09 - \033[0;32m✓ Subscription Limit Tests PASSED\033[0m (Duration: 1s)
2025-10-11 14:12:09 - ==========================================
2025-10-11 14:12:09 - Running Test Suite: Load Testing
2025-10-11 14:12:09 - Description: High concurrent connection testing
2025-10-11 14:12:09 - ==========================================
==========================================
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: 260
✓ 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: 3s
Total connections attempted: 35
Successful connections: 25
Failed connections: 0
Connection success rate: 71%
Messages expected: 250
Messages sent: 250
Messages received: 1275
✗ 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: 13s
Total connections attempted: 85
Successful connections: 50
Failed connections: 0
Connection success rate: 58%
Messages expected: 1000
Messages sent: 1000
Messages received: 5050
✗ 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: 63s
Total connections attempted: 185
Successful connections: 100
Failed connections: 0
Connection success rate: 54%
Messages expected: 5000
Messages sent: 5000
Messages received: 15100
✗ 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.
2025-10-11 14:13:31 - \033[0;32m✓ Load Testing PASSED\033[0m (Duration: 82s)
2025-10-11 14:13:31 - ==========================================
2025-10-11 14:13:31 - Running Test Suite: Stress Testing
2025-10-11 14:13:31 - Description: Resource usage and stability testing
2025-10-11 14:13:31 - ==========================================
2025-10-11 14:13:31 - \033[0;31mERROR: Test script stress_tests.sh not found\033[0m