Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b248b740f | ||
|
|
0f77aeb72b | ||
|
|
bb64651a0c | ||
|
|
17be9c2b03 | ||
|
|
f396c622b6 | ||
|
|
cbd260c1ae | ||
|
|
bc9ac290ab | ||
|
|
192eeb248d | ||
|
|
da6c505420 | ||
|
|
c077c209e5 | ||
|
|
9bf02702c2 | ||
|
|
0920cc092d | ||
|
|
a89460be5d | ||
|
|
ff2a3aa335 | ||
|
|
94b61e8a7c | ||
|
|
b0c0754e83 | ||
|
|
3265e3d114 | ||
|
|
927659ece1 | ||
|
|
b6ff4150b4 | ||
|
|
63bc526163 | ||
|
|
a1f712236a | ||
|
|
06e6c17b7b | ||
|
|
0751a7c55c | ||
|
|
f1728932a9 | ||
|
|
ef8bdef2a8 | ||
|
|
c11a8ba292 | ||
|
|
0f124fe575 | ||
|
|
6b20452fab | ||
|
|
10c19bc243 | ||
|
|
3d7aa2196f | ||
|
|
a416c3f275 | ||
|
|
bba9baabc3 | ||
|
|
31187c4c4f | ||
|
|
55f862b879 | ||
|
|
76c9b3fcf0 | ||
|
|
929bd09164 | ||
|
|
d17f1dd8d5 | ||
|
|
207a949835 | ||
|
|
d08f4e4221 | ||
|
|
c96736fa6a | ||
|
|
f8ec4ae924 | ||
|
|
fe7304ac7f | ||
|
|
e5d39c984b | ||
|
|
5e45f21e35 | ||
|
|
5321a238b8 | ||
|
|
083bc14972 | ||
|
|
11aaccba9b | ||
|
|
bd1bbd763d | ||
|
|
2bd7aa5a10 | ||
|
|
361912ec85 | ||
|
|
0de491382e | ||
|
|
3148bbbee7 | ||
|
|
3965ba04d8 | ||
|
|
b96af938bd | ||
|
|
89c8248013 | ||
|
|
d8f477c6cf | ||
|
|
040eeadb13 | ||
|
|
83f8b0ab88 | ||
|
|
b82f7eaaf3 | ||
|
|
0dc5b75d7c | ||
|
|
e94b1a81e3 |
Executable
BIN
Binary file not shown.
+12
-15
@@ -28,7 +28,8 @@ RUN apk add --no-cache \
|
||||
sqlite-static \
|
||||
linux-headers \
|
||||
wget \
|
||||
bash
|
||||
bash \
|
||||
openssh-client
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /build
|
||||
@@ -68,15 +69,8 @@ RUN cd /tmp && \
|
||||
make install && \
|
||||
rm -rf /tmp/libwebsockets
|
||||
|
||||
# Copy only submodule configuration and git directory
|
||||
COPY .gitmodules /build/.gitmodules
|
||||
COPY .git /build/.git
|
||||
|
||||
# Clean up any stale submodule references (nips directory is not a submodule)
|
||||
RUN git rm --cached nips 2>/dev/null || true
|
||||
|
||||
# Initialize submodules (cached unless .gitmodules changes)
|
||||
RUN git submodule update --init --recursive
|
||||
# Submodules are provided in the local build context and copied explicitly below.
|
||||
# Avoid network/SSH dependency inside Docker image build.
|
||||
|
||||
# Copy nostr_core_lib source files (cached unless nostr_core_lib changes)
|
||||
COPY nostr_core_lib /build/nostr_core_lib/
|
||||
@@ -84,11 +78,13 @@ COPY nostr_core_lib /build/nostr_core_lib/
|
||||
# Copy c_utils_lib source files (cached unless c_utils_lib changes)
|
||||
COPY c_utils_lib /build/c_utils_lib/
|
||||
|
||||
# Build c_utils_lib with MUSL-compatible flags (cached unless c_utils_lib changes)
|
||||
# Build c_utils_lib static library for relay logging utilities
|
||||
# (build only debug.c to avoid broken/missing version header surface in submodule revision)
|
||||
RUN cd c_utils_lib && \
|
||||
sed -i 's/CFLAGS = -Wall -Wextra -std=c99 -O2 -g/CFLAGS = -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -std=c99 -O2 -g/' Makefile && \
|
||||
make clean && \
|
||||
make
|
||||
mkdir -p build && \
|
||||
gcc -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -std=c99 -O2 -g \
|
||||
-Isrc -Iinclude -c src/debug.c -o build/debug.o && \
|
||||
ar rcs libc_utils.a build/debug.o
|
||||
|
||||
# Build nostr_core_lib with required NIPs (cached unless nostr_core_lib changes)
|
||||
# Disable fortification in build.sh to prevent __*_chk symbol issues
|
||||
@@ -121,7 +117,8 @@ RUN if [ "$DEBUG_BUILD" = "true" ]; then \
|
||||
-Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket \
|
||||
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 \
|
||||
src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c src/ip_ban.c \
|
||||
src/db_ops.c src/thread_pool.c \
|
||||
-o /build/c_relay_static \
|
||||
c_utils_lib/libc_utils.a \
|
||||
nostr_core_lib/libnostr_core_x64.a \
|
||||
|
||||
@@ -9,7 +9,7 @@ LIBS = -lsqlite3 -lwebsockets -lz -ldl -lpthread -lm -L/usr/local/lib -lsecp256k
|
||||
BUILD_DIR = build
|
||||
|
||||
# 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/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 src/ip_ban.c src/db_ops.c src/thread_pool.c
|
||||
NOSTR_CORE_LIB = nostr_core_lib/libnostr_core_x64.a
|
||||
C_UTILS_LIB = c_utils_lib/libc_utils.a
|
||||
|
||||
|
||||
@@ -1486,3 +1486,16 @@ body.dark-mode .admin-verification-content {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
||||
/* ================================
|
||||
IP BANS TABLE - compact rows
|
||||
================================ */
|
||||
#ip-bans-table td, #ip-bans-table th {
|
||||
padding: 4px 8px;
|
||||
line-height: 1.3;
|
||||
font-size: 13px;
|
||||
}
|
||||
#ip-bans-table button {
|
||||
padding: 2px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
+118
-6
@@ -16,6 +16,7 @@
|
||||
<li><button class="nav-item" data-page="subscriptions">Subscriptions</button></li>
|
||||
<li><button class="nav-item" data-page="configuration">Configuration</button></li>
|
||||
<li><button class="nav-item" data-page="authorization">Authorization</button></li>
|
||||
<li><button class="nav-item" data-page="ip-bans">IP BANS</button></li>
|
||||
<li><button class="nav-item" data-page="relay-events">Relay Events</button></li>
|
||||
<li><button class="nav-item" data-page="dm">DM</button></li>
|
||||
<li><button class="nav-item" data-page="database">Database Query</button></li>
|
||||
@@ -111,6 +112,10 @@
|
||||
<td>Process ID</td>
|
||||
<td id="process-id">-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>WebSocket Connections</td>
|
||||
<td id="websocket-connections">-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Active Subscriptions</td>
|
||||
<td id="active-subscriptions">-</td>
|
||||
@@ -119,14 +124,14 @@
|
||||
<td>Memory Usage</td>
|
||||
<td id="memory-usage">-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CPU Core</td>
|
||||
<td id="cpu-core">-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CPU Usage</td>
|
||||
<td id="cpu-usage">-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CPU Core</td>
|
||||
<td id="cpu-core">-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Oldest Event</td>
|
||||
<td id="oldest-event">-</td>
|
||||
@@ -288,8 +293,8 @@ AUTH RULES MANAGEMENT
|
||||
<!-- Auth Rule Input Section -->
|
||||
<div id="authRuleInputSections" style="display: block;">
|
||||
<div class="input-group">
|
||||
<label for="authRulePubkey">Pubkey (nsec or hex):</label>
|
||||
<input type="text" id="authRulePubkey" placeholder="nsec1... or 64-character hex pubkey">
|
||||
<label for="authRulePubkey">Public Key (npub or hex):</label>
|
||||
<input type="text" id="authRulePubkey" placeholder="npub1... or 64-character hex pubkey">
|
||||
</div>
|
||||
<div id="whitelistWarning" class="warning-box" style="display: none;">
|
||||
<strong>⚠️ WARNING:</strong> Adding whitelist rules changes relay behavior to whitelist-only
|
||||
@@ -382,6 +387,109 @@ WEB OF TRUST
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IP BANS Section -->
|
||||
<div class="section" id="ipBansSection" style="display: none;">
|
||||
<div class="section-header">
|
||||
IP BAN MANAGEMENT
|
||||
</div>
|
||||
|
||||
<!-- Statistics Cards -->
|
||||
<div class="input-group">
|
||||
<div class="config-table-container">
|
||||
<table class="config-table" id="ip-bans-stats-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Total IPs Tracked</th>
|
||||
<th>Currently Banned</th>
|
||||
<th>Total Bans Issued</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td id="ip-bans-total">-</td>
|
||||
<td id="ip-bans-active">-</td>
|
||||
<td id="ip-bans-issued">-</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Ban Form -->
|
||||
<div class="input-group">
|
||||
<h3>Manually Ban IP Address</h3>
|
||||
<div class="form-group">
|
||||
<label for="ban-ip-input">IP Address:</label>
|
||||
<input type="text" id="ban-ip-input" placeholder="192.168.1.100">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ban-duration-select">Ban Duration:</label>
|
||||
<select id="ban-duration-select">
|
||||
<option value="3600">1 Hour</option>
|
||||
<option value="86400" selected>24 Hours</option>
|
||||
<option value="604800">7 Days</option>
|
||||
<option value="2592000">30 Days</option>
|
||||
<option value="31536000">1 Year</option>
|
||||
<option value="999999999">Permanent</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="inline-buttons">
|
||||
<button type="button" id="add-ban-btn">BAN IP</button>
|
||||
</div>
|
||||
<div id="add-ban-status" class="status-message"></div>
|
||||
</div>
|
||||
|
||||
<!-- Whitelist Management -->
|
||||
<div class="input-group">
|
||||
<h3>IP Whitelist (Never Banned)</h3>
|
||||
<p style="font-size:13px;opacity:0.8;">IPs in this list are never idle-banned. Comma-separated.</p>
|
||||
<div class="form-group">
|
||||
<label for="whitelist-ip-input">Add IP to Whitelist:</label>
|
||||
<input type="text" id="whitelist-ip-input" placeholder="103.81.231.220">
|
||||
</div>
|
||||
<div class="inline-buttons">
|
||||
<button type="button" id="add-whitelist-btn">ADD TO WHITELIST</button>
|
||||
</div>
|
||||
<div id="whitelist-status" class="status-message"></div>
|
||||
<div id="whitelist-current" style="margin-top:8px;font-size:13px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Controls -->
|
||||
<div class="input-group">
|
||||
<div class="inline-buttons">
|
||||
<button type="button" id="ip-ban-filter-all" class="active">All IPs</button>
|
||||
<button type="button" id="ip-ban-filter-banned">Currently Banned</button>
|
||||
<button type="button" id="ip-ban-filter-expired">Expired</button>
|
||||
<button type="button" id="refresh-ip-bans-btn">REFRESH</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IP Bans List -->
|
||||
<div class="input-group">
|
||||
<label>Banned IP Addresses:</label>
|
||||
<div class="config-table-container">
|
||||
<table class="config-table" id="ip-bans-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<th>Status</th>
|
||||
<th>Banned Until</th>
|
||||
<th>Failures</th>
|
||||
<th>Authed Successfully</th>
|
||||
<th>Connection Attempts</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="ip-bans-tbody">
|
||||
<tr>
|
||||
<td colspan="7" style="text-align: center;">Click REFRESH to load IP bans</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RELAY EVENTS Section -->
|
||||
<div class="section" id="relayEventsSection" style="display: none;">
|
||||
<div class="section-header">
|
||||
@@ -467,6 +575,10 @@ WEB OF TRUST
|
||||
<option value="event_kinds">Event Kinds Distribution</option>
|
||||
<option value="time_stats">Time-based Statistics</option>
|
||||
</optgroup>
|
||||
<optgroup label="IP Ban Queries">
|
||||
<option value="banned_ips_summary">Banned IPs Summary</option>
|
||||
<option value="banned_ips_list">All Banned IP Addresses</option>
|
||||
</optgroup>
|
||||
<optgroup label="Query History" id="history-group">
|
||||
<!-- Dynamically populated from localStorage -->
|
||||
</optgroup>
|
||||
|
||||
+678
-113
File diff suppressed because it is too large
Load Diff
+95
-1
@@ -3297,8 +3297,18 @@ var NostrTools = (() => {
|
||||
this.enablePing = opts.enablePing;
|
||||
}
|
||||
async ensureRelay(url, params) {
|
||||
const rawUrl = url;
|
||||
const debugEnabled = typeof window !== "undefined" && !!window.__SIMPLE_POOL_DEBUG__;
|
||||
url = normalizeURL(url);
|
||||
let relay = this.relays.get(url);
|
||||
if (debugEnabled) {
|
||||
console.log("🔎 SIMPLE_POOL ensureRelay START", {
|
||||
rawUrl,
|
||||
normalizedUrl: url,
|
||||
hadRelay: !!relay,
|
||||
relayMapKeysBefore: Array.from(this.relays.keys())
|
||||
});
|
||||
}
|
||||
if (!relay) {
|
||||
relay = new AbstractRelay(url, {
|
||||
verifyEvent: this.trustedRelayURLs.has(url) ? alwaysTrue : this.verifyEvent,
|
||||
@@ -3306,13 +3316,38 @@ var NostrTools = (() => {
|
||||
enablePing: this.enablePing
|
||||
});
|
||||
relay.onclose = () => {
|
||||
if (debugEnabled) {
|
||||
console.log("🔎 SIMPLE_POOL ensureRelay onclose", {
|
||||
normalizedUrl: url,
|
||||
relayMapKeysBeforeDelete: Array.from(this.relays.keys())
|
||||
});
|
||||
}
|
||||
this.relays.delete(url);
|
||||
if (debugEnabled) {
|
||||
console.log("🔎 SIMPLE_POOL ensureRelay onclose complete", {
|
||||
normalizedUrl: url,
|
||||
relayMapKeysAfterDelete: Array.from(this.relays.keys())
|
||||
});
|
||||
}
|
||||
};
|
||||
if (params?.connectionTimeout)
|
||||
relay.connectionTimeout = params.connectionTimeout;
|
||||
this.relays.set(url, relay);
|
||||
if (debugEnabled) {
|
||||
console.log("🔎 SIMPLE_POOL ensureRelay created relay", {
|
||||
normalizedUrl: url,
|
||||
relayMapKeysAfterCreate: Array.from(this.relays.keys())
|
||||
});
|
||||
}
|
||||
}
|
||||
await relay.connect();
|
||||
if (debugEnabled) {
|
||||
console.log("🔎 SIMPLE_POOL ensureRelay connected", {
|
||||
normalizedUrl: url,
|
||||
relayConnected: relay.connected,
|
||||
relayMapKeysAfterConnect: Array.from(this.relays.keys())
|
||||
});
|
||||
}
|
||||
return relay;
|
||||
}
|
||||
close(relays) {
|
||||
@@ -3334,16 +3369,26 @@ var NostrTools = (() => {
|
||||
}
|
||||
subscribeMany(relays, filters, params) {
|
||||
params.onauth = params.onauth || params.doauth;
|
||||
const debugEnabled = typeof window !== "undefined" && !!window.__SIMPLE_POOL_DEBUG__;
|
||||
const request = [];
|
||||
const uniqUrls = [];
|
||||
for (let i2 = 0; i2 < relays.length; i2++) {
|
||||
const url = normalizeURL(relays[i2]);
|
||||
if (uniqUrls.indexOf(url) === -1) {
|
||||
uniqUrls.push(url);
|
||||
for (let f2 = 0; f2 < filters.length; f2++) {
|
||||
request.push({ url, filter: filters[f2] });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (debugEnabled) {
|
||||
console.log("🔎 SIMPLE_POOL subscribeMany", {
|
||||
relaysInput: relays,
|
||||
uniqUrls,
|
||||
filtersCount: filters.length,
|
||||
requestsCount: request.length
|
||||
});
|
||||
}
|
||||
return this.subscribeMap(request, params);
|
||||
}
|
||||
subscribeMap(requests, params) {
|
||||
@@ -3391,14 +3436,31 @@ var NostrTools = (() => {
|
||||
_knownIds.add(id);
|
||||
return have;
|
||||
};
|
||||
const debugEnabled = typeof window !== "undefined" && !!window.__SIMPLE_POOL_DEBUG__;
|
||||
const allOpened = Promise.all(
|
||||
requests.map(async ({ url, filter }, i2) => {
|
||||
if (debugEnabled) {
|
||||
console.log("🔎 SIMPLE_POOL subscribeMap request", {
|
||||
index: i2,
|
||||
url,
|
||||
filterKinds: filter?.kinds,
|
||||
hasAuthorFilter: !!filter?.authors,
|
||||
hasPTagFilter: !!filter?.["#p"]
|
||||
});
|
||||
}
|
||||
let relay;
|
||||
try {
|
||||
relay = await this.ensureRelay(url, {
|
||||
connectionTimeout: params.maxWait ? Math.max(params.maxWait * 0.8, params.maxWait - 1e3) : void 0
|
||||
});
|
||||
} catch (err) {
|
||||
if (debugEnabled) {
|
||||
console.log("🔎 SIMPLE_POOL subscribeMap ensureRelay FAILED", {
|
||||
index: i2,
|
||||
url,
|
||||
error: err?.message || String(err)
|
||||
});
|
||||
}
|
||||
handleClose(i2, err?.message || String(err));
|
||||
return;
|
||||
}
|
||||
@@ -3480,18 +3542,50 @@ var NostrTools = (() => {
|
||||
return events[0] || null;
|
||||
}
|
||||
publish(relays, event, options) {
|
||||
return relays.map(normalizeURL).map(async (url, i2, arr) => {
|
||||
const debugEnabled = typeof window !== "undefined" && !!window.__SIMPLE_POOL_DEBUG__;
|
||||
const normalizedRelays = relays.map(normalizeURL);
|
||||
if (debugEnabled) {
|
||||
console.log("🔎 SIMPLE_POOL publish START", {
|
||||
relaysInput: relays,
|
||||
normalizedRelays,
|
||||
eventKind: event?.kind,
|
||||
eventId: event?.id
|
||||
});
|
||||
}
|
||||
return normalizedRelays.map(async (url, i2, arr) => {
|
||||
if (arr.indexOf(url) !== i2) {
|
||||
if (debugEnabled) {
|
||||
console.log("🔎 SIMPLE_POOL publish duplicate URL", { url, index: i2 });
|
||||
}
|
||||
return Promise.reject("duplicate url");
|
||||
}
|
||||
let r = await this.ensureRelay(url);
|
||||
if (debugEnabled) {
|
||||
console.log("🔎 SIMPLE_POOL publish ensured relay", {
|
||||
url,
|
||||
relayConnected: r?.connected,
|
||||
relayMapKeys: Array.from(this.relays.keys())
|
||||
});
|
||||
}
|
||||
return r.publish(event).catch(async (err) => {
|
||||
if (debugEnabled) {
|
||||
console.log("🔎 SIMPLE_POOL publish ERROR", {
|
||||
url,
|
||||
error: err?.message || String(err)
|
||||
});
|
||||
}
|
||||
if (err instanceof Error && err.message.startsWith("auth-required: ") && options?.onauth) {
|
||||
await r.auth(options.onauth);
|
||||
return r.publish(event);
|
||||
}
|
||||
throw err;
|
||||
}).then((reason) => {
|
||||
if (debugEnabled) {
|
||||
console.log("🔎 SIMPLE_POOL publish SUCCESS", {
|
||||
url,
|
||||
reason
|
||||
});
|
||||
}
|
||||
if (this.trackRelays) {
|
||||
let set = this.seenOn.get(event.id);
|
||||
if (!set) {
|
||||
|
||||
@@ -77,6 +77,11 @@ case "$ARCH" in
|
||||
;;
|
||||
esac
|
||||
|
||||
# Append _debug suffix to output name for debug builds so production binary is never overwritten
|
||||
if [ "$DEBUG_BUILD" = true ]; then
|
||||
OUTPUT_NAME="${OUTPUT_NAME}_debug"
|
||||
fi
|
||||
|
||||
echo "Building for platform: $PLATFORM"
|
||||
echo "Output binary: $OUTPUT_NAME"
|
||||
echo ""
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
key,value,data_type,description,category,requires_restart,created_at,updated_at
|
||||
|
+9970
File diff suppressed because it is too large
Load Diff
Executable
+122
@@ -0,0 +1,122 @@
|
||||
#!/bin/bash
|
||||
|
||||
# C-Relay Debug Binary Deployment Script
|
||||
# Deploys build/c_relay_static_x86_64_debug to server for CPU profiling
|
||||
#
|
||||
# Usage:
|
||||
# ./deploy_lt_debug.sh -- deploy debug binary and restart
|
||||
# ./deploy_lt_debug.sh --profile -- deploy, then run perf and fetch results
|
||||
#
|
||||
# After deploying, profile with:
|
||||
# sudo perf record -g -p $(pgrep c_relay) -- sleep 30
|
||||
# sudo perf report --stdio --sort=symbol --no-children -n 2>/dev/null | head -80
|
||||
#
|
||||
# Restore production binary:
|
||||
# ./deploy_lt.sh
|
||||
|
||||
set -e
|
||||
|
||||
LOCAL_DEBUG_BINARY="build/c_relay_static_x86_64_debug"
|
||||
REMOTE_BINARY_PATH="/usr/local/bin/c_relay/c_relay"
|
||||
REMOTE_PROD_BACKUP="/usr/local/bin/c_relay/c_relay.production"
|
||||
SERVICE_NAME="c-relay"
|
||||
REMOTE_HOST="ubuntu@laantungir.com"
|
||||
|
||||
# Check debug binary exists
|
||||
if [ ! -f "$LOCAL_DEBUG_BINARY" ]; then
|
||||
echo "ERROR: Debug binary not found: $LOCAL_DEBUG_BINARY"
|
||||
echo ""
|
||||
echo "Build it first with:"
|
||||
echo " ./build_static.sh --debug"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=========================================="
|
||||
echo "C-Relay Debug Deployment"
|
||||
echo "=========================================="
|
||||
echo "Binary: $LOCAL_DEBUG_BINARY ($(du -h "$LOCAL_DEBUG_BINARY" | cut -f1))"
|
||||
echo "Target: $REMOTE_HOST:$REMOTE_BINARY_PATH"
|
||||
echo ""
|
||||
echo "WARNING: Debug binary has symbols and is larger than production."
|
||||
echo " It is safe to run but should not be left deployed long-term."
|
||||
echo ""
|
||||
|
||||
# Backup production binary (only if not already backed up)
|
||||
echo "Backing up production binary..."
|
||||
ssh "$REMOTE_HOST" "
|
||||
if [ ! -f '$REMOTE_PROD_BACKUP' ]; then
|
||||
sudo cp '$REMOTE_BINARY_PATH' '$REMOTE_PROD_BACKUP'
|
||||
echo 'Production binary backed up to $REMOTE_PROD_BACKUP'
|
||||
else
|
||||
echo 'Production backup already exists, skipping'
|
||||
fi
|
||||
"
|
||||
|
||||
# Upload debug binary
|
||||
echo "Uploading debug binary..."
|
||||
scp "$LOCAL_DEBUG_BINARY" "$REMOTE_HOST:/tmp/c_relay_debug.tmp"
|
||||
|
||||
# Install debug binary
|
||||
echo "Installing debug binary..."
|
||||
ssh "$REMOTE_HOST" "
|
||||
sudo mv '/tmp/c_relay_debug.tmp' '$REMOTE_BINARY_PATH'
|
||||
sudo chown c-relay:c-relay '$REMOTE_BINARY_PATH'
|
||||
sudo chmod +x '$REMOTE_BINARY_PATH'
|
||||
"
|
||||
|
||||
# Restart service
|
||||
echo "Restarting c-relay service..."
|
||||
ssh "$REMOTE_HOST" "sudo systemctl daemon-reload && sudo systemctl restart '$SERVICE_NAME'"
|
||||
|
||||
echo ""
|
||||
echo "✓ Debug binary deployed and service restarted"
|
||||
echo ""
|
||||
|
||||
# If --profile flag, run perf automatically
|
||||
if [ "$1" = "--profile" ]; then
|
||||
echo "=========================================="
|
||||
echo "Running perf profile (30 seconds)..."
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Wait for relay to start
|
||||
sleep 3
|
||||
|
||||
ssh "$REMOTE_HOST" "
|
||||
PID=\$(pgrep c_relay)
|
||||
if [ -z \"\$PID\" ]; then
|
||||
echo 'ERROR: c_relay not running'
|
||||
exit 1
|
||||
fi
|
||||
echo \"Profiling PID \$PID for 30 seconds...\"
|
||||
sudo perf record -g -p \$PID -- sleep 30
|
||||
echo ''
|
||||
echo '=== TOP FUNCTIONS BY CPU ==='
|
||||
sudo perf report --stdio --sort=symbol --no-children -n 2>/dev/null | head -60
|
||||
"
|
||||
else
|
||||
echo "=========================================="
|
||||
echo "Next Steps: Profile the relay"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "SSH to server and run:"
|
||||
echo " sudo perf record -g -p \$(pgrep c_relay) -- sleep 30"
|
||||
echo " sudo perf report --stdio --sort=symbol --no-children -n 2>/dev/null | head -60"
|
||||
echo ""
|
||||
echo "Or run with --profile to do it automatically:"
|
||||
echo " ./deploy_lt_debug.sh --profile"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Restore Production Binary When Done"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Run this to restore the production binary:"
|
||||
echo " ./deploy_lt.sh"
|
||||
echo ""
|
||||
echo "Or manually on the server:"
|
||||
echo " sudo cp '$REMOTE_PROD_BACKUP' '$REMOTE_BINARY_PATH'"
|
||||
echo " sudo systemctl restart $SERVICE_NAME"
|
||||
echo ""
|
||||
Submodule
+1
Submodule nostr-rs-relay added at 64cfcaf44a
+1
-1
Submodule nostr_core_lib updated: a8dc2ed046...98cfd81b01
@@ -0,0 +1,632 @@
|
||||
# c-relay-pg Plan — PostgreSQL Backend + Multi-Instance + Dashboard
|
||||
|
||||
## Overview
|
||||
|
||||
**c-relay-pg** is a new project (separate repository) forked from c-relay after the `db_ops` abstraction layer is complete. It replaces the SQLite backend with PostgreSQL, enabling horizontal scaling, external dashboards, and production-grade deployments.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- The `db_ops.h` / `db_ops.c` abstraction layer must be complete in c-relay first — see [c-relay thread pool plan](c_relay_thread_pool_plan.md) Phase 1.
|
||||
- The fork happens after Phase 1 of that plan is done and tested.
|
||||
|
||||
### Why a Separate Project?
|
||||
|
||||
| | c-relay | c-relay-pg |
|
||||
|---|---------|-----------|
|
||||
| **Database** | SQLite (embedded) | PostgreSQL (client-server) |
|
||||
| **Deployment** | Single binary + DB file | Binary + PostgreSQL server |
|
||||
| **Scaling** | Single instance | Multiple instances + load balancer |
|
||||
| **Dashboard** | Embedded web UI | Separate web server + Grafana |
|
||||
| **Target user** | Personal relay, small community | Medium-large relay, production |
|
||||
| **Complexity** | Minimal | More infrastructure |
|
||||
|
||||
For the full analysis of why PostgreSQL was chosen over MySQL/MariaDB and other databases, see the [database architecture analysis](database_architecture_analysis.md).
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph c-relay-pg - Production Deployment
|
||||
LB[nginx - TLS + load balancing] -->|WebSocket| R1[c-relay-pg Instance 1]
|
||||
LB -->|WebSocket| R2[c-relay-pg Instance 2]
|
||||
LB -->|HTTP| DASH[Dashboard]
|
||||
R1 -->|db_ops API| PGBACK[PostgreSQL Backend - db_ops_postgres.c]
|
||||
R2 -->|db_ops API| PGBACK
|
||||
PGBACK -->|libpq| PG[PostgreSQL Server]
|
||||
DASH -->|SQL| PG
|
||||
R1 <-->|LISTEN/NOTIFY| R2
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: PostgreSQL Backend
|
||||
|
||||
### Goal
|
||||
|
||||
Fork c-relay into a new repository called **c-relay-pg**. Replace the SQLite `db_ops` implementation with PostgreSQL using `libpq`. The `db_ops.h` interface stays identical — only the backend changes.
|
||||
|
||||
### New Files
|
||||
|
||||
- `src/db_ops_sqlite.c` — Renamed from `db_ops.c` (the Phase 1 SQLite implementation from c-relay)
|
||||
- `src/db_ops_postgres.c` — New PostgreSQL implementation
|
||||
- `src/db_ops.c` — Thin dispatcher that calls the active backend
|
||||
|
||||
### PostgreSQL Schema
|
||||
|
||||
```sql
|
||||
-- PostgreSQL schema for c-relay-pg
|
||||
-- No event_tags table needed — JSONB + GIN handles everything
|
||||
|
||||
CREATE TABLE events (
|
||||
id TEXT PRIMARY KEY,
|
||||
pubkey TEXT NOT NULL,
|
||||
created_at BIGINT NOT NULL,
|
||||
kind INTEGER NOT NULL,
|
||||
event_type TEXT NOT NULL CHECK (event_type IN ('regular', 'replaceable', 'ephemeral', 'addressable')),
|
||||
content TEXT NOT NULL,
|
||||
sig TEXT NOT NULL,
|
||||
tags JSONB NOT NULL DEFAULT '[]',
|
||||
event_json TEXT NOT NULL,
|
||||
first_seen BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT
|
||||
);
|
||||
|
||||
-- Core indexes
|
||||
CREATE INDEX idx_events_pubkey ON events(pubkey);
|
||||
CREATE INDEX idx_events_kind ON events(kind);
|
||||
CREATE INDEX idx_events_created_at ON events(created_at DESC);
|
||||
CREATE INDEX idx_events_kind_created_at ON events(kind, created_at DESC);
|
||||
CREATE INDEX idx_events_pubkey_created_at ON events(pubkey, created_at DESC);
|
||||
CREATE INDEX idx_events_pubkey_kind ON events(pubkey, kind);
|
||||
|
||||
-- THE KEY INDEX: GIN on JSONB tags — replaces the entire event_tags table
|
||||
CREATE INDEX idx_events_tags ON events USING GIN (tags);
|
||||
|
||||
-- Partial index: only non-ephemeral events (what REQ queries actually filter)
|
||||
CREATE INDEX idx_events_non_ephemeral ON events(created_at DESC)
|
||||
WHERE kind < 20000 OR kind >= 30000;
|
||||
|
||||
-- Config table
|
||||
CREATE TABLE config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
data_type TEXT NOT NULL CHECK (data_type IN ('string', 'integer', 'boolean', 'json')),
|
||||
description TEXT,
|
||||
category TEXT DEFAULT 'general',
|
||||
requires_restart INTEGER DEFAULT 0,
|
||||
created_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT,
|
||||
updated_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT
|
||||
);
|
||||
|
||||
-- Auth rules table
|
||||
CREATE TABLE auth_rules (
|
||||
id SERIAL PRIMARY KEY,
|
||||
rule_type TEXT NOT NULL CHECK (rule_type IN ('whitelist', 'blacklist', 'rate_limit', 'auth_required', 'wot_whitelist')),
|
||||
pattern_type TEXT NOT NULL CHECK (pattern_type IN ('pubkey', 'kind', 'ip', 'global')),
|
||||
pattern_value TEXT,
|
||||
active INTEGER NOT NULL DEFAULT 1,
|
||||
created_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT,
|
||||
updated_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT
|
||||
);
|
||||
|
||||
CREATE INDEX idx_auth_rules_lookup ON auth_rules(rule_type, pattern_type, pattern_value) WHERE active = 1;
|
||||
|
||||
-- Relay private key storage
|
||||
CREATE TABLE relay_seckey (
|
||||
private_key_hex TEXT NOT NULL CHECK (length(private_key_hex) = 64),
|
||||
created_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT
|
||||
);
|
||||
|
||||
-- Subscription logging
|
||||
CREATE TABLE subscriptions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
subscription_id TEXT NOT NULL,
|
||||
wsi_pointer TEXT NOT NULL,
|
||||
client_ip TEXT NOT NULL,
|
||||
event_type TEXT NOT NULL CHECK (event_type IN ('created', 'closed', 'expired', 'disconnected')),
|
||||
filter_json TEXT,
|
||||
events_sent INTEGER DEFAULT 0,
|
||||
created_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT,
|
||||
ended_at BIGINT,
|
||||
duration INTEGER,
|
||||
UNIQUE(subscription_id, wsi_pointer)
|
||||
);
|
||||
|
||||
-- IP ban persistence
|
||||
CREATE TABLE ip_bans (
|
||||
ip TEXT PRIMARY KEY,
|
||||
failure_count INTEGER NOT NULL DEFAULT 0,
|
||||
ban_count INTEGER NOT NULL DEFAULT 0,
|
||||
banned_until BIGINT NOT NULL DEFAULT 0,
|
||||
first_failure BIGINT NOT NULL DEFAULT 0,
|
||||
has_authed_successfully INTEGER NOT NULL DEFAULT 0,
|
||||
last_success_at BIGINT NOT NULL DEFAULT 0,
|
||||
total_connections INTEGER NOT NULL DEFAULT 0,
|
||||
total_failures INTEGER NOT NULL DEFAULT 0,
|
||||
total_successes INTEGER NOT NULL DEFAULT 0,
|
||||
first_seen BIGINT NOT NULL DEFAULT 0,
|
||||
idle_failure_count INTEGER NOT NULL DEFAULT 0,
|
||||
idle_ban_count INTEGER NOT NULL DEFAULT 0,
|
||||
idle_banned_until BIGINT NOT NULL DEFAULT 0,
|
||||
idle_first_failure BIGINT NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
-- Materialized views for dashboard (refreshed periodically)
|
||||
CREATE MATERIALIZED VIEW event_kinds_mv AS
|
||||
SELECT kind, COUNT(*) as count,
|
||||
ROUND(COUNT(*) * 100.0 / NULLIF((SELECT COUNT(*) FROM events), 0), 2) as percentage
|
||||
FROM events GROUP BY kind;
|
||||
|
||||
CREATE MATERIALIZED VIEW time_stats_mv AS
|
||||
SELECT 'total' as period, COUNT(*) as total_events, COUNT(DISTINCT pubkey) as unique_pubkeys
|
||||
FROM events
|
||||
UNION ALL
|
||||
SELECT '24h', COUNT(*), COUNT(DISTINCT pubkey)
|
||||
FROM events WHERE created_at >= EXTRACT(EPOCH FROM NOW())::BIGINT - 86400
|
||||
UNION ALL
|
||||
SELECT '7d', COUNT(*), COUNT(DISTINCT pubkey)
|
||||
FROM events WHERE created_at >= EXTRACT(EPOCH FROM NOW())::BIGINT - 604800;
|
||||
|
||||
CREATE MATERIALIZED VIEW top_pubkeys_mv AS
|
||||
SELECT pubkey, COUNT(*) as event_count,
|
||||
ROUND(COUNT(*) * 100.0 / NULLIF((SELECT COUNT(*) FROM events), 0), 2) as percentage
|
||||
FROM events GROUP BY pubkey ORDER BY event_count DESC LIMIT 20;
|
||||
|
||||
-- Unique indexes required for CONCURRENTLY refresh
|
||||
CREATE UNIQUE INDEX idx_event_kinds_mv ON event_kinds_mv(kind);
|
||||
CREATE UNIQUE INDEX idx_time_stats_mv ON time_stats_mv(period);
|
||||
CREATE UNIQUE INDEX idx_top_pubkeys_mv ON top_pubkeys_mv(pubkey);
|
||||
```
|
||||
|
||||
### Key Implementation Details
|
||||
|
||||
#### Tag Queries with JSONB
|
||||
|
||||
The biggest win — replacing the `event_tags` subquery with JSONB containment:
|
||||
|
||||
```c
|
||||
// SQLite (current): subquery into event_tags table
|
||||
// AND id IN (SELECT event_id FROM event_tags WHERE tag_name = ? AND tag_value IN (?))
|
||||
|
||||
// PostgreSQL: JSONB containment operator with GIN index
|
||||
// AND tags @> '[["p", "pubkey_hex"]]'
|
||||
|
||||
// For multiple tag values:
|
||||
// AND (tags @> '[["p", "val1"]]' OR tags @> '[["p", "val2"]]')
|
||||
```
|
||||
|
||||
This eliminates the entire `event_tags` table (4 million rows, ~2 GB indexes in the current SQLite schema). The GIN index on JSONB handles everything in ~100–200 MB.
|
||||
|
||||
#### LISTEN/NOTIFY Integration
|
||||
|
||||
For cross-instance event broadcasting:
|
||||
|
||||
```c
|
||||
// In db_ops_postgres.c:
|
||||
int db_notify_new_event(const char* event_id) {
|
||||
char cmd[128];
|
||||
snprintf(cmd, sizeof(cmd), "NOTIFY new_event, '%s'", event_id);
|
||||
PGresult* res = PQexec(g_pg_conn, cmd);
|
||||
PQclear(res);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Called from the lws event loop every iteration:
|
||||
int db_check_notifications(char* event_id_out, size_t max_len) {
|
||||
PQconsumeInput(g_pg_notify_conn);
|
||||
PGnotify* notify = PQnotifies(g_pg_notify_conn);
|
||||
if (notify) {
|
||||
strncpy(event_id_out, notify->extra, max_len);
|
||||
PQfreemem(notify);
|
||||
return 1; // Got a notification
|
||||
}
|
||||
return 0; // No notifications
|
||||
}
|
||||
```
|
||||
|
||||
#### Connection Management
|
||||
|
||||
```c
|
||||
// db_ops_postgres.c connection pool (simple version)
|
||||
#define DB_POOL_SIZE 4
|
||||
|
||||
static PGconn* g_pg_read_pool[DB_POOL_SIZE]; // For REQ queries
|
||||
static PGconn* g_pg_write_conn; // For EVENT inserts
|
||||
static PGconn* g_pg_notify_conn; // For LISTEN/NOTIFY
|
||||
static pthread_mutex_t g_pool_lock;
|
||||
|
||||
PGconn* db_get_read_connection(void) {
|
||||
pthread_mutex_lock(&g_pool_lock);
|
||||
// Round-robin or find idle connection
|
||||
// ...
|
||||
pthread_mutex_unlock(&g_pool_lock);
|
||||
}
|
||||
```
|
||||
|
||||
### Build System Changes
|
||||
|
||||
```makefile
|
||||
# Makefile additions
|
||||
DB_BACKEND ?= sqlite # Default to sqlite, override with: make DB_BACKEND=postgres
|
||||
|
||||
ifeq ($(DB_BACKEND),postgres)
|
||||
MAIN_SRC += src/db_ops.c src/db_ops_postgres.c
|
||||
LIBS += -lpq
|
||||
CFLAGS += -DDB_BACKEND_POSTGRES
|
||||
else
|
||||
MAIN_SRC += src/db_ops.c src/db_ops_sqlite.c
|
||||
CFLAGS += -DDB_BACKEND_SQLITE
|
||||
endif
|
||||
```
|
||||
|
||||
### Data Migration Tool
|
||||
|
||||
A standalone tool to migrate existing SQLite data to PostgreSQL:
|
||||
|
||||
```bash
|
||||
# migrate_to_postgres.sh
|
||||
# 1. Export events from SQLite
|
||||
sqlite3 old_relay.db ".mode csv" "SELECT id,pubkey,created_at,kind,event_type,content,sig,tags,event_json,first_seen FROM events" > events.csv
|
||||
|
||||
# 2. Import into PostgreSQL
|
||||
psql -d crelay -c "\COPY events FROM 'events.csv' WITH CSV"
|
||||
|
||||
# 3. Migrate config
|
||||
sqlite3 old_relay.db ".mode csv" "SELECT key,value,data_type,description,category,requires_restart FROM config" > config.csv
|
||||
psql -d crelay -c "\COPY config(key,value,data_type,description,category,requires_restart) FROM 'config.csv' WITH CSV"
|
||||
|
||||
# 4. Migrate auth rules
|
||||
sqlite3 old_relay.db ".mode csv" "SELECT rule_type,pattern_type,pattern_value,active FROM auth_rules" > auth_rules.csv
|
||||
psql -d crelay -c "\COPY auth_rules(rule_type,pattern_type,pattern_value,active) FROM 'auth_rules.csv' WITH CSV"
|
||||
|
||||
# 5. Refresh materialized views
|
||||
psql -d crelay -c "REFRESH MATERIALIZED VIEW event_kinds_mv; REFRESH MATERIALIZED VIEW time_stats_mv; REFRESH MATERIALIZED VIEW top_pubkeys_mv;"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Multi-Instance + Dashboard
|
||||
|
||||
### Goal
|
||||
|
||||
Run multiple c-relay-pg instances behind a load balancer with a separate dashboard web server, all sharing the same PostgreSQL database.
|
||||
|
||||
### Components
|
||||
|
||||
1. **nginx config** — WebSocket load balancing with `ip_hash` stickiness
|
||||
2. **systemd template** — `c-relay-pg@.service` for multiple instances
|
||||
3. **LISTEN/NOTIFY integration** — Cross-instance event broadcasting in the `lws_service()` loop
|
||||
4. **PgBouncer** — Connection pooling between c-relay-pg instances and PostgreSQL
|
||||
5. **Dashboard** — Separate web server querying PostgreSQL directly
|
||||
6. **Materialized view refresh** — Background job to refresh analytics views
|
||||
|
||||
### Multi-Instance Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
INTERNET[Internet - Nostr Clients] -->|wss://relay.example.com| NGINX[nginx reverse proxy - TLS termination + load balancing]
|
||||
|
||||
NGINX -->|ws://localhost:8888| R1[c-relay-pg Instance 1 - port 8888]
|
||||
NGINX -->|ws://localhost:8889| R2[c-relay-pg Instance 2 - port 8889]
|
||||
NGINX -->|ws://localhost:8890| R3[c-relay-pg Instance 3 - port 8890]
|
||||
NGINX -->|http://localhost:3000| DASHWEB[Dashboard Web Server]
|
||||
|
||||
R1 -->|libpq connection pool| PGPOOL[PgBouncer - connection pooler]
|
||||
R2 -->|libpq connection pool| PGPOOL
|
||||
R3 -->|libpq connection pool| PGPOOL
|
||||
|
||||
PGPOOL -->|pooled connections| PG[PostgreSQL Primary]
|
||||
DASHWEB -->|read queries| PG
|
||||
|
||||
PG -->|streaming replication| REPLICA[Read Replica - optional]
|
||||
DASHWEB -.->|heavy analytics| REPLICA
|
||||
```
|
||||
|
||||
### nginx Load Balancing Config
|
||||
|
||||
```nginx
|
||||
# nginx.conf - WebSocket load balancing for c-relay-pg
|
||||
upstream relay_backends {
|
||||
# ip_hash ensures a client always hits the same instance
|
||||
# (important for WebSocket session stickiness)
|
||||
ip_hash;
|
||||
|
||||
server 127.0.0.1:8888;
|
||||
server 127.0.0.1:8889;
|
||||
server 127.0.0.1:8890;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name relay.example.com;
|
||||
|
||||
# TLS config...
|
||||
|
||||
location / {
|
||||
proxy_pass http://relay_backends;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_read_timeout 86400; # Keep WebSocket alive for 24h
|
||||
}
|
||||
|
||||
# Dashboard on separate path
|
||||
location /dashboard {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Session stickiness** (`ip_hash`) ensures that once a client connects to Instance 2, all their subsequent WebSocket frames go to Instance 2. This is important because subscriptions are held in-memory per instance.
|
||||
|
||||
### Starting Multiple Instances
|
||||
|
||||
Each instance is the same binary, just on a different port, all pointing to the same PostgreSQL:
|
||||
|
||||
```bash
|
||||
# Instance 1
|
||||
./build/c_relay_x86 --port 8888 --db-host localhost --db-name crelay &
|
||||
|
||||
# Instance 2
|
||||
./build/c_relay_x86 --port 8889 --db-host localhost --db-name crelay &
|
||||
|
||||
# Instance 3
|
||||
./build/c_relay_x86 --port 8890 --db-host localhost --db-name crelay &
|
||||
```
|
||||
|
||||
Or with systemd template units:
|
||||
|
||||
```ini
|
||||
# /etc/systemd/system/c-relay-pg@.service
|
||||
[Unit]
|
||||
Description=C-Relay-PG Nostr Instance %i
|
||||
After=postgresql.service
|
||||
|
||||
[Service]
|
||||
ExecStart=/opt/c-relay-pg/c_relay_x86 --port %i --db-host localhost --db-name crelay
|
||||
Restart=always
|
||||
User=c-relay
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
```bash
|
||||
systemctl enable c-relay-pg@8888 c-relay-pg@8889 c-relay-pg@8890
|
||||
systemctl start c-relay-pg@8888 c-relay-pg@8889 c-relay-pg@8890
|
||||
```
|
||||
|
||||
### Cross-Instance Event Broadcasting
|
||||
|
||||
When Instance 1 receives a new EVENT and stores it in PostgreSQL, Instance 2 and Instance 3 need to know about it so they can broadcast to their connected subscribers.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client_A as Client A - connected to Instance 1
|
||||
participant I1 as Instance 1
|
||||
participant PG as PostgreSQL
|
||||
participant I2 as Instance 2
|
||||
participant I3 as Instance 3
|
||||
participant Client_B as Client B - connected to Instance 2
|
||||
participant Client_C as Client C - connected to Instance 3
|
||||
|
||||
Client_A->>I1: EVENT - new kind:1 note
|
||||
I1->>PG: INSERT INTO events...
|
||||
PG-->>I1: OK
|
||||
I1->>I1: broadcast to local subscribers
|
||||
I1->>PG: NOTIFY new_event with event_id
|
||||
|
||||
Note over PG: PostgreSQL delivers notification to all listeners
|
||||
|
||||
PG-->>I2: NOTIFY: new_event event_id
|
||||
PG-->>I3: NOTIFY: new_event event_id
|
||||
|
||||
I2->>PG: SELECT event_json FROM events WHERE id = event_id
|
||||
PG-->>I2: event JSON
|
||||
I2->>I2: match against local subscriptions
|
||||
I2->>Client_B: EVENT message - if subscription matches
|
||||
|
||||
I3->>PG: SELECT event_json FROM events WHERE id = event_id
|
||||
PG-->>I3: event JSON
|
||||
I3->>I3: match against local subscriptions
|
||||
I3->>Client_C: EVENT message - if subscription matches
|
||||
```
|
||||
|
||||
#### PostgreSQL LISTEN/NOTIFY Implementation
|
||||
|
||||
Built into PostgreSQL — no additional infrastructure needed:
|
||||
|
||||
```c
|
||||
// === In the relay's event loop (modified lws_service loop) ===
|
||||
|
||||
// Setup: create a dedicated connection for LISTEN
|
||||
PGconn* notify_conn = PQconnectdb("host=localhost dbname=crelay");
|
||||
PQexec(notify_conn, "LISTEN new_event");
|
||||
int notify_fd = PQsocket(notify_conn); // Get the socket fd for poll()
|
||||
|
||||
// After storing an event:
|
||||
void on_event_stored(PGconn* write_conn, const char* event_id) {
|
||||
char notify_cmd[128];
|
||||
snprintf(notify_cmd, sizeof(notify_cmd),
|
||||
"NOTIFY new_event, '%s'", event_id);
|
||||
PQexec(write_conn, notify_cmd);
|
||||
}
|
||||
|
||||
// In the main event loop (runs every lws_service iteration):
|
||||
void check_cross_instance_events(PGconn* notify_conn) {
|
||||
// Non-blocking check for notifications
|
||||
PQconsumeInput(notify_conn);
|
||||
|
||||
PGnotify* notify;
|
||||
while ((notify = PQnotifies(notify_conn)) != NULL) {
|
||||
// Another instance stored a new event
|
||||
const char* event_id = notify->extra;
|
||||
|
||||
// Fetch the event and check against local subscriptions
|
||||
cJSON* event = db_get_event_by_id(event_id);
|
||||
if (event) {
|
||||
broadcast_event_to_subscriptions(event);
|
||||
cJSON_Delete(event);
|
||||
}
|
||||
|
||||
PQfreemem(notify);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Performance**: LISTEN/NOTIFY adds ~1–5ms latency for cross-instance delivery. For a Nostr relay, this is imperceptible — clients already expect network latency.
|
||||
|
||||
**Payload limit**: NOTIFY payloads are limited to 8000 bytes. For event IDs (64 hex chars), this is fine.
|
||||
|
||||
#### Alternative: Redis Pub/Sub
|
||||
|
||||
If you later need even lower latency or more sophisticated routing:
|
||||
|
||||
```c
|
||||
// Using hiredis (Redis C client)
|
||||
redisContext* redis = redisConnect("127.0.0.1", 6379);
|
||||
|
||||
// After storing event:
|
||||
redisCommand(redis, "PUBLISH new_event %s", event_json);
|
||||
|
||||
// Subscriber (in each instance):
|
||||
redisCommand(redis, "SUBSCRIBE new_event");
|
||||
// Then poll for messages in the event loop
|
||||
```
|
||||
|
||||
Redis adds sub-millisecond pub/sub but requires running a Redis server. For most relays, PostgreSQL LISTEN/NOTIFY is sufficient.
|
||||
|
||||
### Connection Pooling with PgBouncer
|
||||
|
||||
Each relay instance needs multiple database connections. Without pooling, 3 instances × 10 connections = 30 PostgreSQL backend processes. With PgBouncer:
|
||||
|
||||
```ini
|
||||
# /etc/pgbouncer/pgbouncer.ini
|
||||
[databases]
|
||||
crelay = host=127.0.0.1 port=5432 dbname=crelay
|
||||
|
||||
[pgbouncer]
|
||||
listen_port = 6432
|
||||
listen_addr = 127.0.0.1
|
||||
auth_type = md5
|
||||
pool_mode = transaction # Return connection to pool after each transaction
|
||||
max_client_conn = 200 # Total connections from all relay instances
|
||||
default_pool_size = 20 # Actual PostgreSQL connections
|
||||
```
|
||||
|
||||
Relay instances connect to PgBouncer (port 6432) instead of PostgreSQL directly (port 5432). PgBouncer multiplexes 200 client connections onto 20 actual PostgreSQL connections.
|
||||
|
||||
### Zero-Downtime Deployment
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant LB as nginx
|
||||
participant I1 as Instance 1 - v1.0
|
||||
participant I2 as Instance 2 - v1.0
|
||||
participant I3 as Instance 3 - v1.0
|
||||
participant I1_NEW as Instance 1 - v1.1
|
||||
|
||||
Note over LB,I3: Normal operation: 3 instances serving traffic
|
||||
|
||||
LB->>I1: Mark upstream as down
|
||||
Note over I1: Drain: wait for existing connections to close or timeout
|
||||
I1->>I1: Graceful shutdown
|
||||
|
||||
Note over LB: Traffic now goes to I2 and I3 only
|
||||
|
||||
I1_NEW->>I1_NEW: Start with new binary
|
||||
I1_NEW->>LB: Health check passes
|
||||
LB->>I1_NEW: Mark upstream as up
|
||||
|
||||
Note over LB,I1_NEW: Instance 1 now running v1.1, repeat for I2 and I3
|
||||
```
|
||||
|
||||
Rolling deploy script:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# rolling_deploy.sh - Zero-downtime deployment
|
||||
|
||||
for port in 8888 8889 8890; do
|
||||
echo "Deploying instance on port $port..."
|
||||
|
||||
# 1. Tell nginx to stop sending new connections
|
||||
sed -i "s/server 127.0.0.1:$port;/server 127.0.0.1:$port down;/" /etc/nginx/nginx.conf
|
||||
nginx -s reload
|
||||
|
||||
# 2. Wait for existing connections to drain (30 seconds)
|
||||
sleep 30
|
||||
|
||||
# 3. Stop old instance
|
||||
systemctl stop c-relay-pg@$port
|
||||
|
||||
# 4. Deploy new binary
|
||||
cp ./build/c_relay_x86 /opt/c-relay-pg/c_relay_x86
|
||||
|
||||
# 5. Start new instance
|
||||
systemctl start c-relay-pg@$port
|
||||
|
||||
# 6. Wait for health check
|
||||
sleep 5
|
||||
|
||||
# 7. Re-enable in nginx
|
||||
sed -i "s/server 127.0.0.1:$port down;/server 127.0.0.1:$port;/" /etc/nginx/nginx.conf
|
||||
nginx -s reload
|
||||
|
||||
echo "Instance on port $port deployed successfully"
|
||||
done
|
||||
```
|
||||
|
||||
### Dashboard Options
|
||||
|
||||
With PostgreSQL, the dashboard can be built with any technology:
|
||||
|
||||
- **Grafana** — Point directly at PostgreSQL, zero custom code
|
||||
- **Custom web app** — React/Vue frontend + any backend (Node, Python, Go) querying PostgreSQL
|
||||
- **Embedded in c-relay-pg** — Keep current approach but queries go through `db_ops` to PostgreSQL (no longer blocks event loop since PostgreSQL handles concurrency)
|
||||
|
||||
### Scaling Scenarios
|
||||
|
||||
| Scenario | Instances | Connections | Events/hour | Setup |
|
||||
|----------|-----------|-------------|-------------|-------|
|
||||
| **Current** | 1 | ~1,200 | 56 | Single process + SQLite |
|
||||
| **Small upgrade** | 2 | ~2,500 | 500 | 2 instances + PostgreSQL |
|
||||
| **Medium relay** | 4 | ~5,000 | 5,000 | 4 instances + PostgreSQL + PgBouncer |
|
||||
| **Large relay** | 8 | ~10,000 | 50,000 | 8 instances + PostgreSQL + read replica |
|
||||
| **Multi-region** | 2–4 per region | ~50,000 | 500,000 | Multiple servers + PostgreSQL replication |
|
||||
|
||||
### Cost Perspective
|
||||
|
||||
Running multiple relay instances with PostgreSQL on a single VPS:
|
||||
- **PostgreSQL**: ~200–500 MB RAM baseline
|
||||
- **PgBouncer**: ~10 MB RAM
|
||||
- **Each relay instance**: ~50–100 MB RAM
|
||||
- **Dashboard web server**: ~50–100 MB RAM
|
||||
- **4 instances + PostgreSQL + PgBouncer + dashboard**: ~1–2 GB total RAM
|
||||
- A **$20/month VPS** with 4 cores and 4 GB RAM handles this easily
|
||||
- A **$40/month VPS** with 8 cores and 8 GB RAM handles 8 instances comfortably
|
||||
|
||||
---
|
||||
|
||||
## Risk Mitigation
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|-----------|
|
||||
| PostgreSQL connection failures | `db_ops` returns error codes. Relay logs errors but doesn't crash. Reconnection logic with exponential backoff. |
|
||||
| Performance regression | Benchmark before/after. PostgreSQL should be faster for complex queries, similar for simple ones. |
|
||||
| Data loss during migration | Migration tool is read-only on SQLite. PostgreSQL import is idempotent (INSERT ON CONFLICT). |
|
||||
| SQLite fallback needed | Keep `db_ops_sqlite.c` working. Compile-time flag switches backends. Can always go back. |
|
||||
| LISTEN/NOTIFY message loss | NOTIFY is transactional — if the INSERT commits, the NOTIFY is guaranteed. Missed notifications during reconnect handled by periodic full-sync. |
|
||||
| PgBouncer adds latency | Transaction pooling mode adds <0.1ms. Negligible compared to query time. |
|
||||
|
||||
---
|
||||
|
||||
## Relationship to c-relay
|
||||
|
||||
This project depends on the `db_ops.h` abstraction layer built in [c-relay thread pool plan](c_relay_thread_pool_plan.md) Phase 1. The interface is identical — only the backend implementation changes:
|
||||
|
||||
- **c-relay**: `db_ops.c` → SQLite calls via `sqlite3_*`
|
||||
- **c-relay-pg**: `db_ops_postgres.c` → PostgreSQL calls via `libpq` (`PQexec`, `PQgetvalue`, etc.)
|
||||
|
||||
The `db_ops.h` header file is shared between both projects. Changes to the interface should be coordinated.
|
||||
@@ -0,0 +1,332 @@
|
||||
# c-relay Thread Pool Plan — db_ops Abstraction + SQLite Thread Pool
|
||||
|
||||
## Overview
|
||||
|
||||
This plan covers improvements to **c-relay** (this repo) — the lean, single-binary, embedded-SQLite Nostr relay. Two phases:
|
||||
|
||||
1. **Phase 1: Database Abstraction Layer** — Consolidate all scattered `sqlite3_*` calls into a single `db_ops.h` / `db_ops.c` module. Pure refactor, no behavior change.
|
||||
2. **Phase 2: SQLite Thread Pool** — Add worker threads for concurrent reads, unblocking the `lws_service()` event loop.
|
||||
|
||||
This is the **final form of c-relay**: an embedded-SQLite relay with clean architecture and non-blocking database access. The abstraction layer also enables a future fork to PostgreSQL — see [c-relay-pg plan](c_relay_pg_plan.md).
|
||||
|
||||
### Why This First?
|
||||
|
||||
The current architecture has a fundamental bottleneck documented in the [database architecture analysis](database_architecture_analysis.md): the single-threaded event loop blocks on every database call. A 672ms REQ query freezes every connected client. The thread pool fixes this without changing the database engine or deployment model.
|
||||
|
||||
| Metric | Current | After Thread Pool |
|
||||
|--------|---------|-------------------|
|
||||
| **Event loop blocking** | 0.1–672ms per query | Near-zero |
|
||||
| **Concurrent reads** | 1 | 4–8 |
|
||||
| **Deployment** | Single binary + DB file | Same |
|
||||
| **Database** | SQLite | Same |
|
||||
| **Code risk** | N/A | Low — additive change |
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph c-relay - Final Form
|
||||
RELAY[c-relay binary] -->|db_ops API| DBOPS[db_ops.c - abstraction layer]
|
||||
DBOPS -->|sqlite3 calls| SQLITE[SQLite WAL database]
|
||||
end
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph c-relay with Thread Pool
|
||||
LWS[lws_service event loop] -->|REQ arrives| Q[Thread-safe job queue]
|
||||
LWS -->|EVENT arrives| WQ[Write queue - single writer]
|
||||
Q --> T1[Reader Thread 1 - own sqlite3*]
|
||||
Q --> T2[Reader Thread 2 - own sqlite3*]
|
||||
Q --> T3[Reader Thread 3 - own sqlite3*]
|
||||
Q --> T4[Reader Thread 4 - own sqlite3*]
|
||||
WQ --> TW[Writer Thread - own sqlite3*]
|
||||
T1 -->|results| CB[Callback to lws event loop]
|
||||
T2 -->|results| CB
|
||||
T3 -->|results| CB
|
||||
T4 -->|results| CB
|
||||
TW -->|OK/error| CB
|
||||
CB -->|queue_message| LWS
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Database Abstraction Layer
|
||||
|
||||
### Goal
|
||||
|
||||
Consolidate all 258 scattered `sqlite3_*` calls across 8 files into a single `db_ops.h` / `db_ops.c` module with a backend-agnostic interface. SQLite continues to work — this is a pure refactor.
|
||||
|
||||
### Files That Currently Touch SQLite Directly
|
||||
|
||||
| File | `sqlite3_*` calls | Operations |
|
||||
|------|-------------------|------------|
|
||||
| [`src/main.c`](../src/main.c) | ~80 | Event store/retrieve, tag storage, REQ queries, COUNT queries, DB init, schema migration |
|
||||
| [`src/config.c`](../src/config.c) | ~60 | Config CRUD, auth rules CRUD, WoT sync, relay key storage, startup sequence |
|
||||
| [`src/api.c`](../src/api.c) | ~40 | Stats queries, monitoring views, admin SQL execution, config management |
|
||||
| [`src/dm_admin.c`](../src/dm_admin.c) | ~20 | Auth rule management, WoT whitelist operations |
|
||||
| [`src/websockets.c`](../src/websockets.c) | ~15 | COUNT queries, IP ban stats, connection tracking |
|
||||
| [`src/subscriptions.c`](../src/subscriptions.c) | ~15 | Subscription logging — create/close/disconnect |
|
||||
| [`src/nip009.c`](../src/nip009.c) | ~10 | Event deletion — by ID and by address |
|
||||
| [`src/request_validator.c`](../src/request_validator.c) | ~8 | Blacklist/whitelist checks |
|
||||
| [`src/ip_ban.c`](../src/ip_ban.c) | ~15 | IP ban table CRUD, persistence |
|
||||
|
||||
### Abstraction Layer Design
|
||||
|
||||
New files: `src/db_ops.h` and `src/db_ops.c`
|
||||
|
||||
```c
|
||||
// src/db_ops.h — Database operations abstraction layer
|
||||
#ifndef DB_OPS_H
|
||||
#define DB_OPS_H
|
||||
|
||||
#include "../nostr_core_lib/cjson/cJSON.h"
|
||||
|
||||
// ============================================================
|
||||
// Lifecycle
|
||||
// ============================================================
|
||||
int db_init(const char* connection_string); // SQLite: file path, PG: connection string
|
||||
void db_close(void);
|
||||
int db_is_available(void);
|
||||
|
||||
// ============================================================
|
||||
// Schema / Migration
|
||||
// ============================================================
|
||||
int db_ensure_schema(void);
|
||||
int db_get_schema_version(void);
|
||||
int db_execute_raw(const char* sql); // For schema DDL only
|
||||
|
||||
// ============================================================
|
||||
// Event Operations
|
||||
// ============================================================
|
||||
int db_store_event(cJSON* event);
|
||||
int db_event_exists(const char* event_id);
|
||||
char* db_get_event_json(const char* event_id); // Caller must free
|
||||
int db_delete_event_by_id(const char* event_id, const char* requester_pubkey);
|
||||
int db_delete_events_by_address(const char* pubkey, int kind,
|
||||
const char* d_tag, long before_timestamp);
|
||||
|
||||
// ============================================================
|
||||
// Event Queries - REQ handling
|
||||
// ============================================================
|
||||
typedef struct {
|
||||
int* kinds; int kind_count;
|
||||
char** authors; int author_count;
|
||||
char** ids; int id_count;
|
||||
char** tag_names; // Parallel arrays: tag_names[i] has tag_values[i][]
|
||||
char*** tag_values;
|
||||
int* tag_value_counts;
|
||||
int tag_filter_count;
|
||||
long since; // 0 = not set
|
||||
long until; // 0 = not set
|
||||
int limit; // 0 = default 500
|
||||
char* search; // NIP-50 search term, NULL = not set
|
||||
} db_event_filter_t;
|
||||
|
||||
typedef struct db_result db_result_t;
|
||||
|
||||
db_result_t* db_query_events(const db_event_filter_t* filter);
|
||||
const char* db_result_next_json(db_result_t* result); // Returns event_json, NULL when done
|
||||
int db_result_row_count(db_result_t* result);
|
||||
void db_result_free(db_result_t* result);
|
||||
|
||||
int db_count_events(const db_event_filter_t* filter);
|
||||
|
||||
// ============================================================
|
||||
// Config Operations
|
||||
// ============================================================
|
||||
char* db_get_config(const char* key); // Caller must free, NULL if not found
|
||||
int db_set_config(const char* key, const char* value, const char* data_type,
|
||||
const char* description, const char* category, int requires_restart);
|
||||
int db_update_config(const char* key, const char* value);
|
||||
int db_config_exists(const char* key);
|
||||
int db_get_config_count(void);
|
||||
|
||||
// ============================================================
|
||||
// Auth Rules Operations
|
||||
// ============================================================
|
||||
int db_add_auth_rule(const char* rule_type, const char* pattern_type, const char* pattern_value);
|
||||
int db_remove_auth_rule(const char* rule_type, const char* pattern_type, const char* pattern_value);
|
||||
int db_auth_rule_exists(const char* rule_type, const char* pattern_type, const char* pattern_value);
|
||||
int db_clear_auth_rules(const char* rule_type); // NULL = clear all
|
||||
int db_is_blacklisted(const char* pubkey);
|
||||
int db_is_whitelisted(const char* pubkey);
|
||||
int db_has_any_whitelist(void);
|
||||
|
||||
// ============================================================
|
||||
// Relay Key Storage
|
||||
// ============================================================
|
||||
int db_store_relay_private_key(const char* privkey_hex);
|
||||
char* db_get_relay_private_key(void); // Caller must free
|
||||
|
||||
// ============================================================
|
||||
// Subscription Logging
|
||||
// ============================================================
|
||||
int db_log_subscription_created(const char* sub_id, const char* wsi_ptr,
|
||||
const char* client_ip, const char* filter_json);
|
||||
int db_log_subscription_closed(const char* sub_id, const char* client_ip);
|
||||
int db_log_subscription_disconnected(const char* client_ip);
|
||||
int db_update_subscription_events_sent(const char* sub_id, int events_sent);
|
||||
int db_cleanup_orphaned_subscriptions(void);
|
||||
|
||||
// ============================================================
|
||||
// IP Ban Persistence
|
||||
// ============================================================
|
||||
int db_ensure_ip_ban_table(void);
|
||||
int db_load_ip_bans(void* ban_table, int table_size); // Populates in-memory table
|
||||
int db_save_ip_bans(const void* ban_table, int table_size);
|
||||
|
||||
// ============================================================
|
||||
// Analytics / Stats - for dashboard
|
||||
// ============================================================
|
||||
cJSON* db_get_event_kind_distribution(void);
|
||||
cJSON* db_get_time_based_stats(void);
|
||||
cJSON* db_get_top_pubkeys(int limit);
|
||||
cJSON* db_get_subscription_details(void);
|
||||
int db_get_total_event_count(void);
|
||||
|
||||
// ============================================================
|
||||
// Admin SQL Query
|
||||
// ============================================================
|
||||
cJSON* db_execute_admin_query(const char* sql, char* error_msg, size_t error_size);
|
||||
|
||||
#endif // DB_OPS_H
|
||||
```
|
||||
|
||||
### Migration Strategy for Phase 1
|
||||
|
||||
The key principle: **change the interface, not the behavior**. Each function in `db_ops.c` initially just wraps the existing SQLite calls.
|
||||
|
||||
**Step-by-step for each file:**
|
||||
|
||||
1. **Create `db_ops.h` and `db_ops.c`** with the interface above
|
||||
2. **Implement each `db_ops` function** by moving the existing SQLite code from the source files into `db_ops.c`
|
||||
3. **Replace direct `sqlite3_*` calls** in each source file with `db_ops_*` calls
|
||||
4. **Remove `extern sqlite3* g_db`** from each file — only `db_ops.c` knows about `g_db`
|
||||
5. **Remove `#include <sqlite3.h>`** from each file — only `db_ops.c` includes it
|
||||
6. **Update Makefile** to compile `db_ops.c`
|
||||
|
||||
### Order of Migration (by risk, lowest first)
|
||||
|
||||
1. **`src/ip_ban.c`** — Self-contained, simple CRUD. Good warmup.
|
||||
2. **`src/subscriptions.c`** — Logging only, no critical path.
|
||||
3. **`src/nip009.c`** — Event deletion, small file.
|
||||
4. **`src/request_validator.c`** — Auth checks, small file.
|
||||
5. **`src/api.c`** — Stats/monitoring queries. Larger but read-only.
|
||||
6. **`src/dm_admin.c`** — Auth rules + WoT. Medium complexity.
|
||||
7. **`src/config.c`** — Config CRUD. Large but well-structured.
|
||||
8. **`src/websockets.c`** — COUNT queries, minimal DB usage.
|
||||
9. **`src/main.c`** — Event store/retrieve, REQ queries. The big one — do last.
|
||||
|
||||
### Testing Phase 1
|
||||
|
||||
After Phase 1, the relay should behave **identically** to before. Run:
|
||||
- `tests/run_all_tests.sh` — Full test suite
|
||||
- `tests/performance_benchmarks.sh` — Verify no performance regression
|
||||
- Manual testing with production-like traffic
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: SQLite Thread Pool
|
||||
|
||||
### Goal
|
||||
|
||||
Add a pool of N worker threads, each with its own `sqlite3*` connection to the same database file. SQLite WAL mode (already enabled) supports **concurrent readers**. The event loop dispatches database work to the pool and remains responsive.
|
||||
|
||||
### Key Design Points
|
||||
|
||||
1. **Read path**: REQ queries dispatched to thread pool. Each worker opens its own `sqlite3*` connection. SQLite WAL allows unlimited concurrent readers.
|
||||
|
||||
2. **Write path**: EVENT inserts go through a single dedicated writer thread. SQLite only allows one writer at a time anyway — this serializes writes cleanly.
|
||||
|
||||
3. **Result delivery**: Worker threads cannot call `lws_write()` directly (libwebsockets is not thread-safe). Instead, they push results into a per-session message queue and call `lws_cancel_service()` to wake the event loop, which then drains the queue.
|
||||
|
||||
4. **Connection lifecycle**: Each thread opens its own connection with `PRAGMA journal_mode=WAL` and `PRAGMA busy_timeout=5000`.
|
||||
|
||||
### What Changes in the Codebase
|
||||
|
||||
| Component | Current | Thread Pool |
|
||||
|-----------|---------|-------------|
|
||||
| [`g_db`](../src/main.c:49) | Single global connection | One per thread + writer connection |
|
||||
| [`handle_req_message()`](../src/main.c:1101) | Synchronous SQL in callback | Package filter into job, dispatch to pool |
|
||||
| [`store_event()`](../src/main.c:787) | Synchronous INSERT in callback | Dispatch to writer thread |
|
||||
| [`handle_count_message()`](../src/websockets.c:2863) | Synchronous COUNT in callback | Dispatch to pool |
|
||||
| Result delivery | Direct `queue_message()` | Worker pushes to queue + `lws_cancel_service()` |
|
||||
| Config reads | Direct `sqlite3_prepare` on `g_db` | Can stay synchronous with own connection or cache |
|
||||
|
||||
### New Files
|
||||
|
||||
- `src/thread_pool.h` — Thread pool interface
|
||||
- `src/thread_pool.c` — Thread pool implementation (job queue, worker lifecycle, result delivery)
|
||||
|
||||
### Thread Pool Interface (sketch)
|
||||
|
||||
```c
|
||||
// src/thread_pool.h
|
||||
#ifndef THREAD_POOL_H
|
||||
#define THREAD_POOL_H
|
||||
|
||||
typedef enum {
|
||||
JOB_TYPE_REQ_QUERY,
|
||||
JOB_TYPE_COUNT_QUERY,
|
||||
JOB_TYPE_STORE_EVENT,
|
||||
JOB_TYPE_DELETE_EVENT,
|
||||
} job_type_t;
|
||||
|
||||
typedef struct {
|
||||
job_type_t type;
|
||||
void* session; // lws per-session data pointer
|
||||
db_event_filter_t filter; // For REQ/COUNT jobs
|
||||
cJSON* event; // For STORE jobs
|
||||
char event_id[65]; // For DELETE jobs
|
||||
} db_job_t;
|
||||
|
||||
int thread_pool_init(int num_readers, const char* db_path);
|
||||
void thread_pool_shutdown(void);
|
||||
int thread_pool_submit_read(db_job_t* job);
|
||||
int thread_pool_submit_write(db_job_t* job);
|
||||
|
||||
#endif // THREAD_POOL_H
|
||||
```
|
||||
|
||||
### Performance Characteristics
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| **Concurrent reads** | N readers in parallel (N = thread count, typically 4–8) |
|
||||
| **Write throughput** | Same as current — SQLite serializes writes regardless |
|
||||
| **Event loop latency** | Near-zero — REQ no longer blocks the loop |
|
||||
| **Max theoretical read throughput** | ~4–8x current (limited by disk I/O, not CPU) |
|
||||
| **Memory overhead** | ~50–100 MB per connection (page cache) |
|
||||
| **Latency per query** | Same as current per-query, but no head-of-line blocking |
|
||||
|
||||
### Limitations
|
||||
|
||||
- **Write contention**: SQLite still allows only ONE writer at a time. With WAL, readers don't block writers and writers don't block readers, but two simultaneous writes will serialize. At the current write rate (~56 events/hour), this is a non-issue.
|
||||
- **Database size**: The 2.7 GB index bloat problem remains. Thread pool doesn't fix the schema — it fixes the concurrency.
|
||||
- **Scaling ceiling**: Beyond ~8 reader threads, diminishing returns due to disk I/O contention on a single SQLite file.
|
||||
- **Complexity**: Need a proper job queue, thread lifecycle management, and careful handling of the lws ↔ worker thread boundary.
|
||||
|
||||
---
|
||||
|
||||
## Risk Mitigation
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|-----------|
|
||||
| Phase 1 introduces bugs | Each file migrated independently, tested after each. Full test suite runs after each file. |
|
||||
| Thread pool race conditions | Worker threads only touch their own `sqlite3*` connection. Result delivery uses a mutex-protected queue. `lws_cancel_service()` is documented as thread-safe. |
|
||||
| Performance regression from abstraction | Abstraction layer is thin wrappers — no extra allocations or copies. Benchmark before/after. |
|
||||
| lws thread safety issues | Workers never call `lws_write()` directly. They push to a queue and wake the event loop. This is the documented pattern for libwebsockets multi-threading. |
|
||||
| Rollback needed | Phase 1 is a pure refactor — easy to revert. Phase 2 thread pool is additive — can be disabled with a compile flag. |
|
||||
|
||||
---
|
||||
|
||||
## Relationship to c-relay-pg
|
||||
|
||||
After Phase 1 (abstraction layer) is complete, the codebase is ready to be forked into a separate **c-relay-pg** project that replaces the SQLite backend with PostgreSQL. See [c-relay-pg plan](c_relay_pg_plan.md) for details.
|
||||
|
||||
The `db_ops.h` interface is designed to work with any backend:
|
||||
- **SQLite** (this plan): `db_result_next_json()` copies from `sqlite3_column_text()`
|
||||
- **PostgreSQL** (c-relay-pg): `db_result_next_json()` returns from `PQgetvalue()`
|
||||
- **LMDB** (future possibility): `db_result_next_json()` returns a zero-copy pointer into mmap'd memory
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,585 @@
|
||||
# Plan: Ban Idle and Early-Disconnect Connections
|
||||
|
||||
## Problem Statement
|
||||
|
||||
The relay needs to defend against spam connections that:
|
||||
1. Connect via WebSocket and sit idle doing nothing (the original problem)
|
||||
2. Connect and immediately disconnect without subscribing or posting (the new requirement)
|
||||
|
||||
Both patterns indicate bot/scanner behavior that should result in IP bans.
|
||||
|
||||
## Goal
|
||||
|
||||
Implement a system that:
|
||||
- Bans IPs that connect but never send a REQ or EVENT (regardless of how the connection ends)
|
||||
- Works **independent of NIP-42 authentication** (no auth required)
|
||||
- Allows legitimate users to connect and use the relay normally
|
||||
- Uses existing IP ban infrastructure
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Client connects] --> B{IP banned?}
|
||||
B -->|Yes| C[Reject immediately]
|
||||
B -->|No| D[Set idle timeout timer]
|
||||
D --> E{Client sends REQ or EVENT?}
|
||||
E -->|Yes| F[Mark session ACTIVE<br>Cancel idle timer]
|
||||
E -->|No| G{Connection closes?}
|
||||
G --> H{Session was ACTIVE?}
|
||||
H -->|Yes| I[Clean close - no ban]
|
||||
H -->|No| J["ip_ban_record_failure()<br>→ may trigger ban"]
|
||||
F --> K[Normal operation]
|
||||
K --> L[Connection closes]
|
||||
L --> I
|
||||
```
|
||||
|
||||
## Key Insight
|
||||
|
||||
The distinction between "idle timeout" and "early disconnect" is minimal:
|
||||
- **Idle timeout**: Connection closed by server because client never became ACTIVE
|
||||
- **Early disconnect**: Connection closed by client because client never became ACTIVE
|
||||
|
||||
Both result in the same check: `if (!session_was_active) ban_ip()`
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### 1. Add Session Activity Tracking
|
||||
|
||||
**File: `src/websockets.h`**
|
||||
|
||||
Add to `struct per_session_data`:
|
||||
|
||||
```c
|
||||
// Session activity tracking for idle connection banning
|
||||
int session_active; // 1 if client sent REQ or EVENT, 0 otherwise
|
||||
int idle_timeout_sec; // Timeout value for this session (copied from config)
|
||||
```
|
||||
|
||||
### 2. Set Idle Timeout on All Connections
|
||||
|
||||
**File: `src/websockets.c` - `LWS_CALLBACK_ESTABLISHED`**
|
||||
|
||||
Currently (lines 561-572):
|
||||
```c
|
||||
// Only set timeout if auth is required
|
||||
if (pss->nip42_auth_required_events || pss->nip42_auth_required_subscriptions) {
|
||||
int auth_timeout = get_config_int("nip42_auth_timeout_sec", 10);
|
||||
if (auth_timeout > 0) {
|
||||
lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_PING, auth_timeout);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Change to:
|
||||
```c
|
||||
// Initialize session activity tracking
|
||||
pss->session_active = 0;
|
||||
pss->idle_timeout_sec = get_config_int("idle_connection_timeout_sec", 30);
|
||||
|
||||
// Set idle timeout for ALL connections (not just auth-required)
|
||||
// This catches bots that connect and do nothing
|
||||
if (pss->idle_timeout_sec > 0) {
|
||||
lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_PING, pss->idle_timeout_sec);
|
||||
DEBUG_TRACE("Idle timeout set: %d seconds for connection from %s",
|
||||
pss->idle_timeout_sec, pss->client_ip);
|
||||
}
|
||||
|
||||
// Also set auth timeout if auth is required (separate concern)
|
||||
if (pss->nip42_auth_required_events || pss->nip42_auth_required_subscriptions) {
|
||||
int auth_timeout = get_config_int("nip42_auth_timeout_sec", 10);
|
||||
// Use the shorter of the two timeouts
|
||||
int effective_timeout = (pss->idle_timeout_sec > 0 && pss->idle_timeout_sec < auth_timeout)
|
||||
? pss->idle_timeout_sec
|
||||
: auth_timeout;
|
||||
if (effective_timeout > 0) {
|
||||
lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_PING, effective_timeout);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Mark Session as Active on Valid Activity
|
||||
|
||||
**File: `src/websockets.c` - `LWS_CALLBACK_RECEIVE`**
|
||||
|
||||
When processing REQ message (around line 1030):
|
||||
```c
|
||||
// Before handling REQ, mark session as active
|
||||
pthread_mutex_lock(&pss->session_lock);
|
||||
pss->session_active = 1;
|
||||
pthread_mutex_unlock(&pss->session_lock);
|
||||
|
||||
// Cancel idle timeout - this is legitimate usage
|
||||
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
|
||||
```
|
||||
|
||||
When processing EVENT message (around line 1380):
|
||||
```c
|
||||
// Before handling EVENT, mark session as active
|
||||
pthread_mutex_lock(&pss->session_lock);
|
||||
pss->session_active = 1;
|
||||
pthread_mutex_unlock(&pss->session_lock);
|
||||
|
||||
// Cancel idle timeout - this is legitimate usage
|
||||
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
|
||||
```
|
||||
|
||||
**Note**: We should also consider AUTH as "activity" since the client is engaging with the protocol:
|
||||
```c
|
||||
// In handle_nip42_auth_signed_event (nip042.c)
|
||||
// After successful auth, also mark session active
|
||||
pthread_mutex_lock(&pss->session_lock);
|
||||
pss->session_active = 1;
|
||||
pthread_mutex_unlock(&pss->session_lock);
|
||||
```
|
||||
|
||||
### 3.5. Separate Rate Limiting for Idle Connections
|
||||
|
||||
Per user feedback, idle/early-disconnect failures have **separate rate limiting** from auth failures. This requires:
|
||||
|
||||
**New Config Keys:**
|
||||
|
||||
| Config Key | Type | Default | Range | Description |
|
||||
|------------|------|---------|-------|-------------|
|
||||
| `idle_ban_threshold` | int | 3 | 1-100 | Idle failures before ban |
|
||||
| `idle_ban_window_sec` | int | 60 | 10-3600 | Window for counting idle failures |
|
||||
| `idle_ban_duration_sec` | int | 300 | 60-86400 | Initial idle ban duration |
|
||||
|
||||
**Add to `ip_ban_entry_t` in `src/ip_ban.c`:**
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
int state;
|
||||
char ip[46];
|
||||
|
||||
// Auth failure tracking (existing)
|
||||
int failure_count;
|
||||
time_t first_failure;
|
||||
time_t banned_until;
|
||||
int ban_count;
|
||||
|
||||
// NEW: Idle failure tracking (separate)
|
||||
int idle_failure_count;
|
||||
time_t idle_first_failure;
|
||||
time_t idle_banned_until;
|
||||
int idle_ban_count;
|
||||
|
||||
// Other existing fields...
|
||||
int has_authed_successfully;
|
||||
time_t last_success_at;
|
||||
int total_connections;
|
||||
int total_failures;
|
||||
int total_successes;
|
||||
time_t first_seen;
|
||||
} ip_ban_entry_t;
|
||||
```
|
||||
|
||||
**New function in `src/ip_ban.c`:**
|
||||
|
||||
```c
|
||||
// Record an idle/early-disconnect failure for an IP
|
||||
void ip_ban_record_idle_failure(const char* ip) {
|
||||
if (!ip || !g_initialized) return;
|
||||
if (!get_config_bool("idle_ban_enabled", 1)) return;
|
||||
|
||||
int threshold = get_config_int("idle_ban_threshold", 3);
|
||||
int window_sec = get_config_int("idle_ban_window_sec", 60);
|
||||
int ban_duration = get_config_int("idle_ban_duration_sec", 300);
|
||||
|
||||
pthread_mutex_lock(&g_ban_mutex);
|
||||
ip_ban_entry_t* entry = get_or_create_entry(ip);
|
||||
if (!entry) {
|
||||
pthread_mutex_unlock(&g_ban_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
time_t now = time(NULL);
|
||||
|
||||
// Reset window if expired
|
||||
if (entry->idle_first_failure > 0 && (now - entry->idle_first_failure) > window_sec) {
|
||||
entry->idle_failure_count = 0;
|
||||
entry->idle_first_failure = now;
|
||||
}
|
||||
if (entry->idle_first_failure == 0) {
|
||||
entry->idle_first_failure = now;
|
||||
}
|
||||
|
||||
entry->idle_failure_count++;
|
||||
entry->total_failures++;
|
||||
|
||||
DEBUG_TRACE("IP %s idle failure count: %d/%d", ip, entry->idle_failure_count, threshold);
|
||||
|
||||
if (entry->idle_failure_count >= threshold) {
|
||||
int duration = ban_duration;
|
||||
for (int i = 0; i < entry->idle_ban_count && duration < 86400; i++) {
|
||||
duration *= 2;
|
||||
}
|
||||
if (duration > 86400) duration = 86400;
|
||||
|
||||
entry->idle_banned_until = now + duration;
|
||||
entry->idle_ban_count++;
|
||||
entry->idle_failure_count = 0;
|
||||
entry->idle_first_failure = 0;
|
||||
|
||||
DEBUG_WARN("IP %s banned for %d seconds (idle ban #%d) after %d idle failures",
|
||||
ip, duration, entry->idle_ban_count, threshold);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_ban_mutex);
|
||||
}
|
||||
```
|
||||
|
||||
**Update `ip_ban_is_banned()` to check BOTH ban types:**
|
||||
|
||||
```c
|
||||
int ip_ban_is_banned(const char* ip) {
|
||||
if (!ip || !g_initialized) return 0;
|
||||
|
||||
pthread_mutex_lock(&g_ban_mutex);
|
||||
int idx = find_slot(ip);
|
||||
if (idx < 0 || g_ban_table[idx].state == IP_BAN_EMPTY) {
|
||||
pthread_mutex_unlock(&g_ban_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ip_ban_entry_t* entry = &g_ban_table[idx];
|
||||
time_t now = time(NULL);
|
||||
int banned = 0;
|
||||
|
||||
// Check auth ban (if enabled)
|
||||
if (get_config_bool("auth_fail_ban_enabled", 1) &&
|
||||
entry->banned_until > 0 && now < entry->banned_until) {
|
||||
banned = 1;
|
||||
}
|
||||
|
||||
// Check idle ban (if enabled)
|
||||
if (get_config_bool("idle_ban_enabled", 1) &&
|
||||
entry->idle_banned_until > 0 && now < entry->idle_banned_until) {
|
||||
banned = 1;
|
||||
}
|
||||
|
||||
// Clear expired bans
|
||||
if (!banned) {
|
||||
if (entry->banned_until > 0 && now >= entry->banned_until) {
|
||||
entry->banned_until = 0;
|
||||
entry->failure_count = 0;
|
||||
entry->first_failure = 0;
|
||||
}
|
||||
if (entry->idle_banned_until > 0 && now >= entry->idle_banned_until) {
|
||||
entry->idle_banned_until = 0;
|
||||
entry->idle_failure_count = 0;
|
||||
entry->idle_first_failure = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_ban_mutex);
|
||||
return banned;
|
||||
}
|
||||
```
|
||||
|
||||
**Update persistence:**
|
||||
- `ip_ban_load_from_db()`: Load idle_* fields from new columns
|
||||
- `ip_ban_save_to_db()`: Save idle_* fields to new columns
|
||||
- Add migration SQL to create new columns if they don't exist
|
||||
|
||||
**Add to `src/ip_ban.h`:**
|
||||
|
||||
```c
|
||||
// Record an idle/early-disconnect failure for an IP
|
||||
void ip_ban_record_idle_failure(const char* ip);
|
||||
```
|
||||
|
||||
### 4. Record Failure on Inactive Connection Close
|
||||
|
||||
**File: `src/websockets.c` - `LWS_CALLBACK_CLOSED`**
|
||||
|
||||
Currently (lines 2121-2128):
|
||||
```c
|
||||
// Record auth failure if connection closed while unauthenticated and auth was required
|
||||
if (!pss->authenticated &&
|
||||
(pss->nip42_auth_required_events || pss->nip42_auth_required_subscriptions) &&
|
||||
pss->auth_challenge_sent &&
|
||||
strlen(pss->client_ip) > 0) {
|
||||
ip_ban_record_failure(pss->client_ip);
|
||||
}
|
||||
```
|
||||
|
||||
Change to:
|
||||
```c
|
||||
// Record failure if connection closed without ever becoming active
|
||||
// This catches:
|
||||
// 1. Idle connections that timed out (never sent REQ/EVENT/AUTH)
|
||||
// 2. Early disconnects (client closed before sending REQ/EVENT/AUTH)
|
||||
if (!pss->session_active && strlen(pss->client_ip) > 0) {
|
||||
// Use separate idle failure recording (has its own threshold/duration)
|
||||
ip_ban_record_idle_failure(pss->client_ip);
|
||||
DEBUG_LOG("Recording idle/early-disconnect failure for IP %s (connected %ld seconds)",
|
||||
pss->client_ip,
|
||||
time(NULL) - pss->connection_established);
|
||||
}
|
||||
// Legacy: record auth failure if auth was required but not completed
|
||||
else if (!pss->authenticated &&
|
||||
(pss->nip42_auth_required_events || pss->nip42_auth_required_subscriptions) &&
|
||||
pss->auth_challenge_sent &&
|
||||
strlen(pss->client_ip) > 0) {
|
||||
ip_ban_record_failure(pss->client_ip);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Add Configuration Keys
|
||||
|
||||
**File: `src/config.c` - Add validation for new keys**
|
||||
|
||||
In the config validation section, add:
|
||||
|
||||
```c
|
||||
// Idle connection ban settings
|
||||
if (strcmp(key, "idle_connection_timeout_sec") == 0) {
|
||||
if (!is_valid_positive_integer(value)) {
|
||||
snprintf(error_msg, error_size, "invalid idle_connection_timeout_sec '%s' (must be positive integer)", value);
|
||||
return -1;
|
||||
}
|
||||
int timeout = atoi(value);
|
||||
if (timeout < 5 || timeout > 300) {
|
||||
snprintf(error_msg, error_size, "idle_connection_timeout_sec must be between 5 and 300 seconds");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(key, "idle_ban_enabled") == 0) {
|
||||
if (!is_valid_boolean(value)) {
|
||||
snprintf(error_msg, error_size, "invalid boolean value '%s' for idle_ban_enabled", value);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(key, "idle_ban_threshold") == 0) {
|
||||
if (!is_valid_positive_integer(value)) {
|
||||
snprintf(error_msg, error_size, "invalid idle_ban_threshold '%s' (must be positive integer)", value);
|
||||
return -1;
|
||||
}
|
||||
int threshold = atoi(value);
|
||||
if (threshold < 1 || threshold > 100) {
|
||||
snprintf(error_msg, error_size, "idle_ban_threshold must be between 1 and 100");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(key, "idle_ban_window_sec") == 0) {
|
||||
if (!is_valid_positive_integer(value)) {
|
||||
snprintf(error_msg, error_size, "invalid idle_ban_window_sec '%s' (must be positive integer)", value);
|
||||
return -1;
|
||||
}
|
||||
int window = atoi(value);
|
||||
if (window < 10 || window > 3600) {
|
||||
snprintf(error_msg, error_size, "idle_ban_window_sec must be between 10 and 3600");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(key, "idle_ban_duration_sec") == 0) {
|
||||
if (!is_valid_positive_integer(value)) {
|
||||
snprintf(error_msg, error_size, "invalid idle_ban_duration_sec '%s' (must be positive integer)", value);
|
||||
return -1;
|
||||
}
|
||||
int duration = atoi(value);
|
||||
if (duration < 60 || duration > 86400) {
|
||||
snprintf(error_msg, error_size, "idle_ban_duration_sec must be between 60 and 86400");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Also update the config introspection/documentation functions to describe these keys.
|
||||
|
||||
### 6. Add API Documentation
|
||||
|
||||
**File: `src/api.c` - Add to config metadata**
|
||||
|
||||
```c
|
||||
{"idle_connection_timeout_sec", "int", 5, 300},
|
||||
{"idle_ban_enabled", "bool", 0, 1},
|
||||
{"idle_ban_threshold", "int", 1, 100},
|
||||
{"idle_ban_window_sec", "int", 10, 3600},
|
||||
{"idle_ban_duration_sec", "int", 60, 86400},
|
||||
```
|
||||
|
||||
Add descriptions in the config query handler:
|
||||
```c
|
||||
} else if (strcmp(key, "idle_connection_timeout_sec") == 0) {
|
||||
description = "Seconds before an idle connection (no REQ/EVENT) is closed and IP flagged. 0 to disable.";
|
||||
} else if (strcmp(key, "idle_ban_enabled") == 0) {
|
||||
description = "Whether to ban IPs that repeatedly connect without sending REQ or EVENT.";
|
||||
} else if (strcmp(key, "idle_ban_threshold") == 0) {
|
||||
description = "Number of idle/early-disconnect failures before banning an IP.";
|
||||
} else if (strcmp(key, "idle_ban_window_sec") == 0) {
|
||||
description = "Time window in seconds for counting idle failures.";
|
||||
} else if (strcmp(key, "idle_ban_duration_sec") == 0) {
|
||||
description = "Initial ban duration in seconds for idle failures (doubles each time).";
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Update ip_ban Persistence and Cleanup
|
||||
|
||||
**File: `src/ip_ban.c` - Update `ip_ban_load_from_db()`**
|
||||
|
||||
Add migration SQL to create new columns if they don't exist, then load them:
|
||||
```sql
|
||||
ALTER TABLE ip_bans ADD COLUMN idle_failure_count INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE ip_bans ADD COLUMN idle_ban_count INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE ip_bans ADD COLUMN idle_banned_until INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE ip_bans ADD COLUMN idle_first_failure INTEGER NOT NULL DEFAULT 0;
|
||||
```
|
||||
|
||||
**File: `src/ip_ban.c` - Update `ip_ban_save_to_db()`**
|
||||
|
||||
Add the idle_* fields to the INSERT/REPLACE statement.
|
||||
|
||||
**File: `src/ip_ban.c` - Update `ip_ban_cleanup()`**
|
||||
|
||||
Add cleanup logic for idle ban entries (same pattern as auth ban cleanup).
|
||||
|
||||
**File: `src/ip_ban.c` - Update `ip_ban_log_stats()`**
|
||||
|
||||
Add idle ban count to the periodic log summary:
|
||||
```c
|
||||
DEBUG_WARN("IP BAN SUMMARY: %d auth-banned, %d idle-banned, %d tracked, %d trusted",
|
||||
auth_banned_count, idle_banned_count, tracked_count, trusted_count);
|
||||
```
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
### Idle Connection Settings
|
||||
|
||||
| Config Key | Type | Default | Range | Description |
|
||||
|------------|------|---------|-------|-------------|
|
||||
| `idle_connection_timeout_sec` | int | 30 | 5-300 | Seconds to wait for REQ/EVENT before closing connection. 0 = disable idle timeout. |
|
||||
| `idle_ban_enabled` | bool | true | true/false | Whether to ban IPs with repeated idle/early-disconnect failures. |
|
||||
| `idle_ban_threshold` | int | 3 | 1-100 | Idle failures before ban. |
|
||||
| `idle_ban_window_sec` | int | 60 | 10-3600 | Window for counting idle failures. |
|
||||
| `idle_ban_duration_sec` | int | 300 | 60-86400 | Initial idle ban duration. |
|
||||
|
||||
### Auth Failure Settings (Existing)
|
||||
|
||||
| Config Key | Type | Default | Range | Description |
|
||||
|------------|------|---------|-------|-------------|
|
||||
| `auth_fail_ban_enabled` | bool | true | true/false | Whether to ban IPs with failed auth. |
|
||||
| `auth_fail_ban_threshold` | int | 3 | 1-100 | Auth failures before ban. |
|
||||
| `auth_fail_window_sec` | int | 60 | 10-3600 | Window for counting auth failures. |
|
||||
| `auth_fail_ban_duration_sec` | int | 300 | 60-86400 | Initial auth ban duration. |
|
||||
|
||||
## Behavior Matrix
|
||||
|
||||
| Scenario | idle_timeout_sec | idle_ban_enabled | Result |
|
||||
|----------|------------------|------------------|--------|
|
||||
| Connect → do nothing → timeout | >0 | true | Connection closed, IP failure recorded, may be banned |
|
||||
| Connect → do nothing → timeout | >0 | false | Connection closed, no ban |
|
||||
| Connect → immediate disconnect | any | true | IP failure recorded, may be banned |
|
||||
| Connect → immediate disconnect | any | false | No ban |
|
||||
| Connect → send REQ | any | any | Session active, no ban |
|
||||
| Connect → send EVENT | any | any | Session active, no ban |
|
||||
| Connect → send AUTH (NIP-42) | any | any | Session active, no ban |
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
1. **Idle timeout test**: Connect via wscat, wait 30 seconds, verify disconnect and ban table entry
|
||||
2. **Early disconnect test**: Connect via wscat, immediately Ctrl-C, verify ban table entry
|
||||
3. **Active session test**: Connect, send REQ, disconnect immediately, verify NO ban
|
||||
4. **Config disable test**: Set `idle_ban_enabled=false`, repeat test 1, verify no ban
|
||||
5. **Timeout config test**: Set `idle_connection_timeout_sec=5`, verify faster disconnect
|
||||
|
||||
## Files to Modify
|
||||
|
||||
| File | Change Type |
|
||||
|------|-------------|
|
||||
| `src/websockets.h` | Add `session_active` and `idle_timeout_sec` fields to `per_session_data` |
|
||||
| `src/websockets.c` ~565 | Set idle timeout on ALL connections in `LWS_CALLBACK_ESTABLISHED` |
|
||||
| `src/websockets.c` ~1030 | Mark `session_active=1` and cancel idle timer on REQ |
|
||||
| `src/websockets.c` ~1380 | Mark `session_active=1` and cancel idle timer on EVENT |
|
||||
| `src/websockets.c` ~2121 | Call `ip_ban_record_idle_failure()` for inactive sessions in `LWS_CALLBACK_CLOSED` |
|
||||
| `src/nip042.c` ~130 | Mark `session_active=1` on successful AUTH |
|
||||
| `src/ip_ban.h` | Add `ip_ban_record_idle_failure()` declaration |
|
||||
| `src/ip_ban.c` | Add idle_* fields to `ip_ban_entry_t`, new `ip_ban_record_idle_failure()` function, update `ip_ban_is_banned()` to check both ban types, update persistence and cleanup |
|
||||
| `src/config.c` ~987+ | Add validation for 5 new idle_* config keys |
|
||||
| `src/api.c` ~664 | Add config metadata entries for idle_* keys |
|
||||
|
||||
## Deployment on Existing Server
|
||||
|
||||
### Config Keys: No Manual SQL Required
|
||||
|
||||
All config keys use [`get_config_int()`](src/config.c:300) and [`get_config_bool()`](src/config.c:313) which return **hardcoded defaults** when a key doesn't exist in the config table. The new code will work immediately with these defaults:
|
||||
|
||||
- `idle_connection_timeout_sec` → defaults to 30
|
||||
- `idle_ban_enabled` → defaults to true
|
||||
- `idle_ban_threshold` → defaults to 3
|
||||
- `idle_ban_window_sec` → defaults to 60
|
||||
- `idle_ban_duration_sec` → defaults to 300
|
||||
|
||||
If you want to **override** any defaults, you can insert them into the config table. From the same directory as the `.db` file:
|
||||
|
||||
```bash
|
||||
# Find your database file
|
||||
DB_FILE=$(ls *.db | head -1)
|
||||
|
||||
# Optional: Insert custom values (only if you want non-default settings)
|
||||
sqlite3 "$DB_FILE" "INSERT OR REPLACE INTO config (key, value) VALUES ('idle_connection_timeout_sec', '30');"
|
||||
sqlite3 "$DB_FILE" "INSERT OR REPLACE INTO config (key, value) VALUES ('idle_ban_enabled', 'true');"
|
||||
sqlite3 "$DB_FILE" "INSERT OR REPLACE INTO config (key, value) VALUES ('idle_ban_threshold', '3');"
|
||||
sqlite3 "$DB_FILE" "INSERT OR REPLACE INTO config (key, value) VALUES ('idle_ban_window_sec', '60');"
|
||||
sqlite3 "$DB_FILE" "INSERT OR REPLACE INTO config (key, value) VALUES ('idle_ban_duration_sec', '300');"
|
||||
```
|
||||
|
||||
Or change them via the admin API/web UI after deployment.
|
||||
|
||||
### ip_bans Table: Automatic Migration
|
||||
|
||||
The `ip_bans` table needs 4 new columns for idle tracking. The updated [`ip_ban_load_from_db()`](src/ip_ban.c:93) will run `ALTER TABLE` statements automatically on startup. These are safe because SQLite's `ALTER TABLE ADD COLUMN` is a no-op if the column already exists (we'll use error-tolerant execution).
|
||||
|
||||
If you prefer to run the migration manually before deploying:
|
||||
|
||||
```bash
|
||||
DB_FILE=$(ls *.db | head -1)
|
||||
|
||||
sqlite3 "$DB_FILE" <<'SQL'
|
||||
ALTER TABLE ip_bans ADD COLUMN idle_failure_count INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE ip_bans ADD COLUMN idle_ban_count INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE ip_bans ADD COLUMN idle_banned_until INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE ip_bans ADD COLUMN idle_first_failure INTEGER NOT NULL DEFAULT 0;
|
||||
SQL
|
||||
```
|
||||
|
||||
**Note:** SQLite will error on `ALTER TABLE ADD COLUMN` if the column already exists, but the code will handle this gracefully (ignore the error). Running it manually is optional — the relay will do it on startup.
|
||||
|
||||
### Deployment Steps
|
||||
|
||||
1. Build and deploy with `deploy_lt.sh` as usual
|
||||
2. The relay starts, `ip_ban_load_from_db()` auto-migrates the `ip_bans` table
|
||||
3. Config keys use defaults immediately — no manual SQL needed
|
||||
4. Optionally tune settings via admin API or direct SQL
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
- Default `idle_connection_timeout_sec=30` provides immediate protection
|
||||
- Default `idle_ban_enabled=true` maintains current spam protection behavior
|
||||
- Setting `idle_connection_timeout_sec=0` restores old behavior (no idle timeout)
|
||||
- Setting `idle_ban_enabled=false` allows connections without banning (for debugging)
|
||||
- Existing auth-based banning continues to work independently with its own thresholds
|
||||
- Database migration adds new columns with defaults — existing ban data preserved
|
||||
|
||||
## Summary
|
||||
|
||||
This plan implements a unified "session activity" tracking system with **separate rate limiting** for idle vs auth failures:
|
||||
|
||||
1. **Catches idle connections** via `lws_set_timeout()` on ALL connections (not just auth-required)
|
||||
2. **Catches early disconnects** via the same `!session_active` check on close
|
||||
3. **Respects legitimate users** who actually use the relay (REQ/EVENT/AUTH marks session active)
|
||||
4. **Separate idle ban tracking** — idle failures have their own threshold, window, and duration independent of auth failures
|
||||
5. **Extends existing ban infrastructure** — adds idle_* fields to `ip_ban_entry_t`, unified `ip_ban_is_banned()` checks both ban types
|
||||
6. **Fully configurable** — 5 new config keys for idle banning, all tunable via admin events
|
||||
|
||||
The key insight is that there's no meaningful difference between "idle timeout" and "early disconnect" — both indicate a client that connected but never actually used the relay. The same `!session_active` check handles both cases, and the separate rate limiting ensures idle bots don't interfere with auth failure tracking for legitimate clients who fail NIP-42.
|
||||
@@ -0,0 +1,194 @@
|
||||
# Main Thread CPU Offload Plan
|
||||
|
||||
## Problem Statement
|
||||
|
||||
The main `c_relay` thread consumes ~56% CPU while the DB worker threads (`db-read-1..4`, `db-write`) sit near 0%. All protocol handling, JSON parsing, event validation, subscription matching, and message queueing happens synchronously inside `nostr_relay_callback()` on the main libwebsockets event-loop thread.
|
||||
|
||||
## Current Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
LWS[lws_service - main thread] --> CB[nostr_relay_callback]
|
||||
CB --> PARSE[JSON parse - cJSON_Parse]
|
||||
CB --> VALIDATE[Signature verify - nostr_validate_unified_request]
|
||||
CB --> STORE[store_event - builds payload]
|
||||
STORE --> SYNC_WRITE[thread_pool_execute_store_event_sync]
|
||||
SYNC_WRITE --> |blocks main thread| DB_WRITE[db-write thread]
|
||||
DB_WRITE --> |signal| SYNC_WRITE
|
||||
CB --> REQ[handle_req_message - builds SQL]
|
||||
REQ --> SYNC_READ[thread_pool_execute_req_sync]
|
||||
SYNC_READ --> |blocks main thread| DB_READ[db-read-N thread]
|
||||
DB_READ --> |signal| SYNC_READ
|
||||
CB --> BROADCAST[broadcast_event_to_subscriptions]
|
||||
BROADCAST --> QUEUE[queue_message per subscriber]
|
||||
CB --> WRITEABLE[LWS_CALLBACK_SERVER_WRITEABLE]
|
||||
WRITEABLE --> DRAIN[process_message_queue - lws_write]
|
||||
```
|
||||
|
||||
### Why the main thread is hot
|
||||
|
||||
| Work item | Where | Cost |
|
||||
|-----------|-------|------|
|
||||
| JSON parsing | `cJSON_Parse` in `LWS_CALLBACK_RECEIVE` | Medium - per message |
|
||||
| Signature verification | `nostr_validate_unified_request` - ed25519 crypto | **High** - per EVENT |
|
||||
| SQL query building | `handle_req_message` filter-to-SQL loop | Low-Medium |
|
||||
| Sync DB wait | `thread_pool_execute_*_sync` - pthread_cond_wait | Blocks but yields CPU |
|
||||
| Result iteration + expiration check | Row loop in `handle_req_message` with `cJSON_Parse` per row | Medium-High for large result sets |
|
||||
| Subscription matching | `broadcast_event_to_subscriptions` - filter matching | Medium - scales with subscriber count |
|
||||
| Message serialization + queueing | `snprintf` + `queue_message_take_ownership` per subscriber | Medium |
|
||||
| Config lookups | `get_config_int/bool` called repeatedly in hot paths | Low but frequent |
|
||||
|
||||
### Key constraint: libwebsockets is single-threaded
|
||||
|
||||
libwebsockets requires that **all `lws_write`, `lws_callback_on_writable`, and `lws_close_reason` calls happen from the service thread** (the thread running `lws_service`). This means we cannot directly write to WebSocket connections from worker threads. However, we **can** do computation on worker threads and post results back to the main thread for I/O.
|
||||
|
||||
## Offload Strategy
|
||||
|
||||
### Phase 1: Async EVENT Processing (Highest Impact)
|
||||
|
||||
Convert EVENT handling from synchronous to async. Currently the main thread does: parse → validate → store → broadcast, all blocking. Instead:
|
||||
|
||||
1. **Main thread**: Parse JSON (fast), extract event ID for dedup check, then submit a job to a new "event processing" worker thread
|
||||
2. **Worker thread**: Signature verification (expensive crypto), store_event (already goes to DB thread), prepare broadcast payload
|
||||
3. **Main thread callback**: Receive result via `wake_loop_cb` + `lws_cancel_service`, send OK response and broadcast to subscribers
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
LWS[lws_service - main thread] --> RECEIVE[LWS_CALLBACK_RECEIVE]
|
||||
RECEIVE --> PARSE[JSON parse + dedup check]
|
||||
PARSE --> SUBMIT[Submit to event-worker queue]
|
||||
SUBMIT --> LWS
|
||||
|
||||
WORKER[event-worker thread] --> VERIFY[Signature verification]
|
||||
VERIFY --> STORE_DB[store_event via DB pool]
|
||||
STORE_DB --> PREP[Prepare broadcast payload]
|
||||
PREP --> RESULT_Q[Push result to completion queue]
|
||||
RESULT_Q --> WAKE[lws_cancel_service]
|
||||
|
||||
LWS2[lws_service wakes] --> POLL[Poll completion queue]
|
||||
POLL --> OK[Send OK response via queue_message]
|
||||
POLL --> BCAST[broadcast_event_to_subscriptions]
|
||||
```
|
||||
|
||||
**What this offloads**: ed25519 signature verification (~the most expensive per-event operation), event classification, tag serialization, and the synchronous DB store wait.
|
||||
|
||||
**What stays on main thread**: JSON parse (needed to extract event ID for dedup), OK response writing, broadcast fan-out (requires lws access).
|
||||
|
||||
### Phase 2: Async REQ Query Execution (Medium Impact)
|
||||
|
||||
Convert REQ handling from sync to async:
|
||||
|
||||
1. **Main thread**: Parse filters, build SQL, create subscription, submit query job
|
||||
2. **DB reader thread**: Execute query (already happens, but currently blocks main thread via `_sync`)
|
||||
3. **Main thread callback**: Iterate results, queue EVENT messages, send EOSE
|
||||
|
||||
This is simpler than Phase 1 because the thread pool already supports async submission via `thread_pool_submit_read` with a `result_cb`. The `_sync` wrappers just add a condvar wait on top. We need to:
|
||||
- Use `thread_pool_submit_read` directly instead of `thread_pool_execute_req_sync`
|
||||
- Store pending REQ context (sub_id, wsi, pss) so the callback can complete the work
|
||||
- In the result callback, push results to a completion queue and call `lws_cancel_service`
|
||||
- On the main thread, drain the completion queue and send EVENT + EOSE messages
|
||||
|
||||
### Phase 3: Reduce Per-Row Overhead in REQ Results (Low-Medium Impact)
|
||||
|
||||
Currently each row from a REQ query gets `cJSON_Parse` just to check NIP-40 expiration. This is wasteful:
|
||||
|
||||
- Option A: Add an `expiration` column to the events table so expiration filtering can be done in SQL
|
||||
- Option B: Store expiration timestamp in a fast-parse format (extract during INSERT, store as integer column)
|
||||
- Option C: Use string search on the raw JSON for the expiration tag instead of full parse
|
||||
|
||||
### Phase 4: Config Value Caching (Low Impact, Easy Win)
|
||||
|
||||
`get_config_int` and `get_config_bool` are called on every message in hot paths. These do SQLite queries. Cache config values in memory with a TTL or invalidation signal, so the main loop only refreshes them periodically (already done for `debug_level` every 60s — extend to all hot-path config values).
|
||||
|
||||
## Implementation Priority
|
||||
|
||||
| Phase | Impact | Risk | Complexity |
|
||||
|-------|--------|------|------------|
|
||||
| Phase 1: Async EVENT | **High** - removes crypto from main thread | Medium - async state management | Medium-High |
|
||||
| Phase 2: Async REQ | **Medium** - unblocks main thread during queries | Low - infrastructure exists | Medium |
|
||||
| Phase 3: Expiration optimization | **Low-Medium** - reduces per-row parse cost | Low | Low |
|
||||
| Phase 4: Config caching | **Low** - reduces DB round-trips | Very Low | Low |
|
||||
|
||||
## Detailed Design: Phase 1 (Async EVENT Processing)
|
||||
|
||||
### New Components
|
||||
|
||||
#### Completion Queue (`src/completion_queue.h/.c`)
|
||||
A thread-safe FIFO queue for posting results from worker threads back to the main thread:
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
int type; // COMPLETION_TYPE_EVENT_RESULT, COMPLETION_TYPE_REQ_RESULT, etc.
|
||||
void* data; // Type-specific result data
|
||||
struct lws* wsi; // Target WebSocket connection
|
||||
void* pss; // Per-session data
|
||||
} completion_item_t;
|
||||
|
||||
int completion_queue_init(void);
|
||||
int completion_queue_push(completion_item_t* item);
|
||||
completion_item_t* completion_queue_pop(void); // Non-blocking
|
||||
void completion_queue_shutdown(void);
|
||||
```
|
||||
|
||||
#### Event Worker Thread
|
||||
A dedicated pthread that processes EVENT validation/storage:
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
cJSON* event; // Parsed event JSON - ownership transferred
|
||||
cJSON* full_message; // Full message JSON for context
|
||||
struct lws* wsi;
|
||||
void* pss;
|
||||
char sub_id[64]; // For response routing
|
||||
} event_work_item_t;
|
||||
```
|
||||
|
||||
#### Main Loop Integration
|
||||
Add a completion queue drain step to the main event loop. After `lws_service` returns (either from timeout or `lws_cancel_service` wake), check the completion queue:
|
||||
|
||||
```c
|
||||
while (g_server_running && !g_shutdown_flag) {
|
||||
int result = lws_service(ws_context, 1000);
|
||||
|
||||
// NEW: Drain completion queue
|
||||
completion_item_t* item;
|
||||
while ((item = completion_queue_pop()) != NULL) {
|
||||
process_completion(item); // Send OK, broadcast, etc.
|
||||
free(item);
|
||||
}
|
||||
|
||||
// ... existing periodic checks ...
|
||||
}
|
||||
```
|
||||
|
||||
### Changes to Existing Code
|
||||
|
||||
1. **`nostr_relay_callback` EVENT path**: After JSON parse and dedup check, instead of calling `nostr_validate_unified_request` + `store_event` + `broadcast_event_to_subscriptions` synchronously, submit an `event_work_item_t` to the event worker queue and return 0 immediately.
|
||||
|
||||
2. **`store_event`**: No changes needed — it already uses `thread_pool_execute_store_event_sync` which will run on the DB writer thread. The event worker thread will call it.
|
||||
|
||||
3. **`broadcast_event_to_subscriptions`**: No changes needed — it will be called from the main thread when processing the completion item, which is correct since it calls `queue_message_take_ownership` (requires lws thread).
|
||||
|
||||
### Thread Safety Considerations
|
||||
|
||||
- The `cJSON* event` object must be fully owned by the worker thread during processing. The main thread must not access it after submission.
|
||||
- The `wsi` and `pss` pointers could become invalid if the client disconnects while the event is being processed. The completion handler must validate that the connection is still alive before sending the OK response.
|
||||
- A generation counter or epoch on `pss` can detect stale references.
|
||||
|
||||
## What Cannot Be Offloaded
|
||||
|
||||
- **`lws_write` / `queue_message`**: Must happen on the lws service thread
|
||||
- **`lws_callback_on_writable`**: Must happen on the lws service thread
|
||||
- **`lws_close_reason`**: Must happen on the lws service thread
|
||||
- **Subscription list iteration for broadcast**: Accesses `lws_wsi_user` which is lws-internal
|
||||
|
||||
These are fundamental libwebsockets constraints. The pattern is always: do computation off-thread, post result to completion queue, wake main thread, do I/O on main thread.
|
||||
|
||||
## Expected Impact
|
||||
|
||||
With Phase 1 alone, the main thread would no longer perform:
|
||||
- ed25519 signature verification (~100-500μs per event depending on CPU)
|
||||
- Synchronous DB store wait (~50-200μs per event)
|
||||
- Event classification, tag serialization, JSON serialization for storage
|
||||
|
||||
This should reduce main-thread CPU by roughly 30-50% for EVENT-heavy workloads, shifting that work to the event worker thread and DB threads.
|
||||
@@ -0,0 +1,207 @@
|
||||
# C-Relay Memory Leak Investigation & Fix Plan
|
||||
|
||||
## Problem Statement
|
||||
|
||||
c-relay reached **1.56 GB** of its 1.5 GB `MemoryHigh` cgroup limit after only **1 hour 37 minutes** of runtime. The kernel is actively throttling the process with swap pressure (1.8M swap used). This strongly indicates one or more memory leaks causing unbounded growth.
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
After thorough code review, I identified **three categories** of memory issues:
|
||||
|
||||
1. **Confirmed `get_config_value()` leaks** — the most pervasive issue
|
||||
2. **Potential structural leaks** in subscription/connection lifecycle
|
||||
3. **Stack pressure** from large stack-allocated arrays
|
||||
|
||||
---
|
||||
|
||||
## Category 1: `get_config_value()` Leaks (HIGH SEVERITY — Most Likely Root Cause)
|
||||
|
||||
### The Core Problem
|
||||
|
||||
[`get_config_value()`](src/config.c:293) calls [`get_config_value_from_table()`](src/config.c:1690) which returns `strdup(value)` — a **heap-allocated string that the caller must free**. Many call sites treat the return value as a borrowed pointer and never free it.
|
||||
|
||||
**Every unfree'd call leaks ~64-256 bytes per invocation.** On a busy relay processing hundreds of events/second, this accumulates to GB-scale leaks within hours.
|
||||
|
||||
### Confirmed Leak Sites
|
||||
|
||||
#### `websockets.c` — Called on EVERY incoming event
|
||||
|
||||
| Line | Code | Frequency | Severity |
|
||||
|------|------|-----------|----------|
|
||||
| [1527](src/websockets.c:1527) | `get_config_value("relay_pubkey")` — auth bypass check for kind 14 DMs | Every kind-14 event | **HIGH** |
|
||||
| [1549](src/websockets.c:1549) | `get_config_value("admin_pubkey")` — auth bypass check for kind 23456 | Every kind-23456 event | **MEDIUM** |
|
||||
| [2704](src/websockets.c:2704) | `get_config_value("relay_pubkey")` — DM stats command processing | Every DM to relay | **MEDIUM** |
|
||||
| [2742](src/websockets.c:2742) | `get_config_value("admin_pubkey")` — DM admin check | Every DM to relay | **MEDIUM** |
|
||||
|
||||
#### `main.c` — Called on EVERY admin event check
|
||||
|
||||
| Line | Code | Frequency | Severity |
|
||||
|------|------|-----------|----------|
|
||||
| [1718](src/main.c:1718) | `get_config_value("relay_pubkey")` — inside a loop iterating tags | Every admin event, per tag | **CRITICAL** |
|
||||
| [1742](src/main.c:1742) | `get_config_value("admin_pubkey")` — admin authorization | Every admin event | **HIGH** |
|
||||
|
||||
#### `config.c` — Called on EVERY event kind check
|
||||
|
||||
| Line | Code | Frequency | Severity |
|
||||
|------|------|-----------|----------|
|
||||
| [377](src/config.c:377) | `get_config_value("nip42_auth_required_kinds")` — NIP-42 kind check | Every incoming event | **CRITICAL** |
|
||||
|
||||
#### `ip_ban.c` — Called on EVERY ban check
|
||||
|
||||
| Line | Code | Frequency | Severity |
|
||||
|------|------|-----------|----------|
|
||||
| [255](src/ip_ban.c:255) | `get_config_value("idle_ban_whitelist")` — whitelist check | Every connection check | **HIGH** |
|
||||
|
||||
#### `nip042.c` — Called on every auth verification
|
||||
|
||||
| Line | Code | Frequency | Severity |
|
||||
|------|------|-----------|----------|
|
||||
| [99](src/nip042.c:99) | `get_config_value("relay_url")` — relay URL for auth verification | Every NIP-42 auth | **MEDIUM** |
|
||||
|
||||
#### `request_validator.c` — Called on every auth rules check
|
||||
|
||||
| Line | Code | Frequency | Severity |
|
||||
|------|------|-----------|----------|
|
||||
| [208](src/request_validator.c:208) | `get_config_value("auth_enabled")` — properly freed | N/A | OK |
|
||||
| [216](src/request_validator.c:216) | `get_config_value("auth_rules_enabled")` — properly freed | N/A | OK |
|
||||
| [304](src/request_validator.c:304) | `get_config_value("admin_pubkey")` — NOT freed | Every event validation | **HIGH** |
|
||||
| [320](src/request_validator.c:320) | `get_config_value("nip42_auth_enabled")` — NOT freed | Every event validation | **HIGH** |
|
||||
|
||||
### Files That DO Free Correctly (for reference)
|
||||
|
||||
- [`nip011.c`](src/nip011.c:127) — Properly frees all `get_config_value()` results
|
||||
- [`nip013.c`](src/nip013.c:43) — Properly frees `pow_mode`
|
||||
- [`request_validator.c`](src/request_validator.c:191) — Properly frees `nip42_timeout` and `nip42_tolerance`
|
||||
|
||||
### Estimated Impact
|
||||
|
||||
On a relay processing ~100 events/second:
|
||||
- `is_nip42_auth_required_for_kind()` leaks ~100-200 bytes × 100/sec = **~10-20 KB/sec**
|
||||
- `ip_is_whitelisted()` leaks ~100 bytes × connections/sec
|
||||
- `is_authorized_admin_event()` leaks inside tag loop — potentially **multiple leaks per event**
|
||||
- Combined: **~50-100 KB/sec → ~180-360 MB/hour** — consistent with the observed 1.56 GB in 97 minutes
|
||||
|
||||
---
|
||||
|
||||
## Category 2: Structural Lifecycle Leaks (MEDIUM SEVERITY)
|
||||
|
||||
### 2a. `LWS_CALLBACK_CLOSED` — Inactive Subscription Skip
|
||||
|
||||
In [`websockets.c:2339`](src/websockets.c:2339), the cleanup handler only collects subscription IDs where `sub->active` is true:
|
||||
|
||||
```c
|
||||
if (sub->active) { // Only process active subscriptions
|
||||
temp_sub_id_t* temp = malloc(sizeof(temp_sub_id_t));
|
||||
```
|
||||
|
||||
If a subscription was marked inactive by another thread between the time it was added and the connection close, it will be **skipped** — never removed from the global manager, never freed. The subscription object, its filters, and all cJSON objects within leak permanently.
|
||||
|
||||
### 2b. `connection_list_remove` Potential
|
||||
|
||||
The `connection_list_remove(wsi)` call at [`websockets.c:2252`](src/websockets.c:2252) happens before pss cleanup. Need to verify the connection list implementation doesn't hold references that prevent cleanup.
|
||||
|
||||
### 2c. `ip_connection_info_t` Leak on Disconnect
|
||||
|
||||
The per-IP connection tracking in [`subscriptions.h:79`](src/subscriptions.h:79) creates `ip_connection_info_t` nodes via `calloc`. These are only removed by `remove_ip_connection()` — need to verify this is called on every disconnect path.
|
||||
|
||||
---
|
||||
|
||||
## Category 3: Stack Pressure (LOW SEVERITY but notable)
|
||||
|
||||
### 3a. `candidates_to_check` Stack Array
|
||||
|
||||
In [`subscriptions.c:810`](src/subscriptions.c:810):
|
||||
|
||||
```c
|
||||
subscription_t* candidates_to_check[MAX_TOTAL_SUBSCRIPTIONS]; // 5000 × 8 bytes = 40 KB
|
||||
```
|
||||
|
||||
This allocates **40 KB on the stack** for every `broadcast_event_to_subscriptions()` call. While not a heap leak, it contributes to high memory pressure under concurrent load.
|
||||
|
||||
### 3b. `added_kinds` Bitmap
|
||||
|
||||
In [`subscriptions.c:68`](src/subscriptions.c:68):
|
||||
|
||||
```c
|
||||
unsigned char added_kinds[8192] = {0}; // 8 KB on stack
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fix Implementation Plan
|
||||
|
||||
### Phase 1: Fix `get_config_value()` Leaks (Highest Impact)
|
||||
|
||||
**Strategy A — Preferred: Add a config cache layer**
|
||||
|
||||
Instead of fixing 20+ call sites, add a **cached config system** that reads values once and caches them in memory, invalidating on config change events. This eliminates both the leak AND the repeated SQLite queries per event.
|
||||
|
||||
```
|
||||
Config Cache Architecture:
|
||||
- Static hash table of key-value pairs
|
||||
- Populated at startup and on config change events
|
||||
- get_config_value_cached() returns const pointer to cached value (no allocation)
|
||||
- Existing get_config_value() kept for dynamic/rare lookups
|
||||
```
|
||||
|
||||
**Strategy B — Quick fix: Free at every call site**
|
||||
|
||||
Add `free((char*)result)` after every `get_config_value()` call that doesn't already free. This is more error-prone but faster to implement.
|
||||
|
||||
**Recommendation: Implement Strategy A for hot-path values (relay_pubkey, admin_pubkey, auth settings), Strategy B for cold-path values.**
|
||||
|
||||
### Phase 2: Fix Structural Leaks
|
||||
|
||||
1. **Fix inactive subscription skip in CLOSED handler** — Remove the `if (sub->active)` guard, or add a second pass that also collects inactive subscriptions for cleanup
|
||||
2. **Verify `remove_ip_connection()` is called on all disconnect paths**
|
||||
3. **Add defensive cleanup** — periodic sweep of orphaned subscriptions
|
||||
|
||||
### Phase 3: Add Memory Monitoring
|
||||
|
||||
1. **Add a `/stats` endpoint** that reports:
|
||||
- Current RSS/VSZ from `/proc/self/status`
|
||||
- Active subscription count
|
||||
- Message queue depth across all sessions
|
||||
- Config cache hit/miss ratio
|
||||
2. **Add periodic memory logging** to the service loop
|
||||
3. **Consider integrating jemalloc** for better allocation tracking in production
|
||||
|
||||
### Phase 4: Reduce Stack Pressure
|
||||
|
||||
1. Move `candidates_to_check` to heap allocation with `malloc`/`free`
|
||||
2. Consider reducing `MAX_TOTAL_SUBSCRIPTIONS` or using a dynamic array
|
||||
|
||||
---
|
||||
|
||||
## Verification Strategy
|
||||
|
||||
1. **Build with AddressSanitizer** (`-fsanitize=address`) for development testing
|
||||
2. **Run under Valgrind** with `--leak-check=full` for comprehensive leak detection
|
||||
3. **Monitor RSS over time** after fixes — should plateau rather than grow linearly
|
||||
4. **Load test** with `tests/load_tests.sh` while monitoring memory
|
||||
|
||||
---
|
||||
|
||||
## Immediate Mitigation
|
||||
|
||||
While fixes are being implemented:
|
||||
1. **Raise `MemoryHigh`** to 2 GB in the systemd unit to prevent throttling
|
||||
2. **Add a cron job** to restart the service every 12-24 hours
|
||||
3. **Monitor with**: `watch -n 5 'cat /proc/$(pgrep c_relay)/status | grep -E "VmRSS|VmSwap"'`
|
||||
|
||||
---
|
||||
|
||||
## Implementation Priority Order
|
||||
|
||||
| Priority | Task | Impact |
|
||||
|----------|------|--------|
|
||||
| P0 | Fix `is_nip42_auth_required_for_kind()` leak in config.c:377 | Called on every event |
|
||||
| P0 | Fix `ip_is_whitelisted()` leak in ip_ban.c:255 | Called on every connection check |
|
||||
| P0 | Fix `is_authorized_admin_event()` leaks in main.c:1718,1742 | Called per admin event, leaks in loop |
|
||||
| P1 | Fix websockets.c:1527,1549 auth bypass leaks | Called on every event with auth |
|
||||
| P1 | Fix request_validator.c:304,320 leaks | Called on every event validation |
|
||||
| P1 | Fix nip042.c:99 relay_url leak | Called on every NIP-42 auth |
|
||||
| P2 | Implement config cache for hot-path values | Eliminates leak class entirely |
|
||||
| P2 | Fix inactive subscription cleanup in CLOSED handler | Prevents slow subscription leak |
|
||||
| P3 | Add memory monitoring endpoint | Ongoing visibility |
|
||||
| P3 | Move large stack arrays to heap | Reduces stack pressure |
|
||||
@@ -0,0 +1,257 @@
|
||||
# PostgreSQL Nostr Relay Architecture Analysis
|
||||
|
||||
This document summarizes common approaches and best practices for building Nostr relays backed by PostgreSQL, based on industry-standard designs and existing open-source relay implementations.
|
||||
|
||||
## Identified Projects
|
||||
* **Nostrss**: High-performance Rust-based relay.
|
||||
* **Nostrgres**: Focused on PostgreSQL integration with complex event filtering.
|
||||
* **Relay-rs (Postgres branch)**: General purpose high-throughput relay.
|
||||
* **Nostream** ([github.com/Cameri/nostream](https://github.com/Cameri/nostream)): Production-ready TypeScript relay backed by PostgreSQL and Redis.
|
||||
* **Nostr-Relay-NestJS** ([github.com/CodyTseng/nostr-relay-nestjs](https://github.com/CodyTseng/nostr-relay-nestjs)): High-performance relay built with NestJS and PostgreSQL, utilizing Kysely for type-safe database interactions.
|
||||
|
||||
## Detailed Analysis
|
||||
|
||||
### 1. Common Schema Patterns
|
||||
* **Event Storage (JSONB)**: Most PostgreSQL-backed relays store the raw event as a JSONB object in a central `events` table. This provides flexibility for the evolving Nostr spec while allowing efficient extraction of fields.
|
||||
* **Tag Denormalization**: While the raw event is in JSONB, performant relays extract tags (like `p`, `e`, `t`, `d`) into a separate `tags` table with foreign key relationships to the `events` table. This avoids slow JSONB traversal during complex filter queries.
|
||||
* **Author/Publisher Tracking**: A dedicated `authors` or `pubkeys` table is typically used to maintain indexing on the `pubkey` field, enabling quick lookups of user activity.
|
||||
|
||||
### 2. Performance Optimization
|
||||
* **GIN Indexes on JSONB**: Crucial for filtering on specific event tags or custom properties stored within the event object. GIN indexes with `jsonb_path_ops` are generally preferred for equality checks.
|
||||
* **Time-Series Partitioning**: Given the append-only nature of Nostr, partitioning the `events` table by time (e.g., daily or weekly chunks) is highly recommended. This significantly improves query performance for recent data and simplifies data expiration/deletion.
|
||||
* **Clustering**: Clustering the `events` table by the timestamp index can reduce disk I/O, as it keeps temporally related events physically adjacent on the disk.
|
||||
|
||||
### 3. Schema Management Approaches
|
||||
* **Migrations**: Most mature relays utilize migration tools (like `Flyway`, `Diesel migrations`, or `Golang-migrate`) to version control database schema changes. This is critical for production stability.
|
||||
* **Auto-generation**: Some lightweight prototypes auto-generate schemas at startup. This is generally discouraged for production due to the risk of destructive changes, data loss, or blocking DDL operations on large tables.
|
||||
|
||||
## Recommendations for Building a New Relay
|
||||
1. **Prioritize Denormalization**: Do not rely solely on JSONB queries for high-traffic filters. Extract indexed tags into dedicated columns or tables.
|
||||
2. **Use Partitioning from Day One**: Implementing native PostgreSQL partitioning (e.g., using `pg_partman`) is much easier before the dataset grows to millions of rows.
|
||||
3. **Connection Pooling**: PostgreSQL requires aggressive connection management. Use a high-performance pooler like `PgBouncer` to handle the large number of concurrent, short-lived connections common in WebSocket-based relay traffic.
|
||||
4. **Asynchronous Writes**: Decouple the WebSocket ingestion thread from the database writer thread to ensure that slow database writes do not impact the relay's responsiveness.
|
||||
|
||||
## Nostream ([github.com/Cameri/nostream](https://github.com/Cameri/nostream))
|
||||
|
||||
A production-ready Nostr relay written in TypeScript (Node.js), backed by PostgreSQL 14+ and Redis. Uses Knex.js for versioned migrations.
|
||||
|
||||
### PostgreSQL Schema
|
||||
|
||||
The schema is fully normalized — no JSONB blob for the whole event. Tags are stored separately and populated by a trigger.
|
||||
|
||||
**`events` table** — one row per event, tags kept as a JSONB column for the trigger to process:
|
||||
```sql
|
||||
CREATE TABLE events (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
event_id BYTEA UNIQUE NOT NULL, -- 32-byte hash
|
||||
event_pubkey BYTEA NOT NULL,
|
||||
event_kind INTEGER UNSIGNED NOT NULL,
|
||||
event_created_at INTEGER UNSIGNED NOT NULL,
|
||||
event_content TEXT NOT NULL,
|
||||
event_tags JSONB, -- raw tags array, source of truth for trigger
|
||||
event_signature BYTEA NOT NULL,
|
||||
remote_address TEXT,
|
||||
expires_at INTEGER,
|
||||
deleted_at TIMESTAMP,
|
||||
event_deduplication JSONB, -- used for replaceable event conflict key
|
||||
first_seen TIMESTAMP DEFAULT now()
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX ON events (event_pubkey);
|
||||
CREATE INDEX ON events (event_kind);
|
||||
CREATE INDEX ON events (event_created_at);
|
||||
CREATE INDEX ON events (expires_at);
|
||||
CREATE INDEX event_tags_idx ON events USING GIN (event_tags); -- GIN on raw JSONB
|
||||
|
||||
-- Unique index enforcing NIP-16 replaceable event semantics
|
||||
CREATE UNIQUE INDEX replaceable_events_idx ON events (event_pubkey, event_kind)
|
||||
WHERE event_kind = 0 OR event_kind = 3
|
||||
OR (event_kind >= 10000 AND event_kind < 20000);
|
||||
```
|
||||
|
||||
**`event_tags` table** — denormalized single-letter tags, populated by a PostgreSQL trigger on `events`:
|
||||
```sql
|
||||
CREATE TABLE event_tags (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
event_id BYTEA NOT NULL,
|
||||
tag_name TEXT NOT NULL, -- single-letter only (e.g. 'e', 'p', 't')
|
||||
tag_value TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX ON event_tags (event_id);
|
||||
CREATE INDEX ON event_tags (tag_name, tag_value);
|
||||
|
||||
-- Trigger: on INSERT/UPDATE/DELETE to events, repopulate event_tags
|
||||
CREATE OR REPLACE FUNCTION process_event_tags() RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
tag_element jsonb;
|
||||
BEGIN
|
||||
DELETE FROM event_tags WHERE event_id = OLD.event_id;
|
||||
IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
|
||||
FOR tag_element IN SELECT jsonb_array_elements(NEW.event_tags) LOOP
|
||||
IF length(trim('"' FROM (tag_element->0)::text)) = 1
|
||||
AND (tag_element->1)::text IS NOT NULL THEN
|
||||
INSERT INTO event_tags (event_id, tag_name, tag_value)
|
||||
VALUES (NEW.event_id,
|
||||
trim('"' FROM (tag_element->0)::text),
|
||||
trim('"' FROM (tag_element->1)::text));
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER insert_event_tags
|
||||
AFTER INSERT OR UPDATE OR DELETE ON events
|
||||
FOR EACH ROW EXECUTE FUNCTION process_event_tags();
|
||||
```
|
||||
|
||||
Tag queries (`#e`, `#p`, etc.) join `event_tags` rather than traversing the JSONB array.
|
||||
|
||||
**`users` and `invoices` tables** — for paid relay admission (Lightning payments):
|
||||
```sql
|
||||
CREATE TABLE users (
|
||||
pubkey BYTEA PRIMARY KEY,
|
||||
is_admitted BOOLEAN DEFAULT FALSE,
|
||||
balance BIGINT DEFAULT 0, -- in msats
|
||||
tos_accepted_at DATETIME
|
||||
);
|
||||
|
||||
CREATE TABLE invoices (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
pubkey BYTEA NOT NULL,
|
||||
bolt11 TEXT NOT NULL,
|
||||
amount_requested BIGINT UNSIGNED NOT NULL,
|
||||
amount_paid BIGINT UNSIGNED,
|
||||
unit ENUM('msats','sats','btc'),
|
||||
status ENUM('pending','completed','expired'),
|
||||
description TEXT,
|
||||
confirmed_at DATETIME,
|
||||
expires_at DATETIME
|
||||
);
|
||||
```
|
||||
|
||||
### How Redis Is Used
|
||||
|
||||
Redis is **not** used for subscription fan-out. Subscription delivery is handled entirely in-process: each `WebSocketAdapter` holds a `Map<subscriptionId, filters[]>`, and a broadcast event walks all connected clients and filters locally.
|
||||
|
||||
Redis has two actual roles:
|
||||
|
||||
1. **Sliding-window rate limiting** — The `SlidingWindowRateLimiter` uses Redis sorted sets to track request timestamps per key (pubkey, IP, etc.) within a rolling time window:
|
||||
- `ZREMRANGEBYSCORE key 0 (now - period)` — evict old entries
|
||||
- `ZADD key timestamp member` — record this hit
|
||||
- `ZRANGE key 0 -1` — count hits in window
|
||||
- `EXPIRE key period` — auto-cleanup
|
||||
- Keys follow the pattern `<pubkey>:events:<period>` or `<ip>:message:<period>`
|
||||
|
||||
2. **General-purpose cache** — A `RedisAdapter` wraps the client with `get`/`set`/`hasKey` helpers, used for caching admission checks (e.g. `<pubkey>:is-admitted`). The event handler has a `TODO` comment noting that the `userRepository.findByPubkey()` call should be replaced with a cache lookup — meaning this is partially implemented.
|
||||
|
||||
---
|
||||
|
||||
## Schema Definitions
|
||||
|
||||
### Relay-rs (Postgres)
|
||||
```sql
|
||||
-- Events table
|
||||
CREATE TABLE "event" (
|
||||
id bytea NOT NULL,
|
||||
pub_key bytea NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
kind integer NOT NULL,
|
||||
"content" bytea NOT NULL,
|
||||
hidden bit(1) NOT NULL DEFAULT 0::bit(1),
|
||||
delegated_by bytea NULL,
|
||||
first_seen timestamp with time zone NOT NULL DEFAULT now(),
|
||||
expires_at timestamp(0) with time zone,
|
||||
CONSTRAINT event_pkey PRIMARY KEY (id)
|
||||
);
|
||||
CREATE INDEX event_created_at_idx ON "event" (created_at,kind);
|
||||
CREATE INDEX event_pub_key_idx ON "event" (pub_key);
|
||||
CREATE INDEX event_delegated_by_idx ON "event" (delegated_by);
|
||||
CREATE INDEX event_expires_at_idx ON "event" (expires_at);
|
||||
|
||||
-- Tags table
|
||||
CREATE TABLE "tag" (
|
||||
id int8 NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
event_id bytea NOT NULL,
|
||||
"name" varchar NOT NULL,
|
||||
value bytea NULL,
|
||||
value_hex bytea NULL,
|
||||
CONSTRAINT tag_fk FOREIGN KEY (event_id) REFERENCES "event"(id) ON DELETE CASCADE,
|
||||
CONSTRAINT unique_constraint_name UNIQUE (event_id, "name", value, value_hex)
|
||||
);
|
||||
CREATE INDEX tag_event_id_idx ON tag USING btree (event_id, name);
|
||||
CREATE INDEX tag_value_idx ON tag USING btree (value);
|
||||
CREATE INDEX tag_value_hex_idx ON tag USING btree (value_hex);
|
||||
|
||||
-- Account table
|
||||
CREATE TABLE "account" (
|
||||
pubkey varchar NOT NULL,
|
||||
is_admitted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
balance BIGINT NOT NULL DEFAULT 0,
|
||||
tos_accepted_at TIMESTAMP,
|
||||
CONSTRAINT account_pkey PRIMARY KEY (pubkey)
|
||||
);
|
||||
|
||||
-- Invoice table
|
||||
CREATE TYPE status AS ENUM ('Paid', 'Unpaid', 'Expired');
|
||||
CREATE TABLE "invoice" (
|
||||
payment_hash varchar NOT NULL,
|
||||
pubkey varchar NOT NULL,
|
||||
invoice varchar NOT NULL,
|
||||
amount BIGINT NOT NULL,
|
||||
status status NOT NULL DEFAULT 'Unpaid',
|
||||
description varchar,
|
||||
created_at timestamp,
|
||||
confirmed_at timestamp,
|
||||
CONSTRAINT invoice_payment_hash PRIMARY KEY (payment_hash),
|
||||
CONSTRAINT invoice_pubkey_fkey FOREIGN KEY (pubkey) REFERENCES account (pubkey) ON DELETE CASCADE
|
||||
);
|
||||
```
|
||||
|
||||
### Nostr-Relay-NestJS (PostgreSQL via Kysely)
|
||||
```sql
|
||||
-- Events table
|
||||
CREATE TABLE events (
|
||||
id CHAR(64) PRIMARY KEY,
|
||||
pubkey CHAR(64) NOT NULL,
|
||||
created_at BIGINT NOT NULL,
|
||||
kind INTEGER NOT NULL,
|
||||
tags JSONB NOT NULL DEFAULT '[]',
|
||||
generic_tags TEXT[] NOT NULL DEFAULT '{}',
|
||||
content TEXT NOT NULL DEFAULT '',
|
||||
sig CHAR(128) NOT NULL,
|
||||
expired_at BIGINT,
|
||||
d_tag_value TEXT,
|
||||
delegator CHAR(64),
|
||||
create_date TIMESTAMP NOT NULL DEFAULT now(),
|
||||
update_date TIMESTAMP NOT NULL DEFAULT now(),
|
||||
delete_date TIMESTAMP
|
||||
);
|
||||
|
||||
-- Indices
|
||||
CREATE EXTENSION IF NOT EXISTS btree_gin;
|
||||
CREATE INDEX generic_tags_kind_idx ON events USING gin (generic_tags, kind);
|
||||
CREATE INDEX pubkey_kind_created_at_idx ON events (pubkey, kind, created_at);
|
||||
CREATE INDEX delegator_kind_created_at_idx ON events (delegator, kind, created_at);
|
||||
CREATE INDEX created_at_kind_idx ON events (created_at, kind);
|
||||
|
||||
-- Generic tags table (for optimized tag queries)
|
||||
CREATE TABLE generic_tags (
|
||||
id SERIAL PRIMARY KEY,
|
||||
tag TEXT NOT NULL,
|
||||
author CHAR(64) NOT NULL,
|
||||
kind INTEGER NOT NULL,
|
||||
event_id CHAR(64) NOT NULL REFERENCES events(id),
|
||||
created_at BIGINT NOT NULL
|
||||
);
|
||||
|
||||
-- Indices for tag filtering
|
||||
CREATE UNIQUE INDEX g_tag_event_id_idx ON generic_tags (tag, event_id);
|
||||
CREATE INDEX g_tag_kind_created_at_idx ON generic_tags (tag, kind, created_at);
|
||||
CREATE INDEX g_tag_created_at_idx ON generic_tags (tag, created_at);
|
||||
```
|
||||
Nostr-Relay-NestJS features a hybrid storage model where tags are stored both in a JSONB field (for general lookup) and a normalized `generic_tags` table (for optimized tag query performance). The schema uses Kysely for type-safe interaction.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
https://github.com/rushmi0/Fenrir-s
|
||||
https://github.com/barkyq/gnost-relay
|
||||
https://github.com/lpicanco/knostr
|
||||
https://github.com/bezysoftware/netstr
|
||||
https://github.com/lebrunel/nex
|
||||
https://github.com/CodyTseng/nostr-relay-nestjs
|
||||
https://github.com/mattn/nostr-relay
|
||||
https://github.com/Cameri/nostream
|
||||
https://github.com/Giszmo/NostrPostr/tree/master/NostrRelay
|
||||
https://github.com/fiatjaf/relayer/tree/master/examples/basic
|
||||
@@ -13,7 +13,7 @@ extern void log_query_execution(const char* query_type, const char* sub_id,
|
||||
#include <pthread.h>
|
||||
#include <libwebsockets.h>
|
||||
#include <cjson/cJSON.h>
|
||||
#include <sqlite3.h>
|
||||
int get_active_connection_count(void);
|
||||
#include <time.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
@@ -27,6 +27,7 @@ extern void log_query_execution(const char* query_type, const char* sub_id,
|
||||
#include "../nostr_core_lib/nostr_core/nip017.h"
|
||||
#include "../nostr_core_lib/nostr_core/nip044.h"
|
||||
#include "subscriptions.h"
|
||||
#include "db_ops.h"
|
||||
|
||||
// External subscription manager (from main.c via subscriptions.c)
|
||||
extern subscription_manager_t g_subscription_manager;
|
||||
@@ -61,22 +62,21 @@ int get_monitoring_throttle_seconds(void) {
|
||||
|
||||
// Query event kind distribution from database
|
||||
cJSON* query_event_kind_distribution(void) {
|
||||
extern sqlite3* g_db;
|
||||
if (!g_db) {
|
||||
if (!db_is_available()) {
|
||||
DEBUG_ERROR("Database not available for monitoring query");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Query event kinds distribution with total count
|
||||
sqlite3_stmt* stmt;
|
||||
const char* sql = "SELECT kind, COUNT(*) as count FROM events GROUP BY kind ORDER BY count DESC";
|
||||
|
||||
// Start timing
|
||||
struct timespec query_start, query_end;
|
||||
clock_gettime(CLOCK_MONOTONIC, &query_start);
|
||||
|
||||
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to prepare event kind distribution query");
|
||||
long long total_events = 0;
|
||||
cJSON* kinds_array = db_get_event_kind_distribution_rows(&total_events);
|
||||
if (!kinds_array) {
|
||||
DEBUG_ERROR("Failed to query event kind distribution rows");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -84,29 +84,12 @@ cJSON* query_event_kind_distribution(void) {
|
||||
cJSON_AddStringToObject(distribution, "data_type", "event_kinds");
|
||||
cJSON_AddNumberToObject(distribution, "timestamp", (double)time(NULL));
|
||||
|
||||
cJSON* kinds_array = cJSON_CreateArray();
|
||||
long long total_events = 0;
|
||||
int row_count = 0;
|
||||
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
row_count++;
|
||||
int kind = sqlite3_column_int(stmt, 0);
|
||||
long long count = sqlite3_column_int64(stmt, 1);
|
||||
total_events += count;
|
||||
|
||||
cJSON* kind_obj = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(kind_obj, "kind", kind);
|
||||
cJSON_AddNumberToObject(kind_obj, "count", count);
|
||||
cJSON_AddItemToArray(kinds_array, kind_obj);
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
// Stop timing and log
|
||||
clock_gettime(CLOCK_MONOTONIC, &query_end);
|
||||
long elapsed_us = (query_end.tv_sec - query_start.tv_sec) * 1000000L +
|
||||
(query_end.tv_nsec - query_start.tv_nsec) / 1000L;
|
||||
|
||||
|
||||
int row_count = cJSON_GetArraySize(kinds_array);
|
||||
log_query_execution("MONITOR", "event_kinds", NULL, sql, elapsed_us, row_count);
|
||||
|
||||
cJSON_AddNumberToObject(distribution, "total_events", total_events);
|
||||
@@ -117,8 +100,7 @@ cJSON* query_event_kind_distribution(void) {
|
||||
|
||||
// Query time-based statistics from database
|
||||
cJSON* query_time_based_statistics(void) {
|
||||
extern sqlite3* g_db;
|
||||
if (!g_db) {
|
||||
if (!db_is_available()) {
|
||||
DEBUG_ERROR("Database not available for time stats query");
|
||||
return NULL;
|
||||
}
|
||||
@@ -142,38 +124,18 @@ cJSON* query_time_based_statistics(void) {
|
||||
{NULL, 0, NULL}
|
||||
};
|
||||
|
||||
// Get total events count
|
||||
sqlite3_stmt* total_stmt;
|
||||
const char* total_sql = "SELECT COUNT(*) FROM events";
|
||||
long long total_events = 0;
|
||||
|
||||
if (sqlite3_prepare_v2(g_db, total_sql, -1, &total_stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(total_stmt) == SQLITE_ROW) {
|
||||
total_events = sqlite3_column_int64(total_stmt, 0);
|
||||
}
|
||||
sqlite3_finalize(total_stmt);
|
||||
}
|
||||
(void)db_get_total_event_count_ll(&total_events);
|
||||
|
||||
// Query each time period
|
||||
for (int i = 0; periods[i].period != NULL; i++) {
|
||||
sqlite3_stmt* stmt;
|
||||
const char* sql = "SELECT COUNT(*) FROM events WHERE created_at >= ?";
|
||||
|
||||
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to prepare time stats query");
|
||||
time_t cutoff = now - periods[i].seconds;
|
||||
long long count = 0;
|
||||
if (db_get_event_count_since(cutoff, &count) != 0) {
|
||||
DEBUG_ERROR("Failed to query time stats count");
|
||||
continue;
|
||||
}
|
||||
|
||||
time_t cutoff = now - periods[i].seconds;
|
||||
sqlite3_bind_int64(stmt, 1, cutoff);
|
||||
|
||||
long long count = 0;
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
count = sqlite3_column_int64(stmt, 0);
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
cJSON* period_obj = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(period_obj, "period", periods[i].period);
|
||||
cJSON_AddNumberToObject(period_obj, "count", count);
|
||||
@@ -189,51 +151,39 @@ cJSON* query_time_based_statistics(void) {
|
||||
|
||||
// Query top pubkeys by event count from database
|
||||
cJSON* query_top_pubkeys(void) {
|
||||
extern sqlite3* g_db;
|
||||
if (!g_db) {
|
||||
if (!db_is_available()) {
|
||||
DEBUG_ERROR("Database not available for top pubkeys query");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Query top 10 pubkeys by event count
|
||||
sqlite3_stmt* stmt;
|
||||
const char* sql = "SELECT pubkey, COUNT(*) as count FROM events GROUP BY pubkey ORDER BY count DESC LIMIT 10";
|
||||
|
||||
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to prepare top pubkeys query");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cJSON* top_pubkeys = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(top_pubkeys, "data_type", "top_pubkeys");
|
||||
cJSON_AddNumberToObject(top_pubkeys, "timestamp", (double)time(NULL));
|
||||
|
||||
cJSON* pubkeys_array = cJSON_CreateArray();
|
||||
|
||||
// Get total events count for percentage calculation
|
||||
sqlite3_stmt* total_stmt;
|
||||
const char* total_sql = "SELECT COUNT(*) FROM events";
|
||||
long long total_events = 0;
|
||||
|
||||
if (sqlite3_prepare_v2(g_db, total_sql, -1, &total_stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(total_stmt) == SQLITE_ROW) {
|
||||
total_events = sqlite3_column_int64(total_stmt, 0);
|
||||
}
|
||||
sqlite3_finalize(total_stmt);
|
||||
cJSON* rows = db_get_top_pubkeys_rows(10);
|
||||
if (!rows) {
|
||||
cJSON_Delete(top_pubkeys);
|
||||
DEBUG_ERROR("Failed to query top pubkeys rows");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char* pubkey = (const char*)sqlite3_column_text(stmt, 0);
|
||||
long long count = sqlite3_column_int64(stmt, 1);
|
||||
|
||||
cJSON* pubkeys_array = cJSON_CreateArray();
|
||||
cJSON* row = NULL;
|
||||
cJSON_ArrayForEach(row, rows) {
|
||||
cJSON* pubkey_obj = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(pubkey_obj, "pubkey", pubkey ? pubkey : "");
|
||||
cJSON_AddNumberToObject(pubkey_obj, "event_count", count);
|
||||
// Percentage will be calculated by frontend using total_events
|
||||
cJSON* pubkey_item = cJSON_GetObjectItemCaseSensitive(row, "pubkey");
|
||||
cJSON* count_item = cJSON_GetObjectItemCaseSensitive(row, "count");
|
||||
|
||||
cJSON_AddStringToObject(pubkey_obj, "pubkey",
|
||||
cJSON_IsString(pubkey_item) ? pubkey_item->valuestring : "");
|
||||
cJSON_AddNumberToObject(pubkey_obj, "event_count",
|
||||
cJSON_IsNumber(count_item) ? count_item->valuedouble : 0.0);
|
||||
cJSON_AddItemToArray(pubkeys_array, pubkey_obj);
|
||||
}
|
||||
cJSON_Delete(rows);
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
long long total_events = 0;
|
||||
(void)db_get_total_event_count_ll(&total_events);
|
||||
|
||||
cJSON_AddItemToObject(top_pubkeys, "pubkeys", pubkeys_array);
|
||||
cJSON_AddNumberToObject(top_pubkeys, "total_events", total_events);
|
||||
@@ -245,15 +195,11 @@ cJSON* query_top_pubkeys(void) {
|
||||
// Query detailed subscription information from database log (ADMIN ONLY)
|
||||
// Uses subscriptions table instead of in-memory iteration to avoid mutex contention
|
||||
cJSON* query_subscription_details(void) {
|
||||
extern sqlite3* g_db;
|
||||
if (!g_db) {
|
||||
if (!db_is_available()) {
|
||||
DEBUG_ERROR("Database not available for subscription details query");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Query active subscriptions from the active_subscriptions_log view
|
||||
// This view properly handles deduplication of closed/expired subscriptions
|
||||
sqlite3_stmt* stmt;
|
||||
const char* sql =
|
||||
"SELECT * "
|
||||
"FROM active_subscriptions_log "
|
||||
@@ -267,11 +213,6 @@ cJSON* query_subscription_details(void) {
|
||||
struct timespec query_start, query_end;
|
||||
clock_gettime(CLOCK_MONOTONIC, &query_start);
|
||||
|
||||
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to prepare subscription details query");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
time_t current_time = time(NULL);
|
||||
cJSON* subscriptions_data = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(subscriptions_data, "data_type", "subscription_details");
|
||||
@@ -280,34 +221,51 @@ cJSON* query_subscription_details(void) {
|
||||
cJSON* data = cJSON_CreateObject();
|
||||
cJSON* subscriptions_array = cJSON_CreateArray();
|
||||
|
||||
cJSON* rows = db_get_subscription_details_rows();
|
||||
if (!rows) {
|
||||
cJSON_Delete(subscriptions_data);
|
||||
cJSON_Delete(data);
|
||||
cJSON_Delete(subscriptions_array);
|
||||
DEBUG_ERROR("Failed to query subscription details rows");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Iterate through query results
|
||||
int row_count = 0;
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
cJSON* row = NULL;
|
||||
cJSON_ArrayForEach(row, rows) {
|
||||
row_count++;
|
||||
cJSON* sub_obj = cJSON_CreateObject();
|
||||
|
||||
// Extract subscription data from database
|
||||
const char* sub_id = (const char*)sqlite3_column_text(stmt, 0);
|
||||
const char* client_ip = (const char*)sqlite3_column_text(stmt, 1);
|
||||
const char* filter_json = (const char*)sqlite3_column_text(stmt, 2);
|
||||
long long events_sent = sqlite3_column_int64(stmt, 3);
|
||||
long long created_at = sqlite3_column_int64(stmt, 4);
|
||||
long long duration_seconds = sqlite3_column_int64(stmt, 5);
|
||||
const char* wsi_pointer = (const char*)sqlite3_column_text(stmt, 6);
|
||||
cJSON* sub_id_item = cJSON_GetObjectItemCaseSensitive(row, "id");
|
||||
cJSON* client_ip_item = cJSON_GetObjectItemCaseSensitive(row, "client_ip");
|
||||
cJSON* filter_json_item = cJSON_GetObjectItemCaseSensitive(row, "filter_json");
|
||||
cJSON* events_sent_item = cJSON_GetObjectItemCaseSensitive(row, "events_sent");
|
||||
cJSON* created_at_item = cJSON_GetObjectItemCaseSensitive(row, "created_at");
|
||||
cJSON* duration_item = cJSON_GetObjectItemCaseSensitive(row, "duration_seconds");
|
||||
cJSON* wsi_pointer_item = cJSON_GetObjectItemCaseSensitive(row, "wsi_pointer");
|
||||
|
||||
const char* sub_id = cJSON_IsString(sub_id_item) ? sub_id_item->valuestring : "";
|
||||
const char* client_ip = cJSON_IsString(client_ip_item) ? client_ip_item->valuestring : "";
|
||||
const char* filter_json = cJSON_IsString(filter_json_item) ? filter_json_item->valuestring : "[]";
|
||||
long long events_sent = cJSON_IsNumber(events_sent_item) ? (long long)events_sent_item->valuedouble : 0;
|
||||
long long created_at = cJSON_IsNumber(created_at_item) ? (long long)created_at_item->valuedouble : 0;
|
||||
long long duration_seconds = cJSON_IsNumber(duration_item) ? (long long)duration_item->valuedouble : 0;
|
||||
const char* wsi_pointer = cJSON_IsString(wsi_pointer_item) ? wsi_pointer_item->valuestring : "";
|
||||
|
||||
// DEBUG: Log each subscription found
|
||||
DEBUG_LOG("Row %d: sub_id=%s, client_ip=%s, events_sent=%lld, created_at=%lld",
|
||||
row_count, sub_id ? sub_id : "NULL", client_ip ? client_ip : "NULL",
|
||||
row_count, sub_id[0] ? sub_id : "NULL", client_ip[0] ? client_ip : "NULL",
|
||||
events_sent, created_at);
|
||||
|
||||
// Add basic subscription info
|
||||
cJSON_AddStringToObject(sub_obj, "id", sub_id ? sub_id : "");
|
||||
cJSON_AddStringToObject(sub_obj, "client_ip", client_ip ? client_ip : "");
|
||||
cJSON_AddStringToObject(sub_obj, "id", sub_id);
|
||||
cJSON_AddStringToObject(sub_obj, "client_ip", client_ip);
|
||||
cJSON_AddNumberToObject(sub_obj, "created_at", (double)created_at);
|
||||
cJSON_AddNumberToObject(sub_obj, "duration_seconds", (double)duration_seconds);
|
||||
cJSON_AddNumberToObject(sub_obj, "events_sent", events_sent);
|
||||
cJSON_AddBoolToObject(sub_obj, "active", 1); // All from this view are active
|
||||
cJSON_AddStringToObject(sub_obj, "wsi_pointer", wsi_pointer ? wsi_pointer : "N/A");
|
||||
cJSON_AddStringToObject(sub_obj, "wsi_pointer", wsi_pointer[0] ? wsi_pointer : "N/A");
|
||||
|
||||
// Extract query stats from per_session_data if wsi is still valid
|
||||
int db_queries = 0;
|
||||
@@ -316,7 +274,7 @@ cJSON* query_subscription_details(void) {
|
||||
double row_rate = 0.0;
|
||||
double avg_rows_per_query = 0.0;
|
||||
|
||||
if (wsi_pointer && strlen(wsi_pointer) > 2) { // Check for valid pointer string
|
||||
if (wsi_pointer[0] != '\0' && strlen(wsi_pointer) > 2) { // Check for valid pointer string
|
||||
// Parse wsi pointer from hex string
|
||||
struct lws* wsi = NULL;
|
||||
if (sscanf(wsi_pointer, "%p", (void**)&wsi) == 1 && wsi != NULL) {
|
||||
@@ -325,7 +283,7 @@ cJSON* query_subscription_details(void) {
|
||||
if (pss) {
|
||||
db_queries = pss->db_queries_executed;
|
||||
db_rows = pss->db_rows_returned;
|
||||
|
||||
|
||||
// Calculate rates (per minute)
|
||||
time_t connection_duration = current_time - pss->query_tracking_start;
|
||||
if (connection_duration > 0) {
|
||||
@@ -333,7 +291,7 @@ cJSON* query_subscription_details(void) {
|
||||
query_rate = db_queries / minutes;
|
||||
row_rate = db_rows / minutes;
|
||||
}
|
||||
|
||||
|
||||
// Calculate average rows per query
|
||||
if (db_queries > 0) {
|
||||
avg_rows_per_query = (double)db_rows / (double)db_queries;
|
||||
@@ -364,8 +322,7 @@ cJSON* query_subscription_details(void) {
|
||||
|
||||
cJSON_AddItemToArray(subscriptions_array, sub_obj);
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
cJSON_Delete(rows);
|
||||
|
||||
// Add subscriptions array and count to data
|
||||
cJSON_AddItemToObject(data, "subscriptions", subscriptions_array);
|
||||
@@ -377,9 +334,9 @@ cJSON* query_subscription_details(void) {
|
||||
clock_gettime(CLOCK_MONOTONIC, &query_end);
|
||||
long elapsed_us = (query_end.tv_sec - query_start.tv_sec) * 1000000L +
|
||||
(query_end.tv_nsec - query_start.tv_nsec) / 1000L;
|
||||
|
||||
|
||||
log_query_execution("MONITOR", "subscription_details", NULL, sql, elapsed_us, row_count);
|
||||
|
||||
|
||||
// DEBUG: Log final summary
|
||||
DEBUG_LOG("Total subscriptions found: %d", row_count);
|
||||
DEBUG_LOG("=== END SUBSCRIPTION_DETAILS QUERY DEBUG ===");
|
||||
@@ -499,10 +456,10 @@ int generate_monitoring_event_for_type(const char* d_tag_value, cJSON* (*query_f
|
||||
// Use the library function to create and sign the event
|
||||
cJSON* signed_event = nostr_create_and_sign_event(
|
||||
24567, // kind (ephemeral)
|
||||
cJSON_GetStringValue(cJSON_GetObjectItem(monitoring_event, "content")), // content
|
||||
cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(monitoring_event, "content")), // content
|
||||
tags, // tags
|
||||
relay_privkey, // private key
|
||||
(time_t)cJSON_GetNumberValue(cJSON_GetObjectItem(monitoring_event, "created_at")) // timestamp
|
||||
(time_t)cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(monitoring_event, "created_at")) // timestamp
|
||||
);
|
||||
|
||||
if (!signed_event) {
|
||||
@@ -671,6 +628,13 @@ static const config_definition_t known_configs[] = {
|
||||
{"nip42_time_tolerance", "int", 60, 3600},
|
||||
{"nip70_protected_events_enabled", "bool", 0, 1},
|
||||
|
||||
// Idle Connection Ban Settings
|
||||
{"idle_connection_timeout_sec", "int", 0, 300},
|
||||
{"idle_ban_enabled", "bool", 0, 1},
|
||||
{"idle_ban_threshold", "int", 1, 100},
|
||||
{"idle_ban_window_sec", "int", 10, 3600},
|
||||
{"idle_ban_duration_sec", "int", 60, 86400},
|
||||
|
||||
// Server Core Settings
|
||||
{"relay_port", "int", 1, 65535},
|
||||
{"max_connections", "int", 1, 10000},
|
||||
@@ -723,7 +687,6 @@ static const config_definition_t known_configs[] = {
|
||||
{NULL, NULL, 0, 0}
|
||||
};
|
||||
// External database connection (from main.c)
|
||||
extern sqlite3* g_db;
|
||||
extern char g_database_path[512];
|
||||
|
||||
// Forward declarations for database functions
|
||||
@@ -922,10 +885,10 @@ int send_admin_response(const char* sender_pubkey, const char* response_content,
|
||||
// Use the library function to create and sign the event
|
||||
cJSON* signed_event = nostr_create_and_sign_event(
|
||||
23457, // kind
|
||||
cJSON_GetStringValue(cJSON_GetObjectItem(response_event, "content")), // content
|
||||
cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(response_event, "content")), // content
|
||||
tags, // tags
|
||||
relay_privkey, // private key
|
||||
(time_t)cJSON_GetNumberValue(cJSON_GetObjectItem(response_event, "created_at")) // timestamp
|
||||
(time_t)cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(response_event, "created_at")) // timestamp
|
||||
);
|
||||
|
||||
if (!signed_event) {
|
||||
@@ -1028,138 +991,12 @@ char* execute_sql_query(const char* query, const char* request_id, char* error_m
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!g_db) {
|
||||
if (!db_is_available()) {
|
||||
snprintf(error_message, error_size, "Database not available");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Set busy timeout to prevent long-running queries (5 seconds)
|
||||
sqlite3_busy_timeout(g_db, 5000);
|
||||
|
||||
// Prepare statement
|
||||
sqlite3_stmt* stmt;
|
||||
int rc = sqlite3_prepare_v2(g_db, query, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
const char* err_msg = sqlite3_errmsg(g_db);
|
||||
snprintf(error_message, error_size, "SQL prepare failed: %s", err_msg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Execute query and collect results
|
||||
cJSON* response = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(response, "query_type", "sql_query");
|
||||
cJSON_AddStringToObject(response, "request_id", request_id);
|
||||
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
||||
cJSON_AddStringToObject(response, "query", query);
|
||||
|
||||
// Get column information
|
||||
int col_count = sqlite3_column_count(stmt);
|
||||
cJSON* columns = cJSON_CreateArray();
|
||||
for (int i = 0; i < col_count; i++) {
|
||||
const char* col_name = sqlite3_column_name(stmt, i);
|
||||
cJSON_AddItemToArray(columns, cJSON_CreateString(col_name ? col_name : ""));
|
||||
}
|
||||
cJSON_AddItemToObject(response, "columns", columns);
|
||||
|
||||
// Execute and collect rows (with limit)
|
||||
cJSON* rows = cJSON_CreateArray();
|
||||
int row_count = 0;
|
||||
const int MAX_ROWS = 1000; // Configurable limit
|
||||
|
||||
struct timespec start_time;
|
||||
clock_gettime(CLOCK_MONOTONIC, &start_time);
|
||||
|
||||
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW && row_count < MAX_ROWS) {
|
||||
cJSON* row = cJSON_CreateArray();
|
||||
|
||||
for (int i = 0; i < col_count; i++) {
|
||||
int col_type = sqlite3_column_type(stmt, i);
|
||||
|
||||
switch (col_type) {
|
||||
case SQLITE_INTEGER:
|
||||
cJSON_AddItemToArray(row, cJSON_CreateNumber((double)sqlite3_column_int64(stmt, i)));
|
||||
break;
|
||||
case SQLITE_FLOAT:
|
||||
cJSON_AddItemToArray(row, cJSON_CreateNumber(sqlite3_column_double(stmt, i)));
|
||||
break;
|
||||
case SQLITE_TEXT: {
|
||||
const char* text = (const char*)sqlite3_column_text(stmt, i);
|
||||
cJSON_AddItemToArray(row, cJSON_CreateString(text ? text : ""));
|
||||
break;
|
||||
}
|
||||
case SQLITE_BLOB: {
|
||||
// Convert blob to hex string for JSON compatibility
|
||||
const void* blob = sqlite3_column_blob(stmt, i);
|
||||
int blob_size = sqlite3_column_bytes(stmt, i);
|
||||
if (blob && blob_size > 0) {
|
||||
char* hex_str = malloc(blob_size * 2 + 1);
|
||||
if (hex_str) {
|
||||
for (int j = 0; j < blob_size; j++) {
|
||||
sprintf(hex_str + j * 2, "%02x", ((unsigned char*)blob)[j]);
|
||||
}
|
||||
hex_str[blob_size * 2] = '\0';
|
||||
cJSON_AddItemToArray(row, cJSON_CreateString(hex_str));
|
||||
free(hex_str);
|
||||
} else {
|
||||
cJSON_AddItemToArray(row, cJSON_CreateString("[BLOB]"));
|
||||
}
|
||||
} else {
|
||||
cJSON_AddItemToArray(row, cJSON_CreateString(""));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SQLITE_NULL:
|
||||
cJSON_AddItemToArray(row, cJSON_CreateNull());
|
||||
break;
|
||||
default:
|
||||
cJSON_AddItemToArray(row, cJSON_CreateString("[UNKNOWN]"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_AddItemToArray(rows, row);
|
||||
row_count++;
|
||||
|
||||
// Check timeout (additional safety check)
|
||||
struct timespec current_time;
|
||||
clock_gettime(CLOCK_MONOTONIC, ¤t_time);
|
||||
double elapsed = (current_time.tv_sec - start_time.tv_sec) +
|
||||
(current_time.tv_nsec - start_time.tv_nsec) / 1e9;
|
||||
if (elapsed > 4.5) { // 4.5 seconds to allow for cleanup
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
// Check for execution errors
|
||||
if (rc != SQLITE_DONE && rc != SQLITE_ROW) {
|
||||
const char* err_msg = sqlite3_errmsg(g_db);
|
||||
snprintf(error_message, error_size, "SQL execution failed: %s", err_msg);
|
||||
cJSON_Delete(response);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Check row limit
|
||||
if (row_count >= MAX_ROWS) {
|
||||
cJSON_AddStringToObject(response, "warning", "Result truncated to maximum row limit");
|
||||
}
|
||||
|
||||
// Add metadata
|
||||
cJSON_AddNumberToObject(response, "row_count", row_count);
|
||||
cJSON_AddNumberToObject(response, "execution_time_ms", 0); // Will be set by caller
|
||||
cJSON_AddItemToObject(response, "rows", rows);
|
||||
|
||||
// Convert to JSON string
|
||||
char* json_result = cJSON_Print(response);
|
||||
cJSON_Delete(response);
|
||||
|
||||
if (!json_result) {
|
||||
snprintf(error_message, error_size, "Failed to generate JSON response");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return json_result;
|
||||
return db_execute_readonly_query_json(query, request_id, error_message, error_size, 1000, 5000);
|
||||
}
|
||||
|
||||
// Unified handler for SQL query commands
|
||||
@@ -1169,7 +1006,7 @@ int handle_sql_query_unified(cJSON* event, const char* query, char* error_messag
|
||||
}
|
||||
|
||||
// Get request event ID for response correlation
|
||||
cJSON* request_id_obj = cJSON_GetObjectItem(event, "id");
|
||||
cJSON* request_id_obj = cJSON_GetObjectItemCaseSensitive(event, "id");
|
||||
if (!request_id_obj || !cJSON_IsString(request_id_obj)) {
|
||||
snprintf(error_message, error_size, "Missing request event ID");
|
||||
return -1;
|
||||
@@ -1188,7 +1025,7 @@ int handle_sql_query_unified(cJSON* event, const char* query, char* error_messag
|
||||
}
|
||||
|
||||
// Get sender pubkey for response
|
||||
cJSON* sender_pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
cJSON* sender_pubkey_obj = cJSON_GetObjectItemCaseSensitive(event, "pubkey");
|
||||
if (!sender_pubkey_obj || !cJSON_IsString(sender_pubkey_obj)) {
|
||||
free(result_json);
|
||||
snprintf(error_message, error_size, "Missing sender pubkey");
|
||||
@@ -1284,13 +1121,16 @@ cJSON* query_cpu_metrics(void) {
|
||||
pid_t pid = getpid();
|
||||
cJSON_AddNumberToObject(cpu_stats, "process_id", (double)pid);
|
||||
|
||||
// Get memory usage from /proc/self/status
|
||||
// Get active WebSocket connection count
|
||||
cJSON_AddNumberToObject(cpu_stats, "active_connections", (double)get_active_connection_count());
|
||||
|
||||
// Get memory usage from /proc/self/status and calculate MEM%
|
||||
unsigned long rss_kb = 0;
|
||||
FILE* mem_stat = fopen("/proc/self/status", "r");
|
||||
if (mem_stat) {
|
||||
char line[256];
|
||||
while (fgets(line, sizeof(line), mem_stat)) {
|
||||
if (strncmp(line, "VmRSS:", 6) == 0) {
|
||||
unsigned long rss_kb;
|
||||
if (sscanf(line, "VmRSS: %lu kB", &rss_kb) == 1) {
|
||||
double rss_mb = rss_kb / 1024.0;
|
||||
cJSON_AddNumberToObject(cpu_stats, "memory_usage_mb", rss_mb);
|
||||
@@ -1301,13 +1141,31 @@ cJSON* query_cpu_metrics(void) {
|
||||
fclose(mem_stat);
|
||||
}
|
||||
|
||||
// Get total system memory from /proc/meminfo for MEM%
|
||||
if (rss_kb > 0) {
|
||||
FILE* meminfo = fopen("/proc/meminfo", "r");
|
||||
if (meminfo) {
|
||||
char line[256];
|
||||
while (fgets(line, sizeof(line), meminfo)) {
|
||||
if (strncmp(line, "MemTotal:", 9) == 0) {
|
||||
unsigned long total_kb;
|
||||
if (sscanf(line, "MemTotal: %lu kB", &total_kb) == 1 && total_kb > 0) {
|
||||
double mem_pct = (rss_kb * 100.0) / total_kb;
|
||||
cJSON_AddNumberToObject(cpu_stats, "memory_percent", mem_pct);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(meminfo);
|
||||
}
|
||||
}
|
||||
|
||||
return cpu_stats;
|
||||
}
|
||||
|
||||
// Generate stats JSON from database queries
|
||||
char* generate_stats_json(void) {
|
||||
extern sqlite3* g_db;
|
||||
if (!g_db) {
|
||||
if (!db_is_available()) {
|
||||
DEBUG_ERROR("Database not available for stats generation");
|
||||
return NULL;
|
||||
}
|
||||
@@ -1318,10 +1176,10 @@ char* generate_stats_json(void) {
|
||||
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
||||
|
||||
// Get database file size
|
||||
extern char g_database_path[512];
|
||||
const char* db_path = db_get_database_path();
|
||||
struct stat db_stat;
|
||||
long long db_size = 0;
|
||||
if (stat(g_database_path, &db_stat) == 0) {
|
||||
if (db_path && db_path[0] != '\0' && stat(db_path, &db_stat) == 0) {
|
||||
db_size = db_stat.st_size;
|
||||
}
|
||||
cJSON_AddNumberToObject(response, "database_size_bytes", db_size);
|
||||
@@ -1332,85 +1190,80 @@ char* generate_stats_json(void) {
|
||||
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
|
||||
cJSON_AddNumberToObject(response, "active_subscriptions", active_subs);
|
||||
|
||||
// Query total events count
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM events", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
cJSON_AddNumberToObject(response, "total_events", sqlite3_column_int64(stmt, 0));
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
long long total_events = 0;
|
||||
if (db_get_total_event_count_ll(&total_events) == 0) {
|
||||
cJSON_AddNumberToObject(response, "total_events", total_events);
|
||||
}
|
||||
|
||||
// Query event kinds distribution
|
||||
cJSON* event_kinds = cJSON_CreateArray();
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT kind, count, percentage FROM event_kinds_view ORDER BY count DESC", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
long long kinds_total = 0;
|
||||
cJSON* kind_rows = db_get_event_kind_distribution_rows(&kinds_total);
|
||||
if (kind_rows) {
|
||||
cJSON* kind_row = NULL;
|
||||
cJSON_ArrayForEach(kind_row, kind_rows) {
|
||||
cJSON* kind_obj = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(kind_obj, "kind", sqlite3_column_int(stmt, 0));
|
||||
cJSON_AddNumberToObject(kind_obj, "count", sqlite3_column_int64(stmt, 1));
|
||||
cJSON_AddNumberToObject(kind_obj, "percentage", sqlite3_column_double(stmt, 2));
|
||||
cJSON* kind = cJSON_GetObjectItemCaseSensitive(kind_row, "kind");
|
||||
cJSON* count = cJSON_GetObjectItemCaseSensitive(kind_row, "count");
|
||||
double count_val = cJSON_IsNumber(count) ? count->valuedouble : 0.0;
|
||||
double pct = (kinds_total > 0) ? ((count_val * 100.0) / (double)kinds_total) : 0.0;
|
||||
|
||||
cJSON_AddNumberToObject(kind_obj, "kind", cJSON_IsNumber(kind) ? kind->valuedouble : 0.0);
|
||||
cJSON_AddNumberToObject(kind_obj, "count", count_val);
|
||||
cJSON_AddNumberToObject(kind_obj, "percentage", pct);
|
||||
cJSON_AddItemToArray(event_kinds, kind_obj);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
cJSON_Delete(kind_rows);
|
||||
}
|
||||
cJSON_AddItemToObject(response, "event_kinds", event_kinds);
|
||||
|
||||
// Query time-based statistics
|
||||
cJSON* time_stats = cJSON_CreateObject();
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT period, total_events FROM time_stats_view", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char* period = (const char*)sqlite3_column_text(stmt, 0);
|
||||
sqlite3_int64 count = sqlite3_column_int64(stmt, 1);
|
||||
|
||||
if (strcmp(period, "total") == 0) {
|
||||
cJSON_AddNumberToObject(time_stats, "total", count);
|
||||
} else if (strcmp(period, "24h") == 0) {
|
||||
cJSON_AddNumberToObject(time_stats, "last_24h", count);
|
||||
} else if (strcmp(period, "7d") == 0) {
|
||||
cJSON_AddNumberToObject(time_stats, "last_7d", count);
|
||||
} else if (strcmp(period, "30d") == 0) {
|
||||
cJSON_AddNumberToObject(time_stats, "last_30d", count);
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
time_t now = time(NULL);
|
||||
long long c24h = 0, c7d = 0, c30d = 0;
|
||||
if (db_get_event_count_since(now - 86400, &c24h) == 0) {
|
||||
cJSON_AddNumberToObject(time_stats, "last_24h", c24h);
|
||||
}
|
||||
if (db_get_event_count_since(now - 604800, &c7d) == 0) {
|
||||
cJSON_AddNumberToObject(time_stats, "last_7d", c7d);
|
||||
}
|
||||
if (db_get_event_count_since(now - 2592000, &c30d) == 0) {
|
||||
cJSON_AddNumberToObject(time_stats, "last_30d", c30d);
|
||||
}
|
||||
cJSON_AddNumberToObject(time_stats, "total", total_events);
|
||||
cJSON_AddItemToObject(response, "time_stats", time_stats);
|
||||
|
||||
// Query top pubkeys
|
||||
cJSON* top_pubkeys = cJSON_CreateArray();
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT pubkey, event_count, percentage FROM top_pubkeys_view ORDER BY event_count DESC LIMIT 10", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
cJSON* pubkey_rows = db_get_top_pubkeys_rows(10);
|
||||
if (pubkey_rows) {
|
||||
cJSON* row = NULL;
|
||||
cJSON_ArrayForEach(row, pubkey_rows) {
|
||||
cJSON* pubkey_obj = cJSON_CreateObject();
|
||||
const char* pubkey = (const char*)sqlite3_column_text(stmt, 0);
|
||||
cJSON_AddStringToObject(pubkey_obj, "pubkey", pubkey ? pubkey : "");
|
||||
cJSON_AddNumberToObject(pubkey_obj, "event_count", sqlite3_column_int64(stmt, 1));
|
||||
cJSON_AddNumberToObject(pubkey_obj, "percentage", sqlite3_column_double(stmt, 2));
|
||||
cJSON* pubkey_item = cJSON_GetObjectItemCaseSensitive(row, "pubkey");
|
||||
cJSON* count_item = cJSON_GetObjectItemCaseSensitive(row, "count");
|
||||
double count_val = cJSON_IsNumber(count_item) ? count_item->valuedouble : 0.0;
|
||||
double pct = (total_events > 0) ? ((count_val * 100.0) / (double)total_events) : 0.0;
|
||||
|
||||
cJSON_AddStringToObject(pubkey_obj, "pubkey", cJSON_IsString(pubkey_item) ? pubkey_item->valuestring : "");
|
||||
cJSON_AddNumberToObject(pubkey_obj, "event_count", count_val);
|
||||
cJSON_AddNumberToObject(pubkey_obj, "percentage", pct);
|
||||
cJSON_AddItemToArray(top_pubkeys, pubkey_obj);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
cJSON_Delete(pubkey_rows);
|
||||
}
|
||||
cJSON_AddItemToObject(response, "top_pubkeys", top_pubkeys);
|
||||
|
||||
// Get database creation timestamp (oldest event)
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT MIN(created_at) FROM events", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
sqlite3_int64 oldest_timestamp = sqlite3_column_int64(stmt, 0);
|
||||
if (oldest_timestamp > 0) {
|
||||
cJSON_AddNumberToObject(response, "database_created_at", (double)oldest_timestamp);
|
||||
}
|
||||
// Get database creation/latest timestamps
|
||||
long long min_created_at = 0;
|
||||
long long max_created_at = 0;
|
||||
if (db_get_event_time_bounds(&min_created_at, &max_created_at) == 0) {
|
||||
if (min_created_at > 0) {
|
||||
cJSON_AddNumberToObject(response, "database_created_at", (double)min_created_at);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
// Get latest event timestamp
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT MAX(created_at) FROM events", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
sqlite3_int64 latest_timestamp = sqlite3_column_int64(stmt, 0);
|
||||
if (latest_timestamp > 0) {
|
||||
cJSON_AddNumberToObject(response, "latest_event_at", (double)latest_timestamp);
|
||||
}
|
||||
if (max_created_at > 0) {
|
||||
cJSON_AddNumberToObject(response, "latest_event_at", (double)max_created_at);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
// Convert to JSON string
|
||||
@@ -1491,7 +1344,7 @@ int send_nip17_response(const char* sender_pubkey, const char* response_content,
|
||||
}
|
||||
|
||||
// Fix the p tag in the gift wrap - library function may use wrong pubkey
|
||||
cJSON* gift_wrap_tags = cJSON_GetObjectItem(gift_wraps[0], "tags");
|
||||
cJSON* gift_wrap_tags = cJSON_GetObjectItemCaseSensitive(gift_wraps[0], "tags");
|
||||
if (gift_wrap_tags && cJSON_IsArray(gift_wrap_tags)) {
|
||||
// Find and replace the p tag with the correct user pubkey
|
||||
cJSON* tag = NULL;
|
||||
@@ -1537,8 +1390,7 @@ int send_nip17_response(const char* sender_pubkey, const char* response_content,
|
||||
|
||||
// Generate config text from database
|
||||
char* generate_config_text(void) {
|
||||
extern sqlite3* g_db;
|
||||
if (!g_db) {
|
||||
if (!db_is_available()) {
|
||||
DEBUG_ERROR("NIP-17: Database not available for config query");
|
||||
return NULL;
|
||||
}
|
||||
@@ -1557,25 +1409,29 @@ char* generate_config_text(void) {
|
||||
"🔧 Relay Configuration\n"
|
||||
"━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
||||
|
||||
// Query all config values from database
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT key, value FROM config ORDER BY key", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW && offset < 8192 - 200) {
|
||||
const char* key = (const char*)sqlite3_column_text(stmt, 0);
|
||||
const char* value = (const char*)sqlite3_column_text(stmt, 1);
|
||||
|
||||
if (key && value) {
|
||||
offset += snprintf(config_text + offset, 8192 - offset,
|
||||
"%s: %s\n", key, value);
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
} else {
|
||||
cJSON* rows = db_get_all_config_rows();
|
||||
if (!rows) {
|
||||
free(config_text);
|
||||
DEBUG_ERROR("NIP-17: Failed to query config from database");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cJSON* row = NULL;
|
||||
cJSON_ArrayForEach(row, rows) {
|
||||
if (offset >= 8192 - 200) break;
|
||||
|
||||
cJSON* key_item = cJSON_GetObjectItemCaseSensitive(row, "key");
|
||||
cJSON* value_item = cJSON_GetObjectItemCaseSensitive(row, "value");
|
||||
const char* key = cJSON_IsString(key_item) ? key_item->valuestring : NULL;
|
||||
const char* value = cJSON_IsString(value_item) ? value_item->valuestring : NULL;
|
||||
|
||||
if (key && value) {
|
||||
offset += snprintf(config_text + offset, 8192 - offset,
|
||||
"%s: %s\n", key, value);
|
||||
}
|
||||
}
|
||||
cJSON_Delete(rows);
|
||||
|
||||
// Footer
|
||||
offset += snprintf(config_text + offset, 8192 - offset, "\n");
|
||||
|
||||
@@ -1627,13 +1483,13 @@ char* generate_stats_text(void) {
|
||||
|
||||
if (stats_obj) {
|
||||
// Extract basic metrics
|
||||
cJSON* total_events = cJSON_GetObjectItem(stats_obj, "total_events");
|
||||
cJSON* db_size = cJSON_GetObjectItem(stats_obj, "database_size_bytes");
|
||||
cJSON* oldest_event = cJSON_GetObjectItem(stats_obj, "database_created_at");
|
||||
cJSON* newest_event = cJSON_GetObjectItem(stats_obj, "latest_event_at");
|
||||
cJSON* time_stats = cJSON_GetObjectItem(stats_obj, "time_stats");
|
||||
cJSON* event_kinds = cJSON_GetObjectItem(stats_obj, "event_kinds");
|
||||
// cJSON* top_pubkeys = cJSON_GetObjectItem(stats_obj, "top_pubkeys");
|
||||
cJSON* total_events = cJSON_GetObjectItemCaseSensitive(stats_obj, "total_events");
|
||||
cJSON* db_size = cJSON_GetObjectItemCaseSensitive(stats_obj, "database_size_bytes");
|
||||
cJSON* oldest_event = cJSON_GetObjectItemCaseSensitive(stats_obj, "database_created_at");
|
||||
cJSON* newest_event = cJSON_GetObjectItemCaseSensitive(stats_obj, "latest_event_at");
|
||||
cJSON* time_stats = cJSON_GetObjectItemCaseSensitive(stats_obj, "time_stats");
|
||||
cJSON* event_kinds = cJSON_GetObjectItemCaseSensitive(stats_obj, "event_kinds");
|
||||
// cJSON* top_pubkeys = cJSON_GetObjectItemCaseSensitive(stats_obj, "top_pubkeys");
|
||||
|
||||
long long total = total_events ? (long long)cJSON_GetNumberValue(total_events) : 0;
|
||||
long long db_bytes = db_size ? (long long)cJSON_GetNumberValue(db_size) : 0;
|
||||
@@ -1661,9 +1517,9 @@ char* generate_stats_text(void) {
|
||||
// Extract time-based stats
|
||||
long long last_24h = 0, last_7d = 0, last_30d = 0;
|
||||
if (time_stats) {
|
||||
cJSON* h24 = cJSON_GetObjectItem(time_stats, "last_24h");
|
||||
cJSON* d7 = cJSON_GetObjectItem(time_stats, "last_7d");
|
||||
cJSON* d30 = cJSON_GetObjectItem(time_stats, "last_30d");
|
||||
cJSON* h24 = cJSON_GetObjectItemCaseSensitive(time_stats, "last_24h");
|
||||
cJSON* d7 = cJSON_GetObjectItemCaseSensitive(time_stats, "last_7d");
|
||||
cJSON* d30 = cJSON_GetObjectItemCaseSensitive(time_stats, "last_30d");
|
||||
last_24h = h24 ? (long long)cJSON_GetNumberValue(h24) : 0;
|
||||
last_7d = d7 ? (long long)cJSON_GetNumberValue(d7) : 0;
|
||||
last_30d = d30 ? (long long)cJSON_GetNumberValue(d30) : 0;
|
||||
@@ -1695,9 +1551,9 @@ char* generate_stats_text(void) {
|
||||
if (event_kinds && cJSON_IsArray(event_kinds)) {
|
||||
cJSON* kind_item = NULL;
|
||||
cJSON_ArrayForEach(kind_item, event_kinds) {
|
||||
cJSON* kind = cJSON_GetObjectItem(kind_item, "kind");
|
||||
cJSON* count = cJSON_GetObjectItem(kind_item, "count");
|
||||
cJSON* percentage = cJSON_GetObjectItem(kind_item, "percentage");
|
||||
cJSON* kind = cJSON_GetObjectItemCaseSensitive(kind_item, "kind");
|
||||
cJSON* count = cJSON_GetObjectItemCaseSensitive(kind_item, "count");
|
||||
cJSON* percentage = cJSON_GetObjectItemCaseSensitive(kind_item, "percentage");
|
||||
|
||||
if (kind && count && percentage) {
|
||||
// Format event kind (right-justified, minimum 5 chars wide with underscores)
|
||||
@@ -1767,9 +1623,9 @@ char* generate_stats_text(void) {
|
||||
// int rank = 1;
|
||||
// cJSON* pubkey_item = NULL;
|
||||
// cJSON_ArrayForEach(pubkey_item, top_pubkeys) {
|
||||
// cJSON* pubkey = cJSON_GetObjectItem(pubkey_item, "pubkey");
|
||||
// cJSON* event_count = cJSON_GetObjectItem(pubkey_item, "event_count");
|
||||
// cJSON* percentage = cJSON_GetObjectItem(pubkey_item, "percentage");
|
||||
// cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(pubkey_item, "pubkey");
|
||||
// cJSON* event_count = cJSON_GetObjectItemCaseSensitive(pubkey_item, "event_count");
|
||||
// cJSON* percentage = cJSON_GetObjectItemCaseSensitive(pubkey_item, "percentage");
|
||||
|
||||
// if (pubkey && event_count && percentage) {
|
||||
// const char* pubkey_str = cJSON_GetStringValue(pubkey);
|
||||
@@ -2136,8 +1992,7 @@ int apply_config_change(const char* key, const char* value) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
extern sqlite3* g_db;
|
||||
if (!g_db) {
|
||||
if (!db_is_available()) {
|
||||
DEBUG_ERROR("Database not available for config change");
|
||||
return -1;
|
||||
}
|
||||
@@ -2170,30 +2025,11 @@ int apply_config_change(const char* key, const char* value) {
|
||||
}
|
||||
|
||||
// Update or insert the configuration value
|
||||
sqlite3_stmt* stmt;
|
||||
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) {
|
||||
DEBUG_ERROR("Failed to prepare config update statement");
|
||||
const char* err_msg = sqlite3_errmsg(g_db);
|
||||
DEBUG_ERROR(err_msg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, normalized_value, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 3, data_type, -1, SQLITE_STATIC);
|
||||
|
||||
int result = sqlite3_step(stmt);
|
||||
if (result != SQLITE_DONE) {
|
||||
if (db_upsert_config_value(key, normalized_value, data_type) != 0) {
|
||||
DEBUG_ERROR("Failed to update configuration in database");
|
||||
const char* err_msg = sqlite3_errmsg(g_db);
|
||||
DEBUG_ERROR(err_msg);
|
||||
sqlite3_finalize(stmt);
|
||||
return -1;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2222,6 +2058,16 @@ char* generate_config_change_confirmation(const char* key, const char* old_value
|
||||
description = "This sets the maximum subscriptions per client.";
|
||||
} else if (strcmp(key, "pow_min_difficulty") == 0) {
|
||||
description = "This sets the minimum proof-of-work difficulty required.";
|
||||
} else if (strcmp(key, "idle_connection_timeout_sec") == 0) {
|
||||
description = "Seconds before an idle connection (no REQ/EVENT) is closed and IP flagged. 0 to disable.";
|
||||
} else if (strcmp(key, "idle_ban_enabled") == 0) {
|
||||
description = "Whether to ban IPs that repeatedly connect without sending REQ or EVENT.";
|
||||
} else if (strcmp(key, "idle_ban_threshold") == 0) {
|
||||
description = "Number of idle/early-disconnect failures before banning an IP.";
|
||||
} else if (strcmp(key, "idle_ban_window_sec") == 0) {
|
||||
description = "Time window in seconds for counting idle failures.";
|
||||
} else if (strcmp(key, "idle_ban_duration_sec") == 0) {
|
||||
description = "Initial ban duration in seconds for idle failures (doubles each time).";
|
||||
} else if (strstr(key, "relay_") == key) {
|
||||
description = "This changes relay metadata information.";
|
||||
}
|
||||
@@ -2457,7 +2303,7 @@ int handle_create_relay_event_command(cJSON* event, int kind, cJSON* event_data,
|
||||
}
|
||||
|
||||
// Get request event ID for response correlation
|
||||
cJSON* request_id_obj = cJSON_GetObjectItem(event, "id");
|
||||
cJSON* request_id_obj = cJSON_GetObjectItemCaseSensitive(event, "id");
|
||||
if (!request_id_obj || !cJSON_IsString(request_id_obj)) {
|
||||
snprintf(error_message, error_size, "Missing request event ID");
|
||||
return -1;
|
||||
@@ -2465,7 +2311,7 @@ int handle_create_relay_event_command(cJSON* event, int kind, cJSON* event_data,
|
||||
const char* request_id = cJSON_GetStringValue(request_id_obj);
|
||||
|
||||
// Get sender pubkey for response
|
||||
cJSON* sender_pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
cJSON* sender_pubkey_obj = cJSON_GetObjectItemCaseSensitive(event, "pubkey");
|
||||
if (!sender_pubkey_obj || !cJSON_IsString(sender_pubkey_obj)) {
|
||||
snprintf(error_message, error_size, "Missing sender pubkey");
|
||||
return -1;
|
||||
@@ -2614,7 +2460,7 @@ cJSON* create_relay_dm_list_event(cJSON* dm_relays) {
|
||||
}
|
||||
|
||||
// Extract relays array
|
||||
cJSON* relays_array = cJSON_GetObjectItem(dm_relays, "relays");
|
||||
cJSON* relays_array = cJSON_GetObjectItemCaseSensitive(dm_relays, "relays");
|
||||
if (relays_array && cJSON_IsArray(relays_array)) {
|
||||
cJSON* relay_item = NULL;
|
||||
cJSON_ArrayForEach(relay_item, relays_array) {
|
||||
@@ -2686,14 +2532,14 @@ cJSON* create_relay_list_event(cJSON* relays) {
|
||||
}
|
||||
|
||||
// Extract relays array
|
||||
cJSON* relays_array = cJSON_GetObjectItem(relays, "relays");
|
||||
cJSON* relays_array = cJSON_GetObjectItemCaseSensitive(relays, "relays");
|
||||
if (relays_array && cJSON_IsArray(relays_array)) {
|
||||
cJSON* relay_item = NULL;
|
||||
cJSON_ArrayForEach(relay_item, relays_array) {
|
||||
if (cJSON_IsObject(relay_item)) {
|
||||
cJSON* url = cJSON_GetObjectItem(relay_item, "url");
|
||||
cJSON* read = cJSON_GetObjectItem(relay_item, "read");
|
||||
cJSON* write = cJSON_GetObjectItem(relay_item, "write");
|
||||
cJSON* url = cJSON_GetObjectItemCaseSensitive(relay_item, "url");
|
||||
cJSON* read = cJSON_GetObjectItemCaseSensitive(relay_item, "read");
|
||||
cJSON* write = cJSON_GetObjectItemCaseSensitive(relay_item, "write");
|
||||
|
||||
if (url && cJSON_IsString(url)) {
|
||||
const char* relay_url = cJSON_GetStringValue(url);
|
||||
@@ -2752,7 +2598,7 @@ int handle_monitoring_command(cJSON* event, const char* command, char* error_mes
|
||||
}
|
||||
|
||||
// Get request event ID for response correlation
|
||||
cJSON* request_id_obj = cJSON_GetObjectItem(event, "id");
|
||||
cJSON* request_id_obj = cJSON_GetObjectItemCaseSensitive(event, "id");
|
||||
if (!request_id_obj || !cJSON_IsString(request_id_obj)) {
|
||||
snprintf(error_message, error_size, "Missing request event ID");
|
||||
return -1;
|
||||
@@ -2760,7 +2606,7 @@ int handle_monitoring_command(cJSON* event, const char* command, char* error_mes
|
||||
const char* request_id = cJSON_GetStringValue(request_id_obj);
|
||||
|
||||
// Get sender pubkey for response
|
||||
cJSON* sender_pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
cJSON* sender_pubkey_obj = cJSON_GetObjectItemCaseSensitive(event, "pubkey");
|
||||
if (!sender_pubkey_obj || !cJSON_IsString(sender_pubkey_obj)) {
|
||||
snprintf(error_message, error_size, "Missing sender pubkey");
|
||||
return -1;
|
||||
|
||||
+505
-680
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <cjson/cJSON.h>
|
||||
#include <time.h>
|
||||
#include <pthread.h>
|
||||
|
||||
+1318
File diff suppressed because it is too large
Load Diff
+114
@@ -0,0 +1,114 @@
|
||||
#ifndef DB_OPS_H
|
||||
#define DB_OPS_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <time.h>
|
||||
#include <cjson/cJSON.h>
|
||||
|
||||
// Generic helpers
|
||||
int db_init(const char* connection_string);
|
||||
void db_close(void);
|
||||
int db_is_available(void);
|
||||
const char* db_last_error(void);
|
||||
const char* db_get_database_path(void);
|
||||
|
||||
// Per-thread connection override (used by thread pool workers)
|
||||
int db_set_thread_connection(void* connection);
|
||||
void db_clear_thread_connection(void);
|
||||
|
||||
// DB result codes (backend-agnostic)
|
||||
#define DB_OK 0
|
||||
#define DB_ERROR 1
|
||||
#define DB_CONSTRAINT 19
|
||||
#define DB_MISUSE 21
|
||||
#define DB_ROW 100
|
||||
#define DB_DONE 101
|
||||
|
||||
typedef struct db_stmt db_stmt_t;
|
||||
|
||||
// Generic statement helpers (Phase 1 migration)
|
||||
int db_prepare(const char* sql, db_stmt_t** out_stmt);
|
||||
int db_bind_text_param(db_stmt_t* stmt, int index, const char* value);
|
||||
int db_bind_int_param(db_stmt_t* stmt, int index, int value);
|
||||
int db_bind_int64_param(db_stmt_t* stmt, int index, long long value);
|
||||
int db_step_stmt(db_stmt_t* stmt);
|
||||
int db_reset_stmt(db_stmt_t* stmt);
|
||||
const char* db_column_text_value(db_stmt_t* stmt, int col);
|
||||
int db_column_int_value(db_stmt_t* stmt, int col);
|
||||
long long db_column_int64_value(db_stmt_t* stmt, int col);
|
||||
double db_column_double_value(db_stmt_t* stmt, int col);
|
||||
void db_finalize_stmt(db_stmt_t* stmt);
|
||||
|
||||
// Subscription logging
|
||||
int db_log_subscription_created(const char* sub_id, const char* wsi_ptr,
|
||||
const char* client_ip, const char* filter_json);
|
||||
int db_log_subscription_closed(const char* sub_id, const char* client_ip);
|
||||
int db_log_subscription_disconnected(const char* client_ip);
|
||||
int db_update_subscription_events_sent(const char* sub_id, int events_sent);
|
||||
int db_cleanup_orphaned_subscriptions(void);
|
||||
|
||||
// NIP-09 event deletion helpers
|
||||
int db_get_event_pubkey(const char* event_id, char* pubkey_out, size_t pubkey_out_size);
|
||||
int db_delete_event_by_id(const char* event_id, const char* requester_pubkey);
|
||||
int db_delete_events_by_address(const char* pubkey, int kind,
|
||||
const char* d_tag, long before_timestamp);
|
||||
|
||||
// Auth rule checks for request validator
|
||||
int db_is_pubkey_blacklisted(const char* pubkey);
|
||||
int db_is_hash_blacklisted(const char* resource_hash);
|
||||
int db_is_pubkey_whitelisted(const char* pubkey);
|
||||
int db_count_active_whitelist_rules(void);
|
||||
|
||||
// Generic prepared COUNT helper
|
||||
int db_count_with_sql(const char* sql, const char** bind_params, int bind_param_count, int* out_count);
|
||||
char* db_execute_readonly_query_json(const char* query, const char* request_id,
|
||||
char* error_message, size_t error_size,
|
||||
int max_rows, int timeout_ms);
|
||||
|
||||
// Monitoring/stat helpers (Phase 1 api.c migration)
|
||||
int db_get_total_event_count_ll(long long* out_count);
|
||||
int db_get_event_count_since(time_t cutoff, long long* out_count);
|
||||
cJSON* db_get_event_kind_distribution_rows(long long* out_total_events);
|
||||
cJSON* db_get_top_pubkeys_rows(int limit);
|
||||
cJSON* db_get_subscription_details_rows(void);
|
||||
|
||||
// Config helpers
|
||||
cJSON* db_get_all_config_rows(void);
|
||||
char* db_get_config_value_dup(const char* key);
|
||||
int db_set_config_value_full(const char* key, const char* value, const char* data_type,
|
||||
const char* description, const char* category, int requires_restart);
|
||||
int db_update_config_value_only(const char* key, const char* value);
|
||||
int db_upsert_config_value(const char* key, const char* value, const char* data_type);
|
||||
int db_store_relay_private_key_hex(const char* relay_privkey_hex);
|
||||
char* db_get_relay_private_key_hex_dup(void);
|
||||
int db_store_config_event(const cJSON* event);
|
||||
|
||||
// Event storage/timestamp/retrieval helpers
|
||||
int db_insert_event_with_json(const char* id, const char* pubkey, long long created_at,
|
||||
int kind, const char* event_type, const char* content,
|
||||
const char* sig, const char* tags_json, const char* event_json,
|
||||
int* out_step_rc, int* out_extended_errcode);
|
||||
int db_get_event_time_bounds(long long* out_min_created_at, long long* out_max_created_at);
|
||||
int db_event_id_exists(const char* event_id, int* out_exists);
|
||||
cJSON* db_retrieve_event_by_id(const char* event_id);
|
||||
char* db_get_latest_event_pubkey_for_kind_dup(int kind);
|
||||
|
||||
// Config table helpers
|
||||
int db_get_config_row_count(int* out_count);
|
||||
|
||||
// Event tag helpers
|
||||
int db_store_event_tags_cjson(const char* event_id, const cJSON* tags);
|
||||
int db_populate_event_tags_from_existing(void);
|
||||
|
||||
// Auth/WoT rule helpers
|
||||
int db_add_auth_rule(const char* rule_type, const char* pattern_type, const char* pattern_value);
|
||||
int db_remove_auth_rule(const char* rule_type, const char* pattern_type, const char* pattern_value);
|
||||
int db_delete_wot_whitelist_rules(void);
|
||||
int db_count_wot_whitelist_rules(void);
|
||||
|
||||
// Schema/DDL helpers
|
||||
int db_table_exists(const char* table_name, int* out_exists);
|
||||
char* db_get_schema_version_dup(void);
|
||||
int db_exec_sql(const char* sql);
|
||||
|
||||
#endif // DB_OPS_H
|
||||
@@ -83,6 +83,38 @@ static const struct {
|
||||
// IP-based rate limiting or access control (which would require firewall protection anyway)
|
||||
{"trust_proxy_headers", "true"},
|
||||
|
||||
// Debug Level (0=none, 1=errors, 2=warnings, 3=info, 4=debug, 5=trace)
|
||||
// Can be changed at runtime without restart via config_set admin command
|
||||
{"debug_level", "3"},
|
||||
|
||||
// IP Auth Failure Ban Settings
|
||||
// Ban IPs that repeatedly fail NIP-42 authentication
|
||||
{"auth_fail_ban_enabled", "true"},
|
||||
{"auth_fail_ban_threshold", "3"}, // failures before ban
|
||||
{"auth_fail_window_sec", "60"}, // window to count failures in
|
||||
{"auth_fail_ban_duration_sec", "300"}, // initial ban duration (doubles each time, max 24h)
|
||||
|
||||
// NIP-42 Authentication Timeout
|
||||
// Seconds after connection before unauthenticated clients are disconnected (0 = disabled)
|
||||
// Prevents unauthenticated connections from accumulating under heavy load
|
||||
{"nip42_auth_timeout_sec", "10"},
|
||||
|
||||
// Idle Connection Ban Settings
|
||||
// Ban IPs that connect but never send REQ or EVENT (idle or early disconnect)
|
||||
{"idle_connection_timeout_sec", "0"}, // Seconds before idle connection is closed (0 = disabled)
|
||||
{"idle_ban_enabled", "false"}, // Whether to ban IPs with idle failures
|
||||
{"idle_ban_threshold", "1"}, // Idle failures before ban (1 = ban on first offense)
|
||||
{"idle_ban_window_sec", "30"}, // Window to count idle failures in
|
||||
{"idle_ban_duration_sec", "300"}, // Initial ban duration (doubles each time, max 24h)
|
||||
|
||||
// SQLite Performance Tuning
|
||||
// mmap_size: bytes of database file to memory-map (0 = disabled, 268435456 = 256MB recommended)
|
||||
// Eliminates pread64 syscall overhead for database reads — significant CPU savings under load
|
||||
{"sqlite_mmap_size", "268435456"},
|
||||
// cache_size_kb: SQLite page cache size in KB (negative = KB, positive = pages of 4KB each)
|
||||
// Default 2000KB is too small for a busy relay; 65536KB (64MB) keeps hot data in memory
|
||||
{"sqlite_cache_size_kb", "65536"},
|
||||
|
||||
// NIP-59 Gift Wrap Timestamp Configuration
|
||||
{"nip59_timestamp_max_delay_sec", "0"},
|
||||
|
||||
|
||||
+13
-25
@@ -5,6 +5,7 @@
|
||||
#include "config.h"
|
||||
#include "debug.h"
|
||||
#include "api.h"
|
||||
#include "db_ops.h"
|
||||
#include "../nostr_core_lib/nostr_core/nostr_core.h"
|
||||
#include "../nostr_core_lib/nostr_core/nip017.h"
|
||||
#include "../nostr_core_lib/nostr_core/nip044.h"
|
||||
@@ -17,9 +18,6 @@
|
||||
#include <cjson/cJSON.h>
|
||||
#include <libwebsockets.h>
|
||||
|
||||
// External database connection (from main.c)
|
||||
extern sqlite3* g_db;
|
||||
|
||||
// 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_config_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi);
|
||||
@@ -141,7 +139,7 @@ int process_dm_admin_command(cJSON* command_array, cJSON* event, char* error_mes
|
||||
cJSON_AddItemToArray(synthetic_tags, command_tag);
|
||||
|
||||
// Add existing event tags
|
||||
cJSON* existing_tags = cJSON_GetObjectItem(event, "tags");
|
||||
cJSON* existing_tags = cJSON_GetObjectItemCaseSensitive(event, "tags");
|
||||
if (existing_tags && cJSON_IsArray(existing_tags)) {
|
||||
cJSON* tag = NULL;
|
||||
cJSON_ArrayForEach(tag, existing_tags) {
|
||||
@@ -304,7 +302,7 @@ cJSON* process_nip17_admin_message(cJSON* gift_wrap_event, char* error_message,
|
||||
// Only create a generic response for other command types that don't handle their own responses
|
||||
if (result == 0) {
|
||||
// Extract content to check if it's a plain text command
|
||||
cJSON* content_obj = cJSON_GetObjectItem(inner_dm, "content");
|
||||
cJSON* content_obj = cJSON_GetObjectItemCaseSensitive(inner_dm, "content");
|
||||
if (content_obj && cJSON_IsString(content_obj)) {
|
||||
const char* dm_content = cJSON_GetStringValue(content_obj);
|
||||
|
||||
@@ -347,7 +345,7 @@ cJSON* process_nip17_admin_message(cJSON* gift_wrap_event, char* error_message,
|
||||
return NULL;
|
||||
|
||||
// Get sender pubkey for response from the decrypted DM event
|
||||
cJSON* sender_pubkey_obj = cJSON_GetObjectItem(inner_dm, "pubkey");
|
||||
cJSON* sender_pubkey_obj = cJSON_GetObjectItemCaseSensitive(inner_dm, "pubkey");
|
||||
if (sender_pubkey_obj && cJSON_IsString(sender_pubkey_obj)) {
|
||||
const char* sender_pubkey = cJSON_GetStringValue(sender_pubkey_obj);
|
||||
|
||||
@@ -439,13 +437,13 @@ int is_nip17_gift_wrap_for_relay(cJSON* event) {
|
||||
}
|
||||
|
||||
// Check kind
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
cJSON* kind_obj = cJSON_GetObjectItemCaseSensitive(event, "kind");
|
||||
if (!kind_obj || !cJSON_IsNumber(kind_obj) || (int)cJSON_GetNumberValue(kind_obj) != 1059) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check tags for "p" tag with relay pubkey
|
||||
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
||||
cJSON* tags = cJSON_GetObjectItemCaseSensitive(event, "tags");
|
||||
if (!tags || !cJSON_IsArray(tags)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -484,7 +482,7 @@ int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t err
|
||||
DEBUG_INFO("DM_ADMIN: Processing NIP-17 admin command from decrypted DM");
|
||||
|
||||
// Extract content from DM
|
||||
cJSON* content_obj = cJSON_GetObjectItem(dm_event, "content");
|
||||
cJSON* content_obj = cJSON_GetObjectItemCaseSensitive(dm_event, "content");
|
||||
if (!content_obj || !cJSON_IsString(content_obj)) {
|
||||
DEBUG_INFO("DM_ADMIN: DM missing content field");
|
||||
strncpy(error_message, "NIP-17: DM missing content", error_size - 1);
|
||||
@@ -495,7 +493,7 @@ int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t err
|
||||
DEBUG_INFO("DM_ADMIN: Extracted DM content: %.100s%s", dm_content, strlen(dm_content) > 100 ? "..." : "");
|
||||
|
||||
// Check if sender is admin before processing any commands
|
||||
cJSON* sender_pubkey_obj = cJSON_GetObjectItem(dm_event, "pubkey");
|
||||
cJSON* sender_pubkey_obj = cJSON_GetObjectItemCaseSensitive(dm_event, "pubkey");
|
||||
if (!sender_pubkey_obj || !cJSON_IsString(sender_pubkey_obj)) {
|
||||
DEBUG_INFO("DM_ADMIN: DM missing sender pubkey - treating as user DM");
|
||||
return 0; // Not an error, just treat as user DM
|
||||
@@ -622,8 +620,7 @@ int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t err
|
||||
update_config_in_table("nip42_auth_required_subscriptions", "false");
|
||||
|
||||
// Clear wot_whitelist rules
|
||||
const char* delete_sql = "DELETE FROM auth_rules WHERE rule_type = 'wot_whitelist'";
|
||||
sqlite3_exec(g_db, delete_sql, NULL, NULL, NULL);
|
||||
db_delete_wot_whitelist_rules();
|
||||
|
||||
snprintf(response_msg, sizeof(response_msg),
|
||||
"🔓 Web of Trust Disabled\n"
|
||||
@@ -695,16 +692,7 @@ int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t err
|
||||
else if (strcmp(wot_cmd, "status") == 0 || strlen(wot_cmd) == 0) {
|
||||
// Show status
|
||||
// Count whitelisted pubkeys
|
||||
sqlite3_stmt* stmt;
|
||||
const char* count_sql = "SELECT COUNT(*) FROM auth_rules WHERE rule_type = 'wot_whitelist' AND active = 1";
|
||||
int whitelist_count = 0;
|
||||
|
||||
if (sqlite3_prepare_v2(g_db, count_sql, -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
whitelist_count = sqlite3_column_int(stmt, 0);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
int whitelist_count = db_count_wot_whitelist_rules();
|
||||
|
||||
const char* level_str;
|
||||
if (wot_level == 0) level_str = "Off (open relay)";
|
||||
@@ -804,7 +792,7 @@ int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t err
|
||||
if (cJSON_IsString(first_item) && strcmp(cJSON_GetStringValue(first_item), "stats") == 0) {
|
||||
DEBUG_INFO("DM_ADMIN: Processing JSON stats command");
|
||||
// Get sender pubkey for response
|
||||
cJSON* sender_pubkey_obj = cJSON_GetObjectItem(dm_event, "pubkey");
|
||||
cJSON* sender_pubkey_obj = cJSON_GetObjectItemCaseSensitive(dm_event, "pubkey");
|
||||
if (!sender_pubkey_obj || !cJSON_IsString(sender_pubkey_obj)) {
|
||||
cJSON_Delete(command_array);
|
||||
DEBUG_INFO("DM_ADMIN: DM missing sender pubkey for stats command");
|
||||
@@ -846,13 +834,13 @@ int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t err
|
||||
cJSON_AddStringToObject(synthetic_event, "content", dm_content);
|
||||
|
||||
// Copy pubkey from DM
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(dm_event, "pubkey");
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItemCaseSensitive(dm_event, "pubkey");
|
||||
if (pubkey_obj && cJSON_IsString(pubkey_obj)) {
|
||||
cJSON_AddStringToObject(synthetic_event, "pubkey", cJSON_GetStringValue(pubkey_obj));
|
||||
}
|
||||
|
||||
// Copy tags from DM
|
||||
cJSON* tags = cJSON_GetObjectItem(dm_event, "tags");
|
||||
cJSON* tags = cJSON_GetObjectItemCaseSensitive(dm_event, "tags");
|
||||
if (tags) {
|
||||
cJSON_AddItemToObject(synthetic_event, "tags", cJSON_Duplicate(tags, 1));
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
+581
@@ -0,0 +1,581 @@
|
||||
#define _GNU_SOURCE
|
||||
#include "ip_ban.h"
|
||||
#include "debug.h"
|
||||
#include "config.h"
|
||||
#include "db_ops.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
|
||||
// ============================================================
|
||||
// IP Auth Failure Ban System
|
||||
//
|
||||
// Fixed-size open-addressing hash table. No malloc after init.
|
||||
// Thread-safe via a single mutex (low contention — only called
|
||||
// at connection open/close, not in the hot event path).
|
||||
//
|
||||
// State is persisted to the ip_bans SQLite table every 5 minutes
|
||||
// and loaded at startup so bans survive relay restarts.
|
||||
// ============================================================
|
||||
|
||||
#define IP_BAN_EMPTY 0
|
||||
#define IP_BAN_ACTIVE 1
|
||||
|
||||
typedef struct {
|
||||
int state; // IP_BAN_EMPTY or IP_BAN_ACTIVE
|
||||
char ip[46]; // IPv4 or IPv6 string
|
||||
|
||||
// Auth failure tracking (existing)
|
||||
int failure_count; // failures in current window
|
||||
time_t first_failure; // start of current failure window
|
||||
time_t banned_until; // 0 = not banned
|
||||
int ban_count; // escalation level (for exponential backoff)
|
||||
|
||||
// NEW: Idle failure tracking (separate)
|
||||
int idle_failure_count;
|
||||
time_t idle_first_failure;
|
||||
time_t idle_banned_until;
|
||||
int idle_ban_count;
|
||||
|
||||
// Other existing fields
|
||||
int has_authed_successfully; // 1 if this IP has ever authenticated
|
||||
time_t last_success_at; // timestamp of last successful auth
|
||||
int total_connections; // lifetime connection count
|
||||
int total_failures; // lifetime auth failure count
|
||||
int total_successes; // lifetime successful auth count
|
||||
time_t first_seen; // when this IP was first seen
|
||||
} ip_ban_entry_t;
|
||||
|
||||
static ip_ban_entry_t g_ban_table[IP_BAN_TABLE_SIZE];
|
||||
static pthread_mutex_t g_ban_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static int g_initialized = 0;
|
||||
|
||||
// Simple FNV-1a hash for IP strings
|
||||
static unsigned int ip_hash(const char* ip) {
|
||||
unsigned int hash = 2166136261u;
|
||||
while (*ip) {
|
||||
hash ^= (unsigned char)*ip++;
|
||||
hash *= 16777619u;
|
||||
}
|
||||
return hash % IP_BAN_TABLE_SIZE;
|
||||
}
|
||||
|
||||
// Find slot for IP (open addressing with linear probing)
|
||||
static int find_slot(const char* ip) {
|
||||
unsigned int start = ip_hash(ip);
|
||||
for (unsigned int i = 0; i < IP_BAN_TABLE_SIZE; i++) {
|
||||
unsigned int idx = (start + i) % IP_BAN_TABLE_SIZE;
|
||||
if (g_ban_table[idx].state == IP_BAN_EMPTY) {
|
||||
return (int)idx;
|
||||
}
|
||||
if (strcmp(g_ban_table[idx].ip, ip) == 0) {
|
||||
return (int)idx;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get or create an entry for an IP. Returns NULL if table is full.
|
||||
// Caller must hold g_ban_mutex.
|
||||
static ip_ban_entry_t* get_or_create_entry(const char* ip) {
|
||||
int idx = find_slot(ip);
|
||||
if (idx < 0) {
|
||||
DEBUG_WARN("IP ban table full, cannot track %s", ip);
|
||||
return NULL;
|
||||
}
|
||||
ip_ban_entry_t* entry = &g_ban_table[idx];
|
||||
if (entry->state == IP_BAN_EMPTY) {
|
||||
entry->state = IP_BAN_ACTIVE;
|
||||
strncpy(entry->ip, ip, sizeof(entry->ip) - 1);
|
||||
entry->ip[sizeof(entry->ip) - 1] = '\0';
|
||||
entry->first_seen = time(NULL);
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
void ip_ban_init(void) {
|
||||
pthread_mutex_lock(&g_ban_mutex);
|
||||
memset(g_ban_table, 0, sizeof(g_ban_table));
|
||||
g_initialized = 1;
|
||||
pthread_mutex_unlock(&g_ban_mutex);
|
||||
DEBUG_LOG("IP ban table initialized (%d slots)", IP_BAN_TABLE_SIZE);
|
||||
}
|
||||
|
||||
void ip_ban_load_from_db(void) {
|
||||
if (!db_is_available() || !g_initialized) return;
|
||||
|
||||
// Create table if it doesn't exist (handles existing databases)
|
||||
const char* create_sql =
|
||||
"CREATE TABLE IF NOT EXISTS ip_bans ("
|
||||
" ip TEXT PRIMARY KEY,"
|
||||
" failure_count INTEGER NOT NULL DEFAULT 0,"
|
||||
" ban_count INTEGER NOT NULL DEFAULT 0,"
|
||||
" banned_until INTEGER NOT NULL DEFAULT 0,"
|
||||
" first_failure INTEGER NOT NULL DEFAULT 0,"
|
||||
" has_authed_successfully INTEGER NOT NULL DEFAULT 0,"
|
||||
" last_success_at INTEGER NOT NULL DEFAULT 0,"
|
||||
" total_connections INTEGER NOT NULL DEFAULT 0,"
|
||||
" total_failures INTEGER NOT NULL DEFAULT 0,"
|
||||
" total_successes INTEGER NOT NULL DEFAULT 0,"
|
||||
" first_seen INTEGER NOT NULL DEFAULT 0,"
|
||||
" updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))"
|
||||
");";
|
||||
if (db_exec_sql(create_sql) != 0) {
|
||||
DEBUG_ERROR("Failed to create ip_bans table: %s", db_last_error());
|
||||
return;
|
||||
}
|
||||
|
||||
// Migration: Add idle_* columns if they don't exist (ignore errors if already exists)
|
||||
(void)db_exec_sql("ALTER TABLE ip_bans ADD COLUMN idle_failure_count INTEGER NOT NULL DEFAULT 0");
|
||||
(void)db_exec_sql("ALTER TABLE ip_bans ADD COLUMN idle_ban_count INTEGER NOT NULL DEFAULT 0");
|
||||
(void)db_exec_sql("ALTER TABLE ip_bans ADD COLUMN idle_banned_until INTEGER NOT NULL DEFAULT 0");
|
||||
(void)db_exec_sql("ALTER TABLE ip_bans ADD COLUMN idle_first_failure INTEGER NOT NULL DEFAULT 0");
|
||||
|
||||
const char* sql =
|
||||
"SELECT ip, failure_count, ban_count, banned_until, first_failure,"
|
||||
" has_authed_successfully, last_success_at, total_connections,"
|
||||
" total_failures, total_successes, first_seen,"
|
||||
" idle_failure_count, idle_ban_count, idle_banned_until, idle_first_failure"
|
||||
" FROM ip_bans";
|
||||
|
||||
db_stmt_t* stmt;
|
||||
if (db_prepare(sql, &stmt) != DB_OK) {
|
||||
DEBUG_ERROR("Failed to prepare ip_bans load query");
|
||||
return;
|
||||
}
|
||||
|
||||
int loaded = 0;
|
||||
pthread_mutex_lock(&g_ban_mutex);
|
||||
while (db_step_stmt(stmt) == DB_ROW) {
|
||||
const char* ip = db_column_text_value(stmt, 0);
|
||||
if (!ip) continue;
|
||||
|
||||
int idx = find_slot(ip);
|
||||
if (idx < 0) continue;
|
||||
|
||||
ip_ban_entry_t* entry = &g_ban_table[idx];
|
||||
entry->state = IP_BAN_ACTIVE;
|
||||
strncpy(entry->ip, ip, sizeof(entry->ip) - 1);
|
||||
entry->ip[sizeof(entry->ip) - 1] = '\0';
|
||||
entry->failure_count = db_column_int_value(stmt, 1);
|
||||
entry->ban_count = db_column_int_value(stmt, 2);
|
||||
entry->banned_until = (time_t)db_column_int64_value(stmt, 3);
|
||||
entry->first_failure = (time_t)db_column_int64_value(stmt, 4);
|
||||
entry->has_authed_successfully = db_column_int_value(stmt, 5);
|
||||
entry->last_success_at = (time_t)db_column_int64_value(stmt, 6);
|
||||
entry->total_connections = db_column_int_value(stmt, 7);
|
||||
entry->total_failures = db_column_int_value(stmt, 8);
|
||||
entry->total_successes = db_column_int_value(stmt, 9);
|
||||
entry->first_seen = (time_t)db_column_int64_value(stmt, 10);
|
||||
// Load idle tracking fields (default to 0 if columns don't exist yet)
|
||||
entry->idle_failure_count = db_column_int_value(stmt, 11);
|
||||
entry->idle_ban_count = db_column_int_value(stmt, 12);
|
||||
entry->idle_banned_until = (time_t)db_column_int64_value(stmt, 13);
|
||||
entry->idle_first_failure = (time_t)db_column_int64_value(stmt, 14);
|
||||
loaded++;
|
||||
}
|
||||
pthread_mutex_unlock(&g_ban_mutex);
|
||||
db_finalize_stmt(stmt);
|
||||
|
||||
// Count how many are still actively banned
|
||||
time_t now = time(NULL);
|
||||
int still_banned = 0;
|
||||
pthread_mutex_lock(&g_ban_mutex);
|
||||
for (int i = 0; i < IP_BAN_TABLE_SIZE; i++) {
|
||||
if (g_ban_table[i].state == IP_BAN_ACTIVE &&
|
||||
g_ban_table[i].banned_until > now) {
|
||||
still_banned++;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&g_ban_mutex);
|
||||
|
||||
DEBUG_WARN("IP ban table loaded: %d IPs restored (%d still banned)", loaded, still_banned);
|
||||
}
|
||||
|
||||
void ip_ban_save_to_db(void) {
|
||||
if (!db_is_available() || !g_initialized) return;
|
||||
|
||||
const char* upsert_sql =
|
||||
"INSERT OR REPLACE INTO ip_bans"
|
||||
" (ip, failure_count, ban_count, banned_until, first_failure,"
|
||||
" has_authed_successfully, last_success_at, total_connections,"
|
||||
" total_failures, total_successes, first_seen, updated_at,"
|
||||
" idle_failure_count, idle_ban_count, idle_banned_until, idle_first_failure)"
|
||||
" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, strftime('%s', 'now'), ?, ?, ?, ?)";
|
||||
|
||||
db_stmt_t* stmt;
|
||||
if (db_prepare(upsert_sql, &stmt) != DB_OK) {
|
||||
DEBUG_ERROR("Failed to prepare ip_bans save query");
|
||||
return;
|
||||
}
|
||||
|
||||
(void)db_exec_sql("BEGIN TRANSACTION");
|
||||
|
||||
int saved = 0;
|
||||
pthread_mutex_lock(&g_ban_mutex);
|
||||
for (int i = 0; i < IP_BAN_TABLE_SIZE; i++) {
|
||||
ip_ban_entry_t* entry = &g_ban_table[i];
|
||||
if (entry->state != IP_BAN_ACTIVE) continue;
|
||||
|
||||
(void)db_reset_stmt(stmt);
|
||||
db_bind_text_param(stmt, 1, entry->ip);
|
||||
db_bind_int_param(stmt, 2, entry->failure_count);
|
||||
db_bind_int_param(stmt, 3, entry->ban_count);
|
||||
db_bind_int64_param(stmt, 4, (long long)entry->banned_until);
|
||||
db_bind_int64_param(stmt, 5, (long long)entry->first_failure);
|
||||
db_bind_int_param(stmt, 6, entry->has_authed_successfully);
|
||||
db_bind_int64_param(stmt, 7, (long long)entry->last_success_at);
|
||||
db_bind_int_param(stmt, 8, entry->total_connections);
|
||||
db_bind_int_param(stmt, 9, entry->total_failures);
|
||||
db_bind_int_param(stmt, 10, entry->total_successes);
|
||||
db_bind_int64_param(stmt, 11, (long long)entry->first_seen);
|
||||
// Idle tracking fields
|
||||
db_bind_int_param(stmt, 12, entry->idle_failure_count);
|
||||
db_bind_int_param(stmt, 13, entry->idle_ban_count);
|
||||
db_bind_int64_param(stmt, 14, (long long)entry->idle_banned_until);
|
||||
db_bind_int64_param(stmt, 15, (long long)entry->idle_first_failure);
|
||||
|
||||
if (db_step_stmt(stmt) != DB_DONE) {
|
||||
DEBUG_WARN("Failed to save ip_ban entry for %s", entry->ip);
|
||||
} else {
|
||||
saved++;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&g_ban_mutex);
|
||||
|
||||
db_finalize_stmt(stmt);
|
||||
(void)db_exec_sql("COMMIT");
|
||||
|
||||
DEBUG_TRACE("IP ban table saved: %d entries written to DB", saved);
|
||||
}
|
||||
|
||||
// Check if an IP is in the idle_ban_whitelist config (comma-separated list)
|
||||
static int ip_is_whitelisted(const char* ip) {
|
||||
const char* whitelist = get_config_value("idle_ban_whitelist");
|
||||
if (!whitelist || whitelist[0] == '\0') {
|
||||
if (whitelist) free((char*)whitelist);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Make a mutable copy to tokenize
|
||||
char buf[1024];
|
||||
strncpy(buf, whitelist, sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
|
||||
int is_match = 0;
|
||||
char* token = strtok(buf, ",");
|
||||
while (token) {
|
||||
// Trim leading/trailing spaces
|
||||
while (*token == ' ') token++;
|
||||
char* end = token + strlen(token) - 1;
|
||||
while (end > token && *end == ' ') { *end = '\0'; end--; }
|
||||
if (strcmp(token, ip) == 0) {
|
||||
is_match = 1;
|
||||
break;
|
||||
}
|
||||
token = strtok(NULL, ",");
|
||||
}
|
||||
|
||||
free((char*)whitelist);
|
||||
return is_match;
|
||||
}
|
||||
|
||||
int ip_ban_is_banned(const char* ip) {
|
||||
if (!ip || !g_initialized) return 0;
|
||||
|
||||
// Whitelisted IPs are never banned
|
||||
if (ip_is_whitelisted(ip)) return 0;
|
||||
|
||||
pthread_mutex_lock(&g_ban_mutex);
|
||||
int idx = find_slot(ip);
|
||||
if (idx < 0 || g_ban_table[idx].state == IP_BAN_EMPTY) {
|
||||
pthread_mutex_unlock(&g_ban_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ip_ban_entry_t* entry = &g_ban_table[idx];
|
||||
time_t now = time(NULL);
|
||||
int banned = 0;
|
||||
|
||||
// Check auth ban (if enabled)
|
||||
if (get_config_bool("auth_fail_ban_enabled", 1) &&
|
||||
entry->banned_until > 0 && now < entry->banned_until) {
|
||||
banned = 1;
|
||||
}
|
||||
|
||||
// Check idle ban (if enabled)
|
||||
if (get_config_bool("idle_ban_enabled", 1) &&
|
||||
entry->idle_banned_until > 0 && now < entry->idle_banned_until) {
|
||||
banned = 1;
|
||||
}
|
||||
|
||||
// Clear expired bans
|
||||
if (!banned) {
|
||||
if (entry->banned_until > 0 && now >= entry->banned_until) {
|
||||
entry->banned_until = 0;
|
||||
entry->failure_count = 0;
|
||||
entry->first_failure = 0;
|
||||
}
|
||||
if (entry->idle_banned_until > 0 && now >= entry->idle_banned_until) {
|
||||
entry->idle_banned_until = 0;
|
||||
entry->idle_failure_count = 0;
|
||||
entry->idle_first_failure = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_ban_mutex);
|
||||
return banned;
|
||||
}
|
||||
|
||||
void ip_ban_record_connection(const char* ip) {
|
||||
if (!ip || !g_initialized) return;
|
||||
|
||||
pthread_mutex_lock(&g_ban_mutex);
|
||||
ip_ban_entry_t* entry = get_or_create_entry(ip);
|
||||
if (entry) {
|
||||
entry->total_connections++;
|
||||
}
|
||||
pthread_mutex_unlock(&g_ban_mutex);
|
||||
}
|
||||
|
||||
void ip_ban_record_failure(const char* ip) {
|
||||
if (!ip || !g_initialized) return;
|
||||
if (!get_config_bool("auth_fail_ban_enabled", 1)) return;
|
||||
|
||||
int threshold = get_config_int("auth_fail_ban_threshold", 3);
|
||||
int window_sec = get_config_int("auth_fail_window_sec", 60);
|
||||
int ban_duration = get_config_int("auth_fail_ban_duration_sec", 300);
|
||||
|
||||
pthread_mutex_lock(&g_ban_mutex);
|
||||
ip_ban_entry_t* entry = get_or_create_entry(ip);
|
||||
if (!entry) {
|
||||
pthread_mutex_unlock(&g_ban_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
time_t now = time(NULL);
|
||||
|
||||
// Reset window if expired
|
||||
if (entry->first_failure > 0 && (now - entry->first_failure) > window_sec) {
|
||||
entry->failure_count = 0;
|
||||
entry->first_failure = now;
|
||||
}
|
||||
if (entry->first_failure == 0) {
|
||||
entry->first_failure = now;
|
||||
}
|
||||
|
||||
entry->failure_count++;
|
||||
entry->total_failures++;
|
||||
|
||||
DEBUG_TRACE("IP %s auth failure count: %d/%d", ip, entry->failure_count, threshold);
|
||||
|
||||
if (entry->failure_count >= threshold) {
|
||||
int duration = ban_duration;
|
||||
for (int i = 0; i < entry->ban_count && duration < 86400; i++) {
|
||||
duration *= 2;
|
||||
}
|
||||
if (duration > 86400) duration = 86400;
|
||||
|
||||
entry->banned_until = now + duration;
|
||||
entry->ban_count++;
|
||||
entry->failure_count = 0;
|
||||
entry->first_failure = 0;
|
||||
|
||||
DEBUG_WARN("IP %s banned for %d seconds (ban #%d) after %d auth failures",
|
||||
ip, duration, entry->ban_count, threshold);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_ban_mutex);
|
||||
}
|
||||
|
||||
// Record an idle/early-disconnect failure for an IP
|
||||
void ip_ban_record_idle_failure(const char* ip) {
|
||||
if (!ip || !g_initialized) return;
|
||||
if (!get_config_bool("idle_ban_enabled", 1)) return;
|
||||
if (ip_is_whitelisted(ip)) return; // Never record idle failures for whitelisted IPs
|
||||
|
||||
int threshold = get_config_int("idle_ban_threshold", 1);
|
||||
int window_sec = get_config_int("idle_ban_window_sec", 30);
|
||||
int ban_duration = get_config_int("idle_ban_duration_sec", 300);
|
||||
|
||||
pthread_mutex_lock(&g_ban_mutex);
|
||||
ip_ban_entry_t* entry = get_or_create_entry(ip);
|
||||
if (!entry) {
|
||||
pthread_mutex_unlock(&g_ban_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
time_t now = time(NULL);
|
||||
|
||||
// Reset window if expired
|
||||
if (entry->idle_first_failure > 0 && (now - entry->idle_first_failure) > window_sec) {
|
||||
entry->idle_failure_count = 0;
|
||||
entry->idle_first_failure = now;
|
||||
}
|
||||
if (entry->idle_first_failure == 0) {
|
||||
entry->idle_first_failure = now;
|
||||
}
|
||||
|
||||
entry->idle_failure_count++;
|
||||
entry->total_failures++;
|
||||
|
||||
DEBUG_TRACE("IP %s idle failure count: %d/%d", ip, entry->idle_failure_count, threshold);
|
||||
|
||||
if (entry->idle_failure_count >= threshold) {
|
||||
int duration = ban_duration;
|
||||
for (int i = 0; i < entry->idle_ban_count && duration < 86400; i++) {
|
||||
duration *= 2;
|
||||
}
|
||||
if (duration > 86400) duration = 86400;
|
||||
|
||||
entry->idle_banned_until = now + duration;
|
||||
entry->idle_ban_count++;
|
||||
entry->idle_failure_count = 0;
|
||||
entry->idle_first_failure = 0;
|
||||
|
||||
DEBUG_WARN("IP %s banned for %d seconds (idle ban #%d) after %d idle failures",
|
||||
ip, duration, entry->idle_ban_count, threshold);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_ban_mutex);
|
||||
}
|
||||
|
||||
void ip_ban_record_success(const char* ip) {
|
||||
if (!ip || !g_initialized) return;
|
||||
|
||||
pthread_mutex_lock(&g_ban_mutex);
|
||||
ip_ban_entry_t* entry = get_or_create_entry(ip);
|
||||
if (entry) {
|
||||
entry->failure_count = 0;
|
||||
entry->first_failure = 0;
|
||||
entry->has_authed_successfully = 1;
|
||||
entry->last_success_at = time(NULL);
|
||||
entry->total_successes++;
|
||||
DEBUG_TRACE("IP %s authenticated successfully — failure count cleared", ip);
|
||||
}
|
||||
pthread_mutex_unlock(&g_ban_mutex);
|
||||
}
|
||||
|
||||
void ip_ban_cleanup(void) {
|
||||
if (!g_initialized) return;
|
||||
|
||||
pthread_mutex_lock(&g_ban_mutex);
|
||||
time_t now = time(NULL);
|
||||
int window_sec = get_config_int("auth_fail_window_sec", 60);
|
||||
int idle_window_sec = get_config_int("idle_ban_window_sec", 60);
|
||||
int cleaned = 0;
|
||||
|
||||
for (int i = 0; i < IP_BAN_TABLE_SIZE; i++) {
|
||||
if (g_ban_table[i].state != IP_BAN_ACTIVE) continue;
|
||||
ip_ban_entry_t* entry = &g_ban_table[i];
|
||||
|
||||
// Check auth failure window expiration
|
||||
int auth_ban_expired = (entry->banned_until == 0 || now >= entry->banned_until);
|
||||
int auth_window_expired = (entry->first_failure == 0 || (now - entry->first_failure) > window_sec * 10);
|
||||
|
||||
// Check idle failure window expiration
|
||||
int idle_ban_expired = (entry->idle_banned_until == 0 || now >= entry->idle_banned_until);
|
||||
int idle_window_expired = (entry->idle_first_failure == 0 || (now - entry->idle_first_failure) > idle_window_sec * 10);
|
||||
|
||||
if (auth_ban_expired && auth_window_expired && entry->failure_count == 0 &&
|
||||
idle_ban_expired && idle_window_expired && entry->idle_failure_count == 0) {
|
||||
int retain_sec = 86400; // 24 hours
|
||||
int last_auth_ban_expired_long_ago = (entry->banned_until == 0 ||
|
||||
(now - entry->banned_until) > retain_sec);
|
||||
int last_idle_ban_expired_long_ago = (entry->idle_banned_until == 0 ||
|
||||
(now - entry->idle_banned_until) > retain_sec);
|
||||
|
||||
if (last_auth_ban_expired_long_ago && last_idle_ban_expired_long_ago &&
|
||||
!entry->has_authed_successfully &&
|
||||
entry->ban_count == 0 && entry->idle_ban_count == 0 &&
|
||||
entry->total_connections <= 1) {
|
||||
// Fully clean — never banned, never authenticated, only seen once
|
||||
memset(entry, 0, sizeof(ip_ban_entry_t));
|
||||
cleaned++;
|
||||
} else {
|
||||
// Keep entry permanently — preserve ban_count for escalation.
|
||||
// An IP that has been banned before will always get at least a 24-hour ban
|
||||
// if it fails auth again, regardless of how long it has been away.
|
||||
entry->failure_count = 0;
|
||||
entry->first_failure = 0;
|
||||
entry->idle_failure_count = 0;
|
||||
entry->idle_first_failure = 0;
|
||||
if (last_auth_ban_expired_long_ago) {
|
||||
entry->banned_until = 0;
|
||||
// ban_count intentionally NOT reset — permanent escalation
|
||||
}
|
||||
if (last_idle_ban_expired_long_ago) {
|
||||
entry->idle_banned_until = 0;
|
||||
// idle_ban_count intentionally NOT reset — permanent escalation
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cleaned > 0) {
|
||||
DEBUG_TRACE("IP ban cleanup: freed %d stale entries", cleaned);
|
||||
}
|
||||
pthread_mutex_unlock(&g_ban_mutex);
|
||||
}
|
||||
|
||||
int ip_ban_get_banned_count(void) {
|
||||
if (!g_initialized) return 0;
|
||||
pthread_mutex_lock(&g_ban_mutex);
|
||||
time_t now = time(NULL);
|
||||
int count = 0;
|
||||
for (int i = 0; i < IP_BAN_TABLE_SIZE; i++) {
|
||||
if (g_ban_table[i].state != IP_BAN_ACTIVE) continue;
|
||||
// Count if either auth banned or idle banned
|
||||
if (g_ban_table[i].banned_until > now ||
|
||||
g_ban_table[i].idle_banned_until > now) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&g_ban_mutex);
|
||||
return count;
|
||||
}
|
||||
|
||||
int ip_ban_get_tracked_count(void) {
|
||||
if (!g_initialized) return 0;
|
||||
pthread_mutex_lock(&g_ban_mutex);
|
||||
int count = 0;
|
||||
for (int i = 0; i < IP_BAN_TABLE_SIZE; i++) {
|
||||
if (g_ban_table[i].state == IP_BAN_ACTIVE) count++;
|
||||
}
|
||||
pthread_mutex_unlock(&g_ban_mutex);
|
||||
return count;
|
||||
}
|
||||
|
||||
void ip_ban_log_stats(void) {
|
||||
if (!g_initialized) return;
|
||||
|
||||
static time_t last_log = 0;
|
||||
time_t now = time(NULL);
|
||||
|
||||
if (now - last_log < 300) return;
|
||||
last_log = now;
|
||||
|
||||
// Save to DB every 5 minutes
|
||||
ip_ban_save_to_db();
|
||||
|
||||
pthread_mutex_lock(&g_ban_mutex);
|
||||
int auth_banned_count = 0;
|
||||
int idle_banned_count = 0;
|
||||
int tracked_count = 0;
|
||||
int trusted_count = 0;
|
||||
|
||||
for (int i = 0; i < IP_BAN_TABLE_SIZE; i++) {
|
||||
if (g_ban_table[i].state != IP_BAN_ACTIVE) continue;
|
||||
tracked_count++;
|
||||
if (g_ban_table[i].banned_until > now) auth_banned_count++;
|
||||
if (g_ban_table[i].idle_banned_until > now) idle_banned_count++;
|
||||
if (g_ban_table[i].has_authed_successfully) trusted_count++;
|
||||
}
|
||||
pthread_mutex_unlock(&g_ban_mutex);
|
||||
|
||||
int total_banned = auth_banned_count + idle_banned_count;
|
||||
if (total_banned > 0 || tracked_count > 0) {
|
||||
DEBUG_WARN("IP BAN SUMMARY: %d auth-banned, %d idle-banned, %d tracked, %d trusted (ever authed)",
|
||||
auth_banned_count, idle_banned_count, tracked_count, trusted_count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#ifndef IP_BAN_H
|
||||
#define IP_BAN_H
|
||||
|
||||
// IP Auth Failure Ban System
|
||||
//
|
||||
// Tracks auth failures per IP address and temporarily bans IPs that repeatedly
|
||||
// fail NIP-42 authentication. Uses an in-memory fixed-size hash table — no
|
||||
// database writes on the hot path. State is persisted to the ip_bans table
|
||||
// every 5 minutes and loaded at startup.
|
||||
//
|
||||
// Config keys (all read from the config table at runtime):
|
||||
// auth_fail_ban_enabled bool default: true (0 = disabled)
|
||||
// auth_fail_ban_threshold int default: 3 (failures before ban)
|
||||
// auth_fail_window_sec int default: 60 (window to count failures)
|
||||
// auth_fail_ban_duration_sec int default: 300 (initial ban duration, doubles each time)
|
||||
|
||||
#include <time.h>
|
||||
|
||||
// Maximum number of IPs tracked simultaneously (fixed-size, no malloc)
|
||||
#define IP_BAN_TABLE_SIZE 4096
|
||||
|
||||
// Initialize the IP ban table (call once at startup, before loading from DB)
|
||||
void ip_ban_init(void);
|
||||
|
||||
// Load ban state from the ip_bans database table.
|
||||
// Call after ip_ban_init() and after the database is open.
|
||||
void ip_ban_load_from_db(void);
|
||||
|
||||
// Save current ban state to the ip_bans database table.
|
||||
// Called every 5 minutes from the maintenance timer.
|
||||
void ip_ban_save_to_db(void);
|
||||
|
||||
// Check if an IP is currently banned.
|
||||
// Returns 1 if banned (connection should be rejected), 0 if allowed.
|
||||
int ip_ban_is_banned(const char* ip);
|
||||
|
||||
// Record an auth failure for an IP.
|
||||
// Called when a connection is closed due to auth timeout.
|
||||
// May trigger a ban if the threshold is exceeded.
|
||||
void ip_ban_record_failure(const char* ip);
|
||||
|
||||
// Record an idle/early-disconnect failure for an IP.
|
||||
// Called when a connection closes without ever sending REQ or EVENT.
|
||||
// May trigger a ban if the threshold is exceeded.
|
||||
void ip_ban_record_idle_failure(const char* ip);
|
||||
|
||||
// Record a successful auth for an IP.
|
||||
// Sets has_authed_successfully=1 and clears failure count.
|
||||
void ip_ban_record_success(const char* ip);
|
||||
|
||||
// Record a new connection from an IP (increment total_connections).
|
||||
void ip_ban_record_connection(const char* ip);
|
||||
|
||||
// Periodic cleanup: expire old entries (call from the connection age checker).
|
||||
void ip_ban_cleanup(void);
|
||||
|
||||
// Emit a periodic WARN-level log summary of banned IPs (every 5 minutes).
|
||||
// Also saves state to DB.
|
||||
void ip_ban_log_stats(void);
|
||||
|
||||
// Get stats for logging/monitoring
|
||||
int ip_ban_get_banned_count(void);
|
||||
int ip_ban_get_tracked_count(void);
|
||||
|
||||
#endif // IP_BAN_H
|
||||
+670
-462
File diff suppressed because it is too large
Load Diff
+22
-4
@@ -11,10 +11,10 @@
|
||||
|
||||
// Version information (auto-updated by build system)
|
||||
// Using CRELAY_ prefix to avoid conflicts with nostr_core_lib VERSION macros
|
||||
#define CRELAY_VERSION_MAJOR 1
|
||||
#define CRELAY_VERSION_MINOR 2
|
||||
#define CRELAY_VERSION_PATCH 5
|
||||
#define CRELAY_VERSION "v1.2.5"
|
||||
#define CRELAY_VERSION_MAJOR 2
|
||||
#define CRELAY_VERSION_MINOR 1
|
||||
#define CRELAY_VERSION_PATCH 4
|
||||
#define CRELAY_VERSION "v2.1.4"
|
||||
|
||||
// Relay metadata (authoritative source for NIP-11 information)
|
||||
#define RELAY_NAME "C-Relay"
|
||||
@@ -28,4 +28,22 @@
|
||||
#define POSTING_POLICY ""
|
||||
#define PAYMENTS_URL ""
|
||||
|
||||
// Forward declaration to avoid pulling cJSON headers into all includers.
|
||||
typedef struct cJSON cJSON;
|
||||
|
||||
// Async REQ handler status used by websocket callback to defer EOSE until worker completion
|
||||
#define HANDLE_REQ_ASYNC_PENDING (-2)
|
||||
|
||||
// Async EVENT storage split:
|
||||
// - store_event_core(): worker-safe core DB write path
|
||||
// returns 0 when inserted, 1 when handled without insert (duplicate/ephemeral), -1 on error
|
||||
// - store_event_post_actions(): main-thread-only follow-up actions (monitoring/tags/WoT sync)
|
||||
// - store_event(): legacy wrapper preserving original behavior for synchronous call sites
|
||||
int store_event_core(cJSON* event);
|
||||
void store_event_post_actions(cJSON* event);
|
||||
int store_event(cJSON* event);
|
||||
|
||||
// Drains completed async REQ jobs and queues EVENT/EOSE on the lws service thread.
|
||||
void process_req_async_completions(void);
|
||||
|
||||
#endif /* MAIN_H */
|
||||
|
||||
+39
-95
@@ -7,7 +7,7 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
#include <cjson/cJSON.h>
|
||||
#include "debug.h"
|
||||
#include <sqlite3.h>
|
||||
#include "db_ops.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
@@ -21,10 +21,6 @@ int store_event(cJSON* event);
|
||||
int delete_events_by_id(const char* requester_pubkey, cJSON* event_ids);
|
||||
int delete_events_by_address(const char* requester_pubkey, cJSON* addresses, long deletion_timestamp);
|
||||
|
||||
// Global database variable
|
||||
extern sqlite3* g_db;
|
||||
|
||||
|
||||
// Handle NIP-09 deletion request event (kind 5)
|
||||
int handle_deletion_request(cJSON* event, char* error_message, size_t error_size) {
|
||||
if (!event) {
|
||||
@@ -33,12 +29,12 @@ int handle_deletion_request(cJSON* event, char* error_message, size_t error_size
|
||||
}
|
||||
|
||||
// Extract event details
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
cJSON* created_at_obj = cJSON_GetObjectItem(event, "created_at");
|
||||
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
||||
cJSON* content_obj = cJSON_GetObjectItem(event, "content");
|
||||
cJSON* event_id_obj = cJSON_GetObjectItem(event, "id");
|
||||
cJSON* kind_obj = cJSON_GetObjectItemCaseSensitive(event, "kind");
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItemCaseSensitive(event, "pubkey");
|
||||
cJSON* created_at_obj = cJSON_GetObjectItemCaseSensitive(event, "created_at");
|
||||
cJSON* tags_obj = cJSON_GetObjectItemCaseSensitive(event, "tags");
|
||||
cJSON* content_obj = cJSON_GetObjectItemCaseSensitive(event, "content");
|
||||
cJSON* event_id_obj = cJSON_GetObjectItemCaseSensitive(event, "id");
|
||||
|
||||
if (!kind_obj || !pubkey_obj || !created_at_obj || !tags_obj || !event_id_obj) {
|
||||
snprintf(error_message, error_size, "invalid: incomplete deletion request");
|
||||
@@ -146,97 +142,72 @@ int handle_deletion_request(cJSON* event, char* error_message, size_t error_size
|
||||
|
||||
// Delete events by ID (with pubkey authorization)
|
||||
int delete_events_by_id(const char* requester_pubkey, cJSON* event_ids) {
|
||||
if (!g_db || !requester_pubkey || !event_ids || !cJSON_IsArray(event_ids)) {
|
||||
if (!db_is_available() || !requester_pubkey || !event_ids || !cJSON_IsArray(event_ids)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int deleted_count = 0;
|
||||
|
||||
|
||||
cJSON* event_id = NULL;
|
||||
cJSON_ArrayForEach(event_id, event_ids) {
|
||||
if (!cJSON_IsString(event_id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
const char* id = cJSON_GetStringValue(event_id);
|
||||
|
||||
// First check if event exists and if requester is authorized
|
||||
const char* check_sql = "SELECT pubkey FROM events WHERE id = ?";
|
||||
sqlite3_stmt* check_stmt;
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_db, check_sql, -1, &check_stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
char event_pubkey[65] = {0};
|
||||
int exists = db_get_event_pubkey(id, event_pubkey, sizeof(event_pubkey));
|
||||
if (exists <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(check_stmt, 1, id, -1, SQLITE_STATIC);
|
||||
|
||||
if (sqlite3_step(check_stmt) == SQLITE_ROW) {
|
||||
const char* event_pubkey = (char*)sqlite3_column_text(check_stmt, 0);
|
||||
|
||||
// Only delete if the requester is the author
|
||||
if (event_pubkey && strcmp(event_pubkey, requester_pubkey) == 0) {
|
||||
sqlite3_finalize(check_stmt);
|
||||
|
||||
// Delete the event
|
||||
const char* delete_sql = "DELETE FROM events WHERE id = ? AND pubkey = ?";
|
||||
sqlite3_stmt* delete_stmt;
|
||||
|
||||
rc = sqlite3_prepare_v2(g_db, delete_sql, -1, &delete_stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_text(delete_stmt, 1, id, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(delete_stmt, 2, requester_pubkey, -1, SQLITE_STATIC);
|
||||
|
||||
if (sqlite3_step(delete_stmt) == SQLITE_DONE && sqlite3_changes(g_db) > 0) {
|
||||
deleted_count++;
|
||||
}
|
||||
sqlite3_finalize(delete_stmt);
|
||||
}
|
||||
} else {
|
||||
sqlite3_finalize(check_stmt);
|
||||
char warning_msg[128];
|
||||
snprintf(warning_msg, sizeof(warning_msg), "Unauthorized deletion attempt for event: %.16s...", id);
|
||||
DEBUG_WARN(warning_msg);
|
||||
|
||||
// Only delete if the requester is the author
|
||||
if (strcmp(event_pubkey, requester_pubkey) == 0) {
|
||||
int changes = db_delete_event_by_id(id, requester_pubkey);
|
||||
if (changes > 0) {
|
||||
deleted_count += changes;
|
||||
}
|
||||
} else {
|
||||
sqlite3_finalize(check_stmt);
|
||||
char warning_msg[128];
|
||||
snprintf(warning_msg, sizeof(warning_msg), "Unauthorized deletion attempt for event: %.16s...", id);
|
||||
DEBUG_WARN(warning_msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return deleted_count;
|
||||
}
|
||||
|
||||
// Delete events by addressable reference (kind:pubkey:d-identifier)
|
||||
int delete_events_by_address(const char* requester_pubkey, cJSON* addresses, long deletion_timestamp) {
|
||||
if (!g_db || !requester_pubkey || !addresses || !cJSON_IsArray(addresses)) {
|
||||
if (!db_is_available() || !requester_pubkey || !addresses || !cJSON_IsArray(addresses)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int deleted_count = 0;
|
||||
|
||||
|
||||
cJSON* address = NULL;
|
||||
cJSON_ArrayForEach(address, addresses) {
|
||||
if (!cJSON_IsString(address)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
const char* addr = cJSON_GetStringValue(address);
|
||||
|
||||
|
||||
// Parse address format: kind:pubkey:d-identifier
|
||||
char* addr_copy = strdup(addr);
|
||||
if (!addr_copy) continue;
|
||||
|
||||
|
||||
char* kind_str = strtok(addr_copy, ":");
|
||||
char* pubkey_str = strtok(NULL, ":");
|
||||
char* d_identifier = strtok(NULL, ":");
|
||||
|
||||
|
||||
if (!kind_str || !pubkey_str) {
|
||||
free(addr_copy);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
int kind = atoi(kind_str);
|
||||
|
||||
|
||||
// Only delete if the requester is the author
|
||||
if (strcmp(pubkey_str, requester_pubkey) != 0) {
|
||||
free(addr_copy);
|
||||
@@ -245,42 +216,15 @@ int delete_events_by_address(const char* requester_pubkey, cJSON* addresses, lon
|
||||
DEBUG_WARN(warning_msg);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Build deletion query based on whether we have d-identifier
|
||||
const char* delete_sql;
|
||||
sqlite3_stmt* delete_stmt;
|
||||
|
||||
if (d_identifier && strlen(d_identifier) > 0) {
|
||||
// Delete specific addressable event with d-tag
|
||||
delete_sql = "DELETE FROM events WHERE kind = ? AND pubkey = ? AND created_at <= ? "
|
||||
"AND json_extract(tags, '$[*]') LIKE '%[\"d\",\"' || ? || '\"]%'";
|
||||
} else {
|
||||
// Delete all events of this kind by this author up to deletion timestamp
|
||||
delete_sql = "DELETE FROM events WHERE kind = ? AND pubkey = ? AND created_at <= ?";
|
||||
|
||||
int changes = db_delete_events_by_address(requester_pubkey, kind, d_identifier, deletion_timestamp);
|
||||
if (changes > 0) {
|
||||
deleted_count += changes;
|
||||
}
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_db, delete_sql, -1, &delete_stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_int(delete_stmt, 1, kind);
|
||||
sqlite3_bind_text(delete_stmt, 2, requester_pubkey, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_int64(delete_stmt, 3, deletion_timestamp);
|
||||
|
||||
if (d_identifier && strlen(d_identifier) > 0) {
|
||||
sqlite3_bind_text(delete_stmt, 4, d_identifier, -1, SQLITE_STATIC);
|
||||
}
|
||||
|
||||
if (sqlite3_step(delete_stmt) == SQLITE_DONE) {
|
||||
int changes = sqlite3_changes(g_db);
|
||||
if (changes > 0) {
|
||||
deleted_count += changes;
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(delete_stmt);
|
||||
}
|
||||
|
||||
|
||||
free(addr_copy);
|
||||
}
|
||||
|
||||
|
||||
return deleted_count;
|
||||
}
|
||||
|
||||
|
||||
+23
-2
@@ -118,6 +118,8 @@ cJSON* generate_relay_info_json() {
|
||||
int default_limit = get_config_int("default_limit", 500);
|
||||
int min_pow_difficulty = get_config_int("pow_min_difficulty", 0);
|
||||
int admin_enabled = get_config_bool("admin_enabled", 0);
|
||||
int wot_enabled = get_config_int("wot_enabled", 0);
|
||||
int auth_enabled = get_config_bool("auth_enabled", 0);
|
||||
|
||||
// Add basic relay information
|
||||
if (relay_name && strlen(relay_name) > 0) {
|
||||
@@ -209,6 +211,9 @@ cJSON* generate_relay_info_json() {
|
||||
}
|
||||
|
||||
// Add server limitations
|
||||
// restricted_writes is true when WoT or auth is enabled (only trusted pubkeys can write)
|
||||
int restricted_writes = (wot_enabled > 0 || auth_enabled) ? 1 : 0;
|
||||
|
||||
cJSON* limitation = cJSON_CreateObject();
|
||||
if (limitation) {
|
||||
cJSON_AddNumberToObject(limitation, "max_message_length", max_message_length);
|
||||
@@ -218,15 +223,31 @@ cJSON* generate_relay_info_json() {
|
||||
cJSON_AddNumberToObject(limitation, "max_event_tags", max_event_tags);
|
||||
cJSON_AddNumberToObject(limitation, "max_content_length", max_content_length);
|
||||
cJSON_AddNumberToObject(limitation, "min_pow_difficulty", min_pow_difficulty);
|
||||
cJSON_AddBoolToObject(limitation, "auth_required", admin_enabled ? cJSON_True : cJSON_False);
|
||||
cJSON_AddBoolToObject(limitation, "auth_required", auth_enabled ? cJSON_True : cJSON_False);
|
||||
cJSON_AddBoolToObject(limitation, "payment_required", cJSON_False);
|
||||
cJSON_AddBoolToObject(limitation, "restricted_writes", cJSON_False);
|
||||
cJSON_AddBoolToObject(limitation, "restricted_writes", restricted_writes ? cJSON_True : cJSON_False);
|
||||
cJSON_AddNumberToObject(limitation, "created_at_lower_limit", 0);
|
||||
cJSON_AddNumberToObject(limitation, "created_at_upper_limit", 2147483647);
|
||||
cJSON_AddNumberToObject(limitation, "default_limit", default_limit);
|
||||
cJSON_AddItemToObject(info, "limitation", limitation);
|
||||
}
|
||||
|
||||
// Add Web of Trust information when enabled
|
||||
// This informs clients that the relay operates on a trust network
|
||||
if (wot_enabled > 0) {
|
||||
cJSON* wot_info = cJSON_CreateObject();
|
||||
if (wot_info) {
|
||||
cJSON_AddNumberToObject(wot_info, "level", wot_enabled);
|
||||
const char* level_desc = (wot_enabled == 1)
|
||||
? "write-only: only followed pubkeys can publish events"
|
||||
: "full: only followed pubkeys can publish and subscribe";
|
||||
cJSON_AddStringToObject(wot_info, "description", level_desc);
|
||||
cJSON_AddStringToObject(wot_info, "policy",
|
||||
"Events from pubkeys not in the relay operator's Web of Trust are rejected.");
|
||||
cJSON_AddItemToObject(info, "web_of_trust", wot_info);
|
||||
}
|
||||
}
|
||||
|
||||
// Add retention policies (empty array for now)
|
||||
cJSON* retention = cJSON_CreateArray();
|
||||
if (retention) {
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
|
||||
// If min_pow_difficulty is 0, only validate events that have nonce tags
|
||||
// This allows events without PoW when difficulty requirement is 0
|
||||
if (min_pow_difficulty == 0) {
|
||||
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
||||
cJSON* tags = cJSON_GetObjectItemCaseSensitive(event, "tags");
|
||||
int has_nonce_tag = 0;
|
||||
|
||||
if (tags && cJSON_IsArray(tags)) {
|
||||
|
||||
+2
-2
@@ -114,7 +114,7 @@ int is_event_expired(cJSON* event, time_t current_time) {
|
||||
return 0; // Invalid event, not expired
|
||||
}
|
||||
|
||||
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
||||
cJSON* tags = cJSON_GetObjectItemCaseSensitive(event, "tags");
|
||||
long expiration_ts = extract_expiration_timestamp(tags);
|
||||
|
||||
if (expiration_ts == 0) {
|
||||
@@ -140,7 +140,7 @@ int validate_event_expiration(cJSON* event, char* error_message, size_t error_si
|
||||
time_t current_time = time(NULL);
|
||||
if (is_event_expired(event, current_time)) {
|
||||
if (g_expiration_config.strict_mode) {
|
||||
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
||||
cJSON* tags = cJSON_GetObjectItemCaseSensitive(event, "tags");
|
||||
long expiration_ts = extract_expiration_timestamp(tags);
|
||||
|
||||
snprintf(error_message, error_size,
|
||||
|
||||
+22
-3
@@ -12,7 +12,9 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
#include "websockets.h"
|
||||
#include "ip_ban.h"
|
||||
|
||||
|
||||
// Forward declaration for notice message function
|
||||
@@ -93,13 +95,22 @@ void handle_nip42_auth_signed_event(struct lws* wsi, struct per_session_data* ps
|
||||
|
||||
// Verify authentication using existing request_validator function
|
||||
// Note: nostr_nip42_verify_auth_event doesn't extract pubkey, we need to do that separately
|
||||
char relay_url[RELAY_URL_MAX_LENGTH];
|
||||
const char* configured_relay_url = get_config_value("relay_url");
|
||||
if (configured_relay_url && configured_relay_url[0] != '\0') {
|
||||
snprintf(relay_url, sizeof(relay_url), "%s", configured_relay_url);
|
||||
} else {
|
||||
int relay_port = (g_relay_port > 0) ? g_relay_port : get_config_int("relay_port", DEFAULT_PORT);
|
||||
snprintf(relay_url, sizeof(relay_url), "ws://127.0.0.1:%d", relay_port);
|
||||
}
|
||||
|
||||
int result = nostr_nip42_verify_auth_event(auth_event, challenge_copy,
|
||||
"ws://localhost:8888", 600); // 10 minutes tolerance
|
||||
relay_url, 600); // 10 minutes tolerance
|
||||
|
||||
char authenticated_pubkey[65] = {0};
|
||||
if (result == 0) {
|
||||
// Extract pubkey from the auth event
|
||||
cJSON* pubkey_json = cJSON_GetObjectItem(auth_event, "pubkey");
|
||||
cJSON* pubkey_json = cJSON_GetObjectItemCaseSensitive(auth_event, "pubkey");
|
||||
if (pubkey_json && cJSON_IsString(pubkey_json)) {
|
||||
const char* pubkey_str = cJSON_GetStringValue(pubkey_json);
|
||||
if (pubkey_str && strlen(pubkey_str) == 64) {
|
||||
@@ -125,8 +136,16 @@ void handle_nip42_auth_signed_event(struct lws* wsi, struct per_session_data* ps
|
||||
memset(pss->active_challenge, 0, sizeof(pss->active_challenge));
|
||||
pss->challenge_expires = 0;
|
||||
pss->auth_challenge_sent = 0;
|
||||
// Mark session as active - client sent AUTH
|
||||
pss->session_active = 1;
|
||||
pthread_mutex_unlock(&pss->session_lock);
|
||||
|
||||
|
||||
// Cancel the auth timeout — client has authenticated, keep connection open
|
||||
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
|
||||
|
||||
// Record successful auth for this IP — marks it as trusted
|
||||
ip_ban_record_success(pss->client_ip);
|
||||
|
||||
send_notice_message(wsi, pss, "NIP-42 authentication successful");
|
||||
} else {
|
||||
// Authentication failed
|
||||
|
||||
+51
-106
@@ -17,17 +17,13 @@
|
||||
#include "../nostr_core_lib/nostr_core/utils.h"
|
||||
#include "debug.h" // C-relay debug system
|
||||
#include "config.h" // C-relay configuration system
|
||||
#include <sqlite3.h>
|
||||
#include "db_ops.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <time.h>
|
||||
|
||||
// External references to C-relay global state
|
||||
extern sqlite3* g_db;
|
||||
extern char g_database_path[512];
|
||||
|
||||
// Forward declaration for C-relay event storage function
|
||||
extern int store_event(cJSON* event);
|
||||
|
||||
@@ -133,6 +129,14 @@ typedef struct {
|
||||
static nip42_challenge_manager_t g_challenge_manager = {0};
|
||||
static int g_validator_initialized = 0;
|
||||
|
||||
typedef struct {
|
||||
int has_active_whitelist_rules;
|
||||
time_t last_refresh;
|
||||
} auth_rules_fast_cache_t;
|
||||
|
||||
static auth_rules_fast_cache_t g_auth_rules_fast_cache = {0};
|
||||
#define AUTH_RULES_CACHE_TTL_SEC 5
|
||||
|
||||
// Last rule violation details for status code mapping
|
||||
struct {
|
||||
char violation_type[100]; // "pubkey_blacklist", "hash_blacklist",
|
||||
@@ -252,13 +256,13 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
|
||||
}
|
||||
|
||||
// 4. Validate basic event structure
|
||||
cJSON *id = cJSON_GetObjectItem(event, "id");
|
||||
cJSON *pubkey = cJSON_GetObjectItem(event, "pubkey");
|
||||
cJSON *created_at = cJSON_GetObjectItem(event, "created_at");
|
||||
cJSON *kind = cJSON_GetObjectItem(event, "kind");
|
||||
cJSON *tags = cJSON_GetObjectItem(event, "tags");
|
||||
cJSON *content = cJSON_GetObjectItem(event, "content");
|
||||
cJSON *sig = cJSON_GetObjectItem(event, "sig");
|
||||
cJSON *id = cJSON_GetObjectItemCaseSensitive(event, "id");
|
||||
cJSON *pubkey = cJSON_GetObjectItemCaseSensitive(event, "pubkey");
|
||||
cJSON *created_at = cJSON_GetObjectItemCaseSensitive(event, "created_at");
|
||||
cJSON *kind = cJSON_GetObjectItemCaseSensitive(event, "kind");
|
||||
cJSON *tags = cJSON_GetObjectItemCaseSensitive(event, "tags");
|
||||
cJSON *content = cJSON_GetObjectItemCaseSensitive(event, "content");
|
||||
cJSON *sig = cJSON_GetObjectItemCaseSensitive(event, "sig");
|
||||
|
||||
if (!id || !cJSON_IsString(id) ||
|
||||
!pubkey || !cJSON_IsString(pubkey) ||
|
||||
@@ -304,9 +308,11 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
|
||||
const char* admin_pubkey = get_config_value("admin_pubkey");
|
||||
if (admin_pubkey && strcmp(event_pubkey, admin_pubkey) == 0) {
|
||||
// Valid admin event - bypass remaining validation
|
||||
free((char*)admin_pubkey);
|
||||
cJSON_Delete(event);
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
if (admin_pubkey) free((char*)admin_pubkey);
|
||||
// Not from admin - continue with normal validation
|
||||
}
|
||||
|
||||
@@ -380,7 +386,7 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
|
||||
// Always check expiration tags if present (following NIP-40 specification)
|
||||
|
||||
cJSON *expiration_tag = NULL;
|
||||
cJSON *tags_array = cJSON_GetObjectItem(event, "tags");
|
||||
cJSON *tags_array = cJSON_GetObjectItemCaseSensitive(event, "tags");
|
||||
|
||||
if (tags_array && cJSON_IsArray(tags_array)) {
|
||||
cJSON *tag = NULL;
|
||||
@@ -504,10 +510,10 @@ void nostr_request_result_free_file_data(nostr_request_result_t *result) {
|
||||
|
||||
|
||||
/**
|
||||
* Force cache refresh - cache no longer exists, function kept for compatibility
|
||||
* Force cache refresh for auth rule fast cache.
|
||||
*/
|
||||
void nostr_request_validator_force_cache_refresh(void) {
|
||||
// Cache no longer exists - direct database queries are used
|
||||
g_auth_rules_fast_cache.last_refresh = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -522,118 +528,57 @@ static int reload_auth_config(void) {
|
||||
|
||||
// Note: Blossom protocol validation removed - C-relay uses standard Nostr events only
|
||||
|
||||
static int has_active_whitelist_rules_cached(void) {
|
||||
time_t now = time(NULL);
|
||||
if (g_auth_rules_fast_cache.last_refresh == 0 ||
|
||||
(now - g_auth_rules_fast_cache.last_refresh) >= AUTH_RULES_CACHE_TTL_SEC) {
|
||||
g_auth_rules_fast_cache.has_active_whitelist_rules =
|
||||
(db_count_active_whitelist_rules() > 0) ? 1 : 0;
|
||||
g_auth_rules_fast_cache.last_refresh = now;
|
||||
}
|
||||
return g_auth_rules_fast_cache.has_active_whitelist_rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check database authentication rules for the request
|
||||
* Implements the 6-step rule evaluation engine from AUTH_API.md
|
||||
*/
|
||||
int check_database_auth_rules(const char *pubkey, const char *operation __attribute__((unused)),
|
||||
const char *resource_hash) {
|
||||
sqlite3 *db = NULL;
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
int rc;
|
||||
|
||||
DEBUG_TRACE("Checking auth rules for pubkey: %s", pubkey);
|
||||
|
||||
if (!pubkey) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
// Open database using global database path
|
||||
if (strlen(g_database_path) == 0) {
|
||||
return NOSTR_SUCCESS; // Default allow on DB error
|
||||
}
|
||||
|
||||
rc = sqlite3_open_v2(g_database_path, &db, SQLITE_OPEN_READONLY, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
return NOSTR_SUCCESS; // Default allow on DB error
|
||||
}
|
||||
|
||||
// Step 1: Check pubkey blacklist (highest priority)
|
||||
const char *blacklist_sql =
|
||||
"SELECT rule_type FROM auth_rules WHERE rule_type = "
|
||||
"'blacklist' AND pattern_type = 'pubkey' AND pattern_value = ? AND active = 1 LIMIT 1";
|
||||
DEBUG_TRACE("Blacklist SQL: %s", blacklist_sql);
|
||||
rc = sqlite3_prepare_v2(db, blacklist_sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC);
|
||||
|
||||
int step_result = sqlite3_step(stmt);
|
||||
DEBUG_TRACE("Blacklist query result: %s", step_result == SQLITE_ROW ? "FOUND" : "NOT_FOUND");
|
||||
|
||||
if (step_result == SQLITE_ROW) {
|
||||
DEBUG_TRACE("BLACKLIST HIT: Denying access for pubkey: %s", pubkey);
|
||||
// Set specific violation details for status code mapping
|
||||
strcpy(g_last_rule_violation.violation_type, "pubkey_blacklist");
|
||||
sprintf(g_last_rule_violation.reason, "Public key blacklisted");
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_close(db);
|
||||
return NOSTR_ERROR_AUTH_REQUIRED;
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
if (db_is_pubkey_blacklisted(pubkey)) {
|
||||
DEBUG_TRACE("BLACKLIST HIT: Denying access for pubkey: %s", pubkey);
|
||||
strcpy(g_last_rule_violation.violation_type, "pubkey_blacklist");
|
||||
sprintf(g_last_rule_violation.reason, "Public key blacklisted");
|
||||
return NOSTR_ERROR_AUTH_REQUIRED;
|
||||
}
|
||||
|
||||
// Step 2: Check hash blacklist
|
||||
if (resource_hash) {
|
||||
const char *hash_blacklist_sql =
|
||||
"SELECT rule_type FROM auth_rules WHERE rule_type = "
|
||||
"'blacklist' AND pattern_type = 'hash' AND pattern_value = ? AND active = 1 LIMIT 1";
|
||||
rc = sqlite3_prepare_v2(db, hash_blacklist_sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, resource_hash, -1, SQLITE_STATIC);
|
||||
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
// Set specific violation details for status code mapping
|
||||
strcpy(g_last_rule_violation.violation_type, "hash_blacklist");
|
||||
sprintf(g_last_rule_violation.reason, "File hash blacklisted");
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_close(db);
|
||||
return NOSTR_ERROR_AUTH_REQUIRED;
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
if (resource_hash && db_is_hash_blacklisted(resource_hash)) {
|
||||
strcpy(g_last_rule_violation.violation_type, "hash_blacklist");
|
||||
sprintf(g_last_rule_violation.reason, "File hash blacklisted");
|
||||
return NOSTR_ERROR_AUTH_REQUIRED;
|
||||
}
|
||||
|
||||
// Step 3: Check pubkey whitelist (includes wot_whitelist)
|
||||
const char *whitelist_sql =
|
||||
"SELECT rule_type FROM auth_rules WHERE rule_type IN "
|
||||
"('whitelist', 'wot_whitelist') AND pattern_type = 'pubkey' AND pattern_value = ? AND active = 1 LIMIT 1";
|
||||
rc = sqlite3_prepare_v2(db, whitelist_sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC);
|
||||
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_close(db);
|
||||
return NOSTR_SUCCESS; // Allow whitelisted pubkey
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
if (db_is_pubkey_whitelisted(pubkey)) {
|
||||
return NOSTR_SUCCESS; // Allow whitelisted pubkey
|
||||
}
|
||||
|
||||
// Step 4: Check if any whitelist rules exist - if yes, deny by default (includes wot_whitelist)
|
||||
const char *whitelist_exists_sql =
|
||||
"SELECT COUNT(*) FROM auth_rules WHERE rule_type IN ('whitelist', 'wot_whitelist') "
|
||||
"AND pattern_type = 'pubkey' AND active = 1 LIMIT 1";
|
||||
rc = sqlite3_prepare_v2(db, whitelist_exists_sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int whitelist_count = sqlite3_column_int(stmt, 0);
|
||||
if (whitelist_count > 0) {
|
||||
// Set specific violation details for status code mapping
|
||||
strcpy(g_last_rule_violation.violation_type, "whitelist_violation");
|
||||
strcpy(g_last_rule_violation.reason,
|
||||
"Public key not whitelisted for this operation");
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_close(db);
|
||||
return NOSTR_ERROR_AUTH_REQUIRED;
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
// Step 4: If any whitelist rules exist, deny by default
|
||||
if (has_active_whitelist_rules_cached()) {
|
||||
strcpy(g_last_rule_violation.violation_type, "whitelist_violation");
|
||||
strcpy(g_last_rule_violation.reason,
|
||||
"Public key not whitelisted for this operation");
|
||||
return NOSTR_ERROR_AUTH_REQUIRED;
|
||||
}
|
||||
|
||||
sqlite3_close(db);
|
||||
return NOSTR_SUCCESS; // Default allow if no restrictive rules matched
|
||||
}
|
||||
|
||||
@@ -818,7 +763,7 @@ int nostr_nip42_verify_auth_event(cJSON *event, const char *challenge_id,
|
||||
}
|
||||
|
||||
// Check if event has the required tags for NIP-42
|
||||
cJSON *tags = cJSON_GetObjectItem(event, "tags");
|
||||
cJSON *tags = cJSON_GetObjectItemCaseSensitive(event, "tags");
|
||||
if (!tags || !cJSON_IsArray(tags)) {
|
||||
return NOSTR_ERROR_NIP42_AUTH_EVENT_INVALID;
|
||||
}
|
||||
@@ -896,7 +841,7 @@ int nostr_nip42_verify_auth_event(cJSON *event, const char *challenge_id,
|
||||
}
|
||||
|
||||
// Check created_at timestamp for reasonable bounds
|
||||
cJSON *created_at_json = cJSON_GetObjectItem(event, "created_at");
|
||||
cJSON *created_at_json = cJSON_GetObjectItemCaseSensitive(event, "created_at");
|
||||
if (created_at_json && cJSON_IsNumber(created_at_json)) {
|
||||
time_t created_at = (time_t)cJSON_GetNumberValue(created_at_json);
|
||||
if (abs((int)(now - created_at)) > time_tolerance_seconds) {
|
||||
|
||||
+87
-184
@@ -1,7 +1,7 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <cjson/cJSON.h>
|
||||
#include "debug.h"
|
||||
#include <sqlite3.h>
|
||||
#include "db_ops.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
@@ -28,9 +28,6 @@ int validate_search_term(const char* search_term, char* error_message, size_t er
|
||||
// Forward declaration for monitoring function
|
||||
void monitoring_on_subscription_change(void);
|
||||
|
||||
// Global database variable
|
||||
extern sqlite3* g_db;
|
||||
|
||||
// Configuration functions from config.c
|
||||
extern int get_config_bool(const char* key, int default_value);
|
||||
|
||||
@@ -192,32 +189,32 @@ subscription_filter_t* create_subscription_filter(cJSON* filter_json) {
|
||||
}
|
||||
|
||||
// Copy filter criteria
|
||||
cJSON* kinds = cJSON_GetObjectItem(filter_json, "kinds");
|
||||
cJSON* kinds = cJSON_GetObjectItemCaseSensitive(filter_json, "kinds");
|
||||
if (kinds && cJSON_IsArray(kinds)) {
|
||||
filter->kinds = cJSON_Duplicate(kinds, 1);
|
||||
}
|
||||
|
||||
cJSON* authors = cJSON_GetObjectItem(filter_json, "authors");
|
||||
cJSON* authors = cJSON_GetObjectItemCaseSensitive(filter_json, "authors");
|
||||
if (authors && cJSON_IsArray(authors)) {
|
||||
filter->authors = cJSON_Duplicate(authors, 1);
|
||||
}
|
||||
|
||||
cJSON* ids = cJSON_GetObjectItem(filter_json, "ids");
|
||||
cJSON* ids = cJSON_GetObjectItemCaseSensitive(filter_json, "ids");
|
||||
if (ids && cJSON_IsArray(ids)) {
|
||||
filter->ids = cJSON_Duplicate(ids, 1);
|
||||
}
|
||||
|
||||
cJSON* since = cJSON_GetObjectItem(filter_json, "since");
|
||||
cJSON* since = cJSON_GetObjectItemCaseSensitive(filter_json, "since");
|
||||
if (since && cJSON_IsNumber(since)) {
|
||||
filter->since = (long)cJSON_GetNumberValue(since);
|
||||
}
|
||||
|
||||
cJSON* until = cJSON_GetObjectItem(filter_json, "until");
|
||||
cJSON* until = cJSON_GetObjectItemCaseSensitive(filter_json, "until");
|
||||
if (until && cJSON_IsNumber(until)) {
|
||||
filter->until = (long)cJSON_GetNumberValue(until);
|
||||
}
|
||||
|
||||
cJSON* limit = cJSON_GetObjectItem(filter_json, "limit");
|
||||
cJSON* limit = cJSON_GetObjectItemCaseSensitive(filter_json, "limit");
|
||||
if (limit && cJSON_IsNumber(limit)) {
|
||||
filter->limit = (int)cJSON_GetNumberValue(limit);
|
||||
}
|
||||
@@ -532,9 +529,9 @@ int event_matches_filter(cJSON* event, subscription_filter_t* filter) {
|
||||
}
|
||||
|
||||
// Debug: Log event details being tested
|
||||
cJSON* event_kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
cJSON* event_id_obj = cJSON_GetObjectItem(event, "id");
|
||||
cJSON* event_created_at_obj = cJSON_GetObjectItem(event, "created_at");
|
||||
cJSON* event_kind_obj = cJSON_GetObjectItemCaseSensitive(event, "kind");
|
||||
cJSON* event_id_obj = cJSON_GetObjectItemCaseSensitive(event, "id");
|
||||
cJSON* event_created_at_obj = cJSON_GetObjectItemCaseSensitive(event, "created_at");
|
||||
|
||||
DEBUG_TRACE("FILTER_MATCH: Testing event kind=%d id=%.8s created_at=%ld",
|
||||
event_kind_obj ? (int)cJSON_GetNumberValue(event_kind_obj) : -1,
|
||||
@@ -545,7 +542,7 @@ int event_matches_filter(cJSON* event, subscription_filter_t* filter) {
|
||||
if (filter->kinds && cJSON_IsArray(filter->kinds)) {
|
||||
DEBUG_TRACE("FILTER_MATCH: Checking kinds filter with %d kinds", cJSON_GetArraySize(filter->kinds));
|
||||
|
||||
cJSON* event_kind = cJSON_GetObjectItem(event, "kind");
|
||||
cJSON* event_kind = cJSON_GetObjectItemCaseSensitive(event, "kind");
|
||||
if (!event_kind || !cJSON_IsNumber(event_kind)) {
|
||||
DEBUG_WARN("FILTER_MATCH: Event has no valid kind field");
|
||||
return 0;
|
||||
@@ -577,7 +574,7 @@ int event_matches_filter(cJSON* event, subscription_filter_t* filter) {
|
||||
|
||||
// Check authors filter
|
||||
if (filter->authors && cJSON_IsArray(filter->authors)) {
|
||||
cJSON* event_pubkey = cJSON_GetObjectItem(event, "pubkey");
|
||||
cJSON* event_pubkey = cJSON_GetObjectItemCaseSensitive(event, "pubkey");
|
||||
if (!event_pubkey || !cJSON_IsString(event_pubkey)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -604,7 +601,7 @@ int event_matches_filter(cJSON* event, subscription_filter_t* filter) {
|
||||
|
||||
// Check IDs filter
|
||||
if (filter->ids && cJSON_IsArray(filter->ids)) {
|
||||
cJSON* event_id = cJSON_GetObjectItem(event, "id");
|
||||
cJSON* event_id = cJSON_GetObjectItemCaseSensitive(event, "id");
|
||||
if (!event_id || !cJSON_IsString(event_id)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -631,7 +628,7 @@ int event_matches_filter(cJSON* event, subscription_filter_t* filter) {
|
||||
|
||||
// Check since filter
|
||||
if (filter->since > 0) {
|
||||
cJSON* event_created_at = cJSON_GetObjectItem(event, "created_at");
|
||||
cJSON* event_created_at = cJSON_GetObjectItemCaseSensitive(event, "created_at");
|
||||
if (!event_created_at || !cJSON_IsNumber(event_created_at)) {
|
||||
DEBUG_WARN("FILTER_MATCH: Event has no valid created_at field");
|
||||
return 0;
|
||||
@@ -650,7 +647,7 @@ int event_matches_filter(cJSON* event, subscription_filter_t* filter) {
|
||||
|
||||
// Check until filter
|
||||
if (filter->until > 0) {
|
||||
cJSON* event_created_at = cJSON_GetObjectItem(event, "created_at");
|
||||
cJSON* event_created_at = cJSON_GetObjectItemCaseSensitive(event, "created_at");
|
||||
if (!event_created_at || !cJSON_IsNumber(event_created_at)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -663,7 +660,7 @@ int event_matches_filter(cJSON* event, subscription_filter_t* filter) {
|
||||
|
||||
// Check tag filters (e.g., #e, #p tags)
|
||||
if (filter->tag_filters && cJSON_IsObject(filter->tag_filters)) {
|
||||
cJSON* event_tags = cJSON_GetObjectItem(event, "tags");
|
||||
cJSON* event_tags = cJSON_GetObjectItemCaseSensitive(event, "tags");
|
||||
if (!event_tags || !cJSON_IsArray(event_tags)) {
|
||||
return 0; // Event has no tags but filter requires tags
|
||||
}
|
||||
@@ -777,9 +774,9 @@ int broadcast_event_to_subscriptions(cJSON* event) {
|
||||
int broadcasts = 0;
|
||||
|
||||
// Log event details
|
||||
cJSON* event_kind = cJSON_GetObjectItem(event, "kind");
|
||||
cJSON* event_id = cJSON_GetObjectItem(event, "id");
|
||||
cJSON* event_created_at = cJSON_GetObjectItem(event, "created_at");
|
||||
cJSON* event_kind = cJSON_GetObjectItemCaseSensitive(event, "kind");
|
||||
cJSON* event_id = cJSON_GetObjectItemCaseSensitive(event, "id");
|
||||
cJSON* event_created_at = cJSON_GetObjectItemCaseSensitive(event, "created_at");
|
||||
|
||||
DEBUG_TRACE("BROADCAST: Event kind=%d id=%.8s created_at=%ld",
|
||||
event_kind ? (int)cJSON_GetNumberValue(event_kind) : -1,
|
||||
@@ -873,30 +870,40 @@ int broadcast_event_to_subscriptions(cJSON* event) {
|
||||
// Second pass: send messages without holding lock
|
||||
temp_sub_t* current_temp = matching_subs;
|
||||
while (current_temp) {
|
||||
// Create EVENT message for this subscription
|
||||
cJSON* event_msg = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(event_msg, cJSON_CreateString("EVENT"));
|
||||
cJSON_AddItemToArray(event_msg, cJSON_CreateString(current_temp->id));
|
||||
cJSON_AddItemToArray(event_msg, cJSON_Duplicate(event, 1));
|
||||
|
||||
char* msg_str = cJSON_Print(event_msg);
|
||||
if (msg_str) {
|
||||
size_t msg_len = strlen(msg_str);
|
||||
unsigned char* buf = malloc(LWS_PRE + msg_len);
|
||||
// Serialize event once per subscription using pre-serialized event_json if available,
|
||||
// otherwise fall back to cJSON serialization.
|
||||
// Format: ["EVENT","<sub_id>",<event_json>]
|
||||
const char* event_json_str = NULL;
|
||||
char* event_json_allocated = NULL;
|
||||
|
||||
// Try to get pre-serialized event_json field first (fast path)
|
||||
cJSON* event_json_field = cJSON_GetObjectItemCaseSensitive(event, "event_json");
|
||||
if (event_json_field && cJSON_IsString(event_json_field)) {
|
||||
event_json_str = cJSON_GetStringValue(event_json_field);
|
||||
} else {
|
||||
// Fall back to serializing the event
|
||||
event_json_allocated = cJSON_PrintUnformatted(event);
|
||||
event_json_str = event_json_allocated;
|
||||
}
|
||||
|
||||
if (event_json_str) {
|
||||
size_t sub_id_len = strlen(current_temp->id);
|
||||
size_t event_json_len = strlen(event_json_str);
|
||||
// ["EVENT","<sub_id>",<event_json>]
|
||||
size_t msg_len = 10 + sub_id_len + 3 + event_json_len + 1;
|
||||
|
||||
// Zero-copy: allocate with LWS_PRE prefix, write directly, transfer ownership to queue
|
||||
unsigned char* buf = malloc(LWS_PRE + msg_len + 1);
|
||||
if (buf) {
|
||||
memcpy(buf + LWS_PRE, msg_str, msg_len);
|
||||
|
||||
// DEBUG: Log WebSocket frame details before sending
|
||||
DEBUG_TRACE("WS_FRAME_SEND: type=EVENT sub=%s len=%zu data=%.100s%s",
|
||||
current_temp->id,
|
||||
msg_len,
|
||||
msg_str,
|
||||
msg_len > 100 ? "..." : "");
|
||||
|
||||
// Queue message for proper libwebsockets pattern
|
||||
char* msg_ptr = (char*)(buf + LWS_PRE);
|
||||
snprintf(msg_ptr, msg_len + 1, "[\"EVENT\",\"%s\",%s]", current_temp->id, event_json_str);
|
||||
size_t actual_len = strlen(msg_ptr);
|
||||
|
||||
DEBUG_TRACE("WS_FRAME_SEND: type=EVENT sub=%s len=%zu", current_temp->id, actual_len);
|
||||
|
||||
struct per_session_data* pss = (struct per_session_data*)lws_wsi_user(current_temp->wsi);
|
||||
if (queue_message(current_temp->wsi, pss, msg_str, msg_len, LWS_WRITE_TEXT) == 0) {
|
||||
// Message queued successfully
|
||||
// queue_message_take_ownership takes buf ownership — no memcpy, no free needed here
|
||||
if (queue_message_take_ownership(current_temp->wsi, pss, buf, actual_len, LWS_WRITE_TEXT) == 0) {
|
||||
broadcasts++;
|
||||
|
||||
// Update events sent counter for this subscription
|
||||
@@ -905,30 +912,22 @@ int broadcast_event_to_subscriptions(cJSON* event) {
|
||||
while (update_sub) {
|
||||
if (update_sub->wsi == current_temp->wsi &&
|
||||
strcmp(update_sub->id, current_temp->id) == 0 &&
|
||||
update_sub->active) { // Add active check to prevent use-after-free
|
||||
update_sub->active) {
|
||||
update_sub->events_sent++;
|
||||
break;
|
||||
}
|
||||
update_sub = update_sub->next;
|
||||
}
|
||||
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
|
||||
|
||||
// Log event broadcast to database (optional - can be disabled for performance)
|
||||
// NOTE: event_broadcasts table removed due to FOREIGN KEY constraint issues
|
||||
// cJSON* event_id_obj = cJSON_GetObjectItem(event, "id");
|
||||
// if (event_id_obj && cJSON_IsString(event_id_obj)) {
|
||||
// log_event_broadcast(cJSON_GetStringValue(event_id_obj), current_temp->id, current_temp->client_ip);
|
||||
// }
|
||||
} else {
|
||||
DEBUG_ERROR("Failed to queue EVENT message for sub=%s", current_temp->id);
|
||||
// buf already freed by queue_message_take_ownership on failure
|
||||
}
|
||||
|
||||
free(buf);
|
||||
}
|
||||
free(msg_str);
|
||||
}
|
||||
|
||||
cJSON_Delete(event_msg);
|
||||
if (event_json_allocated) {
|
||||
free(event_json_allocated);
|
||||
}
|
||||
current_temp = current_temp->next;
|
||||
}
|
||||
|
||||
@@ -989,21 +988,21 @@ int has_subscriptions_for_kind(int event_kind) {
|
||||
|
||||
// Log subscription creation to database
|
||||
void log_subscription_created(const subscription_t* sub) {
|
||||
if (!g_db || !sub) return;
|
||||
|
||||
if (!sub) return;
|
||||
|
||||
// Convert wsi pointer to string
|
||||
char wsi_str[32];
|
||||
snprintf(wsi_str, sizeof(wsi_str), "%p", (void*)sub->wsi);
|
||||
|
||||
|
||||
// Create filter JSON for logging
|
||||
char* filter_json = NULL;
|
||||
if (sub->filters) {
|
||||
cJSON* filters_array = cJSON_CreateArray();
|
||||
subscription_filter_t* filter = sub->filters;
|
||||
|
||||
|
||||
while (filter) {
|
||||
cJSON* filter_obj = cJSON_CreateObject();
|
||||
|
||||
|
||||
if (filter->kinds) {
|
||||
cJSON_AddItemToObject(filter_obj, "kinds", cJSON_Duplicate(filter->kinds, 1));
|
||||
}
|
||||
@@ -1032,100 +1031,33 @@ void log_subscription_created(const subscription_t* sub) {
|
||||
}
|
||||
cJSON_Delete(tags_obj);
|
||||
}
|
||||
|
||||
|
||||
cJSON_AddItemToArray(filters_array, filter_obj);
|
||||
filter = filter->next;
|
||||
}
|
||||
|
||||
|
||||
filter_json = cJSON_Print(filters_array);
|
||||
cJSON_Delete(filters_array);
|
||||
}
|
||||
|
||||
// Use INSERT OR REPLACE to handle duplicates automatically
|
||||
const char* sql =
|
||||
"INSERT OR REPLACE INTO subscriptions (subscription_id, wsi_pointer, client_ip, event_type, filter_json) "
|
||||
"VALUES (?, ?, ?, 'created', ?)";
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, sub->id, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, wsi_str, -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(stmt, 3, sub->client_ip, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 4, filter_json ? filter_json : "[]", -1, SQLITE_TRANSIENT);
|
||||
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
|
||||
db_log_subscription_created(sub->id, wsi_str, sub->client_ip, filter_json ? filter_json : "[]");
|
||||
|
||||
if (filter_json) free(filter_json);
|
||||
}
|
||||
|
||||
// Log subscription closure to database
|
||||
void log_subscription_closed(const char* sub_id, const char* client_ip, const char* reason) {
|
||||
(void)reason; // Mark as intentionally unused
|
||||
if (!g_db || !sub_id) return;
|
||||
|
||||
const char* sql =
|
||||
"INSERT INTO subscriptions (subscription_id, wsi_pointer, client_ip, event_type) "
|
||||
"VALUES (?, '', ?, 'closed')";
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, sub_id, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, client_ip ? client_ip : "unknown", -1, SQLITE_STATIC);
|
||||
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
// Update the corresponding 'created' entry with end time and events sent
|
||||
const char* update_sql =
|
||||
"UPDATE subscriptions "
|
||||
"SET ended_at = strftime('%s', 'now') "
|
||||
"WHERE subscription_id = ? AND event_type = 'created' AND ended_at IS NULL";
|
||||
|
||||
rc = sqlite3_prepare_v2(g_db, update_sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, sub_id, -1, SQLITE_STATIC);
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
if (!sub_id) return;
|
||||
|
||||
db_log_subscription_closed(sub_id, client_ip);
|
||||
}
|
||||
|
||||
// Log subscription disconnection to database
|
||||
void log_subscription_disconnected(const char* client_ip) {
|
||||
if (!g_db || !client_ip) return;
|
||||
|
||||
// Mark all active subscriptions for this client as disconnected
|
||||
const char* sql =
|
||||
"UPDATE subscriptions "
|
||||
"SET ended_at = strftime('%s', 'now') "
|
||||
"WHERE client_ip = ? AND event_type = 'created' AND ended_at IS NULL";
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, client_ip, -1, SQLITE_STATIC);
|
||||
int changes = sqlite3_changes(g_db);
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if (changes > 0) {
|
||||
// Log a disconnection event
|
||||
const char* insert_sql =
|
||||
"INSERT INTO subscriptions (subscription_id, wsi_pointer, client_ip, event_type) "
|
||||
"VALUES ('disconnect', '', ?, 'disconnected')";
|
||||
|
||||
rc = sqlite3_prepare_v2(g_db, insert_sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, client_ip, -1, SQLITE_STATIC);
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!client_ip) return;
|
||||
|
||||
db_log_subscription_disconnected(client_ip);
|
||||
}
|
||||
|
||||
// Log event broadcast to database (optional, can be resource intensive)
|
||||
@@ -1151,55 +1083,26 @@ void log_subscription_disconnected(const char* client_ip) {
|
||||
|
||||
// Update events sent counter for a subscription
|
||||
void update_subscription_events_sent(const char* sub_id, int events_sent) {
|
||||
if (!g_db || !sub_id) return;
|
||||
if (!sub_id) return;
|
||||
|
||||
const char* sql =
|
||||
"UPDATE subscriptions "
|
||||
"SET events_sent = ? "
|
||||
"WHERE subscription_id = ? AND event_type = 'created'";
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_int(stmt, 1, events_sent);
|
||||
sqlite3_bind_text(stmt, 2, sub_id, -1, SQLITE_STATIC);
|
||||
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
db_update_subscription_events_sent(sub_id, events_sent);
|
||||
}
|
||||
|
||||
// Cleanup all subscriptions on startup
|
||||
void cleanup_all_subscriptions_on_startup(void) {
|
||||
if (!g_db) {
|
||||
if (!db_is_available()) {
|
||||
DEBUG_ERROR("Database not available for startup cleanup");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
DEBUG_LOG("Performing startup subscription cleanup");
|
||||
|
||||
// Mark all active subscriptions as disconnected
|
||||
const char* sql =
|
||||
"UPDATE subscriptions "
|
||||
"SET ended_at = strftime('%s', 'now') "
|
||||
"WHERE event_type = 'created' AND ended_at IS NULL";
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to prepare startup cleanup query");
|
||||
return;
|
||||
}
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
int changes = sqlite3_changes(g_db);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if (rc != SQLITE_DONE) {
|
||||
|
||||
int changes = db_cleanup_orphaned_subscriptions();
|
||||
if (changes < 0) {
|
||||
DEBUG_ERROR("Failed to execute startup cleanup");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (changes > 0) {
|
||||
DEBUG_LOG("Startup cleanup: marked %d orphaned subscriptions as disconnected", changes);
|
||||
} else {
|
||||
@@ -1473,7 +1376,7 @@ int validate_filter_values(cJSON* filter_json, char* error_message, size_t error
|
||||
int has_kind_99999 = 0; // Track if we encounter kind 99999 (NDK ping)
|
||||
|
||||
// Validate kinds array
|
||||
cJSON* kinds = cJSON_GetObjectItem(filter_json, "kinds");
|
||||
cJSON* kinds = cJSON_GetObjectItemCaseSensitive(filter_json, "kinds");
|
||||
if (kinds) {
|
||||
if (!cJSON_IsArray(kinds)) {
|
||||
snprintf(error_message, error_size, "kinds must be an array");
|
||||
@@ -1516,7 +1419,7 @@ int validate_filter_values(cJSON* filter_json, char* error_message, size_t error
|
||||
}
|
||||
|
||||
// Validate authors array
|
||||
cJSON* authors = cJSON_GetObjectItem(filter_json, "authors");
|
||||
cJSON* authors = cJSON_GetObjectItemCaseSensitive(filter_json, "authors");
|
||||
if (authors) {
|
||||
if (!cJSON_IsArray(authors)) {
|
||||
snprintf(error_message, error_size, "authors must be an array");
|
||||
@@ -1556,7 +1459,7 @@ int validate_filter_values(cJSON* filter_json, char* error_message, size_t error
|
||||
}
|
||||
|
||||
// Validate ids array
|
||||
cJSON* ids = cJSON_GetObjectItem(filter_json, "ids");
|
||||
cJSON* ids = cJSON_GetObjectItemCaseSensitive(filter_json, "ids");
|
||||
if (ids) {
|
||||
if (!cJSON_IsArray(ids)) {
|
||||
snprintf(error_message, error_size, "ids must be an array");
|
||||
@@ -1598,7 +1501,7 @@ int validate_filter_values(cJSON* filter_json, char* error_message, size_t error
|
||||
// Validate since/until timestamps
|
||||
long since_val = 0, until_val = 0;
|
||||
|
||||
cJSON* since = cJSON_GetObjectItem(filter_json, "since");
|
||||
cJSON* since = cJSON_GetObjectItemCaseSensitive(filter_json, "since");
|
||||
if (since) {
|
||||
if (!cJSON_IsNumber(since)) {
|
||||
snprintf(error_message, error_size, "since must be a number");
|
||||
@@ -1607,7 +1510,7 @@ int validate_filter_values(cJSON* filter_json, char* error_message, size_t error
|
||||
since_val = (long)cJSON_GetNumberValue(since);
|
||||
}
|
||||
|
||||
cJSON* until = cJSON_GetObjectItem(filter_json, "until");
|
||||
cJSON* until = cJSON_GetObjectItemCaseSensitive(filter_json, "until");
|
||||
if (until) {
|
||||
if (!cJSON_IsNumber(until)) {
|
||||
snprintf(error_message, error_size, "until must be a number");
|
||||
@@ -1621,7 +1524,7 @@ int validate_filter_values(cJSON* filter_json, char* error_message, size_t error
|
||||
}
|
||||
|
||||
// Validate limit
|
||||
cJSON* limit = cJSON_GetObjectItem(filter_json, "limit");
|
||||
cJSON* limit = cJSON_GetObjectItemCaseSensitive(filter_json, "limit");
|
||||
if (limit) {
|
||||
if (!cJSON_IsNumber(limit)) {
|
||||
snprintf(error_message, error_size, "limit must be a number");
|
||||
@@ -1635,7 +1538,7 @@ int validate_filter_values(cJSON* filter_json, char* error_message, size_t error
|
||||
}
|
||||
|
||||
// Validate search term
|
||||
cJSON* search = cJSON_GetObjectItem(filter_json, "search");
|
||||
cJSON* search = cJSON_GetObjectItemCaseSensitive(filter_json, "search");
|
||||
if (search) {
|
||||
if (!cJSON_IsString(search)) {
|
||||
snprintf(error_message, error_size, "search must be a string");
|
||||
|
||||
@@ -0,0 +1,844 @@
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "thread_pool.h"
|
||||
#include "debug.h"
|
||||
#include "db_ops.h"
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct thread_pool_job_node {
|
||||
uint64_t job_id;
|
||||
thread_pool_job_t job;
|
||||
struct thread_pool_job_node* next;
|
||||
} thread_pool_job_node_t;
|
||||
|
||||
typedef struct {
|
||||
thread_pool_job_node_t* head;
|
||||
thread_pool_job_node_t* tail;
|
||||
int size;
|
||||
int max_size;
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
} thread_pool_queue_t;
|
||||
|
||||
typedef struct {
|
||||
int running;
|
||||
uint64_t next_job_id;
|
||||
|
||||
int reader_count;
|
||||
pthread_t* readers;
|
||||
pthread_t writer;
|
||||
|
||||
thread_pool_queue_t read_q;
|
||||
thread_pool_queue_t write_q;
|
||||
|
||||
thread_pool_wake_loop_cb wake_loop_cb;
|
||||
void* wake_loop_ctx;
|
||||
|
||||
char db_path[512];
|
||||
|
||||
pthread_mutex_t state_mutex;
|
||||
} thread_pool_state_t;
|
||||
|
||||
static thread_pool_state_t g_pool = {0};
|
||||
|
||||
typedef struct {
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
int done;
|
||||
thread_pool_status_t status;
|
||||
void* result_data;
|
||||
size_t result_size;
|
||||
} thread_pool_wait_ctx_t;
|
||||
|
||||
static void wait_ctx_init(thread_pool_wait_ctx_t* ctx) {
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
pthread_mutex_init(&ctx->mutex, NULL);
|
||||
pthread_cond_init(&ctx->cond, NULL);
|
||||
}
|
||||
|
||||
static void wait_ctx_destroy(thread_pool_wait_ctx_t* ctx) {
|
||||
pthread_mutex_destroy(&ctx->mutex);
|
||||
pthread_cond_destroy(&ctx->cond);
|
||||
}
|
||||
|
||||
static void wait_ctx_result_cb(const thread_pool_result_t* result, void* user_ctx) {
|
||||
thread_pool_wait_ctx_t* ctx = (thread_pool_wait_ctx_t*)user_ctx;
|
||||
if (!ctx || !result) return;
|
||||
|
||||
pthread_mutex_lock(&ctx->mutex);
|
||||
ctx->status = result->status;
|
||||
ctx->result_data = result->result_data;
|
||||
ctx->result_size = result->result_size;
|
||||
ctx->done = 1;
|
||||
pthread_cond_signal(&ctx->cond);
|
||||
pthread_mutex_unlock(&ctx->mutex);
|
||||
}
|
||||
|
||||
static void wait_ctx_wait(thread_pool_wait_ctx_t* ctx) {
|
||||
pthread_mutex_lock(&ctx->mutex);
|
||||
while (!ctx->done) {
|
||||
pthread_cond_wait(&ctx->cond, &ctx->mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&ctx->mutex);
|
||||
}
|
||||
|
||||
static void free_count_payload(void* p) {
|
||||
thread_pool_count_payload_t* payload = (thread_pool_count_payload_t*)p;
|
||||
if (!payload) return;
|
||||
free(payload->sql);
|
||||
if (payload->bind_params) {
|
||||
for (int i = 0; i < payload->bind_param_count; i++) {
|
||||
free(payload->bind_params[i]);
|
||||
}
|
||||
free(payload->bind_params);
|
||||
}
|
||||
free(payload);
|
||||
}
|
||||
|
||||
static void free_req_payload(void* p) {
|
||||
thread_pool_req_payload_t* payload = (thread_pool_req_payload_t*)p;
|
||||
if (!payload) return;
|
||||
free(payload->sql);
|
||||
if (payload->bind_params) {
|
||||
for (int i = 0; i < payload->bind_param_count; i++) {
|
||||
free(payload->bind_params[i]);
|
||||
}
|
||||
free(payload->bind_params);
|
||||
}
|
||||
free(payload);
|
||||
}
|
||||
|
||||
static void free_store_event_payload(void* p) {
|
||||
thread_pool_store_event_payload_t* payload = (thread_pool_store_event_payload_t*)p;
|
||||
if (!payload) return;
|
||||
free(payload->id);
|
||||
free(payload->pubkey);
|
||||
free(payload->event_type);
|
||||
free(payload->content);
|
||||
free(payload->sig);
|
||||
free(payload->tags_json);
|
||||
free(payload->event_json);
|
||||
free(payload);
|
||||
}
|
||||
|
||||
static void queue_init(thread_pool_queue_t* q, int max_size) {
|
||||
memset(q, 0, sizeof(*q));
|
||||
q->max_size = (max_size > 0) ? max_size : 4096;
|
||||
pthread_mutex_init(&q->mutex, NULL);
|
||||
pthread_cond_init(&q->cond, NULL);
|
||||
}
|
||||
|
||||
static void queue_destroy(thread_pool_queue_t* q) {
|
||||
pthread_mutex_lock(&q->mutex);
|
||||
thread_pool_job_node_t* cur = q->head;
|
||||
while (cur) {
|
||||
thread_pool_job_node_t* next = cur->next;
|
||||
if (cur->job.payload_free && cur->job.payload) {
|
||||
cur->job.payload_free(cur->job.payload);
|
||||
}
|
||||
free(cur);
|
||||
cur = next;
|
||||
}
|
||||
q->head = q->tail = NULL;
|
||||
q->size = 0;
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
|
||||
pthread_mutex_destroy(&q->mutex);
|
||||
pthread_cond_destroy(&q->cond);
|
||||
}
|
||||
|
||||
static int queue_push(thread_pool_queue_t* q, thread_pool_job_node_t* node) {
|
||||
pthread_mutex_lock(&q->mutex);
|
||||
if (q->size >= q->max_size) {
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
node->next = NULL;
|
||||
if (!q->tail) {
|
||||
q->head = q->tail = node;
|
||||
} else {
|
||||
q->tail->next = node;
|
||||
q->tail = node;
|
||||
}
|
||||
q->size++;
|
||||
pthread_cond_signal(&q->cond);
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static thread_pool_job_node_t* queue_pop(thread_pool_queue_t* q) {
|
||||
pthread_mutex_lock(&q->mutex);
|
||||
while (g_pool.running && q->size == 0) {
|
||||
pthread_cond_wait(&q->cond, &q->mutex);
|
||||
}
|
||||
|
||||
if (!g_pool.running && q->size == 0) {
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
thread_pool_job_node_t* node = q->head;
|
||||
q->head = node->next;
|
||||
if (!q->head) q->tail = NULL;
|
||||
q->size--;
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
return node;
|
||||
}
|
||||
|
||||
static void wake_event_loop(void) {
|
||||
if (g_pool.wake_loop_cb) {
|
||||
g_pool.wake_loop_cb(g_pool.wake_loop_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
static sqlite3* open_worker_connection(void) {
|
||||
if (g_pool.db_path[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sqlite3* db = NULL;
|
||||
int rc = sqlite3_open_v2(g_pool.db_path, &db, SQLITE_OPEN_READWRITE, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
if (db) {
|
||||
sqlite3_close(db);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sqlite3_exec(db, "PRAGMA journal_mode=WAL;", NULL, NULL, NULL);
|
||||
sqlite3_busy_timeout(db, 5000);
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
static void complete_job_with_result(thread_pool_job_node_t* node,
|
||||
thread_pool_status_t status,
|
||||
const char* message,
|
||||
void* result_data,
|
||||
size_t result_size) {
|
||||
thread_pool_result_t result;
|
||||
memset(&result, 0, sizeof(result));
|
||||
result.job_id = node->job_id;
|
||||
result.type = node->job.type;
|
||||
result.status = status;
|
||||
result.session = node->job.session;
|
||||
result.result_data = result_data;
|
||||
result.result_size = result_size;
|
||||
result.message = message;
|
||||
|
||||
if (node->job.result_cb) {
|
||||
node->job.result_cb(&result, node->job.result_cb_ctx);
|
||||
}
|
||||
wake_event_loop();
|
||||
|
||||
if (node->job.payload_free && node->job.payload) {
|
||||
node->job.payload_free(node->job.payload);
|
||||
}
|
||||
free(node);
|
||||
}
|
||||
|
||||
static void execute_count_job(thread_pool_job_node_t* node) {
|
||||
thread_pool_count_payload_t* payload = (thread_pool_count_payload_t*)node->job.payload;
|
||||
if (!payload || !payload->sql) {
|
||||
complete_job_with_result(node, THREAD_POOL_STATUS_INTERNAL_ERROR,
|
||||
"COUNT payload missing", NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
if (db_count_with_sql(payload->sql, (const char**)payload->bind_params,
|
||||
payload->bind_param_count, &count) != 0) {
|
||||
complete_job_with_result(node, THREAD_POOL_STATUS_INTERNAL_ERROR,
|
||||
"COUNT query failed", NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
thread_pool_count_result_t* out = calloc(1, sizeof(*out));
|
||||
if (!out) {
|
||||
complete_job_with_result(node, THREAD_POOL_STATUS_INTERNAL_ERROR,
|
||||
"COUNT result allocation failed", NULL, 0);
|
||||
return;
|
||||
}
|
||||
out->count = count;
|
||||
|
||||
complete_job_with_result(node, THREAD_POOL_STATUS_OK,
|
||||
"COUNT query completed", out, sizeof(*out));
|
||||
}
|
||||
|
||||
static void execute_req_job(thread_pool_job_node_t* node) {
|
||||
thread_pool_req_payload_t* payload = (thread_pool_req_payload_t*)node->job.payload;
|
||||
if (!payload || !payload->sql) {
|
||||
complete_job_with_result(node, THREAD_POOL_STATUS_INTERNAL_ERROR,
|
||||
"REQ payload missing", NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
db_stmt_t* stmt = NULL;
|
||||
if (db_prepare(payload->sql, &stmt) != DB_OK) {
|
||||
complete_job_with_result(node, THREAD_POOL_STATUS_INTERNAL_ERROR,
|
||||
"REQ prepare failed", NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < payload->bind_param_count; i++) {
|
||||
const char* v = (payload->bind_params && payload->bind_params[i]) ? payload->bind_params[i] : "";
|
||||
(void)db_bind_text_param(stmt, i + 1, v);
|
||||
}
|
||||
|
||||
int cap = (payload->row_limit > 0) ? payload->row_limit : 500;
|
||||
if (cap < 1) cap = 1;
|
||||
|
||||
thread_pool_req_result_t* out = calloc(1, sizeof(*out));
|
||||
if (!out) {
|
||||
db_finalize_stmt(stmt);
|
||||
complete_job_with_result(node, THREAD_POOL_STATUS_INTERNAL_ERROR,
|
||||
"REQ result allocation failed", NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
out->event_json_rows = calloc((size_t)cap, sizeof(char*));
|
||||
if (!out->event_json_rows) {
|
||||
db_finalize_stmt(stmt);
|
||||
free(out);
|
||||
complete_job_with_result(node, THREAD_POOL_STATUS_INTERNAL_ERROR,
|
||||
"REQ row array allocation failed", NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
int rc = DB_OK;
|
||||
while ((rc = db_step_stmt(stmt)) == DB_ROW) {
|
||||
if (out->row_count >= cap) break;
|
||||
const char* row = db_column_text_value(stmt, 0);
|
||||
if (!row) continue;
|
||||
|
||||
out->event_json_rows[out->row_count] = strdup(row);
|
||||
if (!out->event_json_rows[out->row_count]) {
|
||||
break;
|
||||
}
|
||||
out->row_count++;
|
||||
}
|
||||
|
||||
db_finalize_stmt(stmt);
|
||||
|
||||
complete_job_with_result(node, THREAD_POOL_STATUS_OK,
|
||||
"REQ query completed", out, sizeof(*out));
|
||||
}
|
||||
|
||||
static void execute_store_event_job(thread_pool_job_node_t* node) {
|
||||
thread_pool_store_event_payload_t* payload = (thread_pool_store_event_payload_t*)node->job.payload;
|
||||
if (!payload || !payload->id || !payload->pubkey || !payload->content || !payload->sig) {
|
||||
complete_job_with_result(node, THREAD_POOL_STATUS_INTERNAL_ERROR,
|
||||
"STORE_EVENT payload missing required fields", NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
thread_pool_store_event_result_t* out = calloc(1, sizeof(*out));
|
||||
if (!out) {
|
||||
complete_job_with_result(node, THREAD_POOL_STATUS_INTERNAL_ERROR,
|
||||
"STORE_EVENT result allocation failed", NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
int step_rc = DB_ERROR;
|
||||
int extended_errcode = 0;
|
||||
if (db_insert_event_with_json(payload->id,
|
||||
payload->pubkey,
|
||||
payload->created_at,
|
||||
payload->kind,
|
||||
payload->event_type ? payload->event_type : "regular",
|
||||
payload->content,
|
||||
payload->sig,
|
||||
payload->tags_json ? payload->tags_json : "[]",
|
||||
payload->event_json ? payload->event_json : "{}",
|
||||
&step_rc,
|
||||
&extended_errcode) != 0) {
|
||||
free(out);
|
||||
complete_job_with_result(node, THREAD_POOL_STATUS_INTERNAL_ERROR,
|
||||
"STORE_EVENT execution failed", NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
out->step_rc = step_rc;
|
||||
out->extended_errcode = extended_errcode;
|
||||
complete_job_with_result(node, THREAD_POOL_STATUS_OK,
|
||||
"STORE_EVENT completed", out, sizeof(*out));
|
||||
}
|
||||
|
||||
static void execute_read_job(thread_pool_job_node_t* node) {
|
||||
switch (node->job.type) {
|
||||
case THREAD_POOL_JOB_REQ_QUERY:
|
||||
execute_req_job(node);
|
||||
break;
|
||||
case THREAD_POOL_JOB_COUNT_QUERY:
|
||||
execute_count_job(node);
|
||||
break;
|
||||
default:
|
||||
complete_job_with_result(node, THREAD_POOL_STATUS_NOT_IMPLEMENTED,
|
||||
"Read job type not implemented", NULL, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void execute_write_job(thread_pool_job_node_t* node) {
|
||||
switch (node->job.type) {
|
||||
case THREAD_POOL_JOB_STORE_EVENT:
|
||||
execute_store_event_job(node);
|
||||
break;
|
||||
default:
|
||||
complete_job_with_result(node, THREAD_POOL_STATUS_NOT_IMPLEMENTED,
|
||||
"Write job type not implemented", NULL, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void* reader_worker_main(void* arg) {
|
||||
int reader_index = (int)(intptr_t)arg;
|
||||
|
||||
char thread_name[16];
|
||||
snprintf(thread_name, sizeof(thread_name), "db-read-%d", reader_index);
|
||||
pthread_setname_np(pthread_self(), thread_name);
|
||||
|
||||
sqlite3* worker_db = open_worker_connection();
|
||||
if (!worker_db) {
|
||||
DEBUG_ERROR("Reader worker failed to open SQLite connection");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
db_set_thread_connection(worker_db);
|
||||
|
||||
while (g_pool.running) {
|
||||
thread_pool_job_node_t* node = queue_pop(&g_pool.read_q);
|
||||
if (!node) break;
|
||||
|
||||
execute_read_job(node);
|
||||
}
|
||||
|
||||
db_clear_thread_connection();
|
||||
sqlite3_close(worker_db);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void* writer_worker_main(void* arg) {
|
||||
(void)arg;
|
||||
|
||||
pthread_setname_np(pthread_self(), "db-write");
|
||||
|
||||
sqlite3* worker_db = open_worker_connection();
|
||||
if (!worker_db) {
|
||||
DEBUG_ERROR("Writer worker failed to open SQLite connection");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
db_set_thread_connection(worker_db);
|
||||
|
||||
while (g_pool.running) {
|
||||
thread_pool_job_node_t* node = queue_pop(&g_pool.write_q);
|
||||
if (!node) break;
|
||||
|
||||
execute_write_job(node);
|
||||
}
|
||||
|
||||
db_clear_thread_connection();
|
||||
sqlite3_close(worker_db);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int thread_pool_init(const thread_pool_config_t* config) {
|
||||
if (!config) return -1;
|
||||
|
||||
pthread_mutex_lock(&g_pool.state_mutex);
|
||||
if (g_pool.running) {
|
||||
pthread_mutex_unlock(&g_pool.state_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
g_pool.reader_count = (config->reader_threads > 0) ? config->reader_threads : 4;
|
||||
g_pool.readers = calloc((size_t)g_pool.reader_count, sizeof(pthread_t));
|
||||
if (!g_pool.readers) {
|
||||
pthread_mutex_unlock(&g_pool.state_mutex);
|
||||
return -1;
|
||||
}
|
||||
|
||||
queue_init(&g_pool.read_q, config->max_queue_depth);
|
||||
queue_init(&g_pool.write_q, config->max_queue_depth);
|
||||
|
||||
g_pool.next_job_id = 1;
|
||||
g_pool.wake_loop_cb = config->wake_loop_cb;
|
||||
g_pool.wake_loop_ctx = config->wake_loop_ctx;
|
||||
|
||||
const char* db_path = (config->db_path && config->db_path[0] != '\0') ? config->db_path : db_get_database_path();
|
||||
if (!db_path || db_path[0] == '\0') {
|
||||
free(g_pool.readers);
|
||||
g_pool.readers = NULL;
|
||||
pthread_mutex_unlock(&g_pool.state_mutex);
|
||||
return -1;
|
||||
}
|
||||
strncpy(g_pool.db_path, db_path, sizeof(g_pool.db_path) - 1);
|
||||
g_pool.db_path[sizeof(g_pool.db_path) - 1] = '\0';
|
||||
|
||||
g_pool.running = 1;
|
||||
|
||||
for (int i = 0; i < g_pool.reader_count; i++) {
|
||||
if (pthread_create(&g_pool.readers[i], NULL, reader_worker_main, (void*)(intptr_t)(i + 1)) != 0) {
|
||||
g_pool.running = 0;
|
||||
pthread_cond_broadcast(&g_pool.read_q.cond);
|
||||
pthread_cond_broadcast(&g_pool.write_q.cond);
|
||||
pthread_mutex_unlock(&g_pool.state_mutex);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (pthread_create(&g_pool.writer, NULL, writer_worker_main, NULL) != 0) {
|
||||
g_pool.running = 0;
|
||||
pthread_cond_broadcast(&g_pool.read_q.cond);
|
||||
pthread_cond_broadcast(&g_pool.write_q.cond);
|
||||
pthread_mutex_unlock(&g_pool.state_mutex);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_pool.state_mutex);
|
||||
DEBUG_LOG("Thread pool initialized: %d readers + 1 writer", g_pool.reader_count);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void thread_pool_shutdown(void) {
|
||||
pthread_mutex_lock(&g_pool.state_mutex);
|
||||
if (!g_pool.running) {
|
||||
pthread_mutex_unlock(&g_pool.state_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
g_pool.running = 0;
|
||||
pthread_cond_broadcast(&g_pool.read_q.cond);
|
||||
pthread_cond_broadcast(&g_pool.write_q.cond);
|
||||
pthread_mutex_unlock(&g_pool.state_mutex);
|
||||
|
||||
for (int i = 0; i < g_pool.reader_count; i++) {
|
||||
pthread_join(g_pool.readers[i], NULL);
|
||||
}
|
||||
pthread_join(g_pool.writer, NULL);
|
||||
|
||||
free(g_pool.readers);
|
||||
g_pool.readers = NULL;
|
||||
g_pool.reader_count = 0;
|
||||
|
||||
queue_destroy(&g_pool.read_q);
|
||||
queue_destroy(&g_pool.write_q);
|
||||
|
||||
DEBUG_LOG("Thread pool shutdown complete");
|
||||
}
|
||||
|
||||
int thread_pool_is_running(void) {
|
||||
return g_pool.running;
|
||||
}
|
||||
|
||||
static thread_pool_status_t submit_to_queue(thread_pool_queue_t* q, const thread_pool_job_t* job, uint64_t* out_job_id) {
|
||||
if (!g_pool.running) return THREAD_POOL_STATUS_SHUTTING_DOWN;
|
||||
if (!job) return THREAD_POOL_STATUS_INTERNAL_ERROR;
|
||||
|
||||
thread_pool_job_node_t* node = calloc(1, sizeof(*node));
|
||||
if (!node) return THREAD_POOL_STATUS_INTERNAL_ERROR;
|
||||
|
||||
node->job = *job;
|
||||
|
||||
pthread_mutex_lock(&g_pool.state_mutex);
|
||||
node->job_id = g_pool.next_job_id++;
|
||||
pthread_mutex_unlock(&g_pool.state_mutex);
|
||||
|
||||
if (out_job_id) *out_job_id = node->job_id;
|
||||
|
||||
if (!queue_push(q, node)) {
|
||||
if (job->payload_free && job->payload) {
|
||||
job->payload_free(job->payload);
|
||||
}
|
||||
free(node);
|
||||
return THREAD_POOL_STATUS_QUEUE_FULL;
|
||||
}
|
||||
|
||||
return THREAD_POOL_STATUS_OK;
|
||||
}
|
||||
|
||||
thread_pool_status_t thread_pool_submit_read(const thread_pool_job_t* job, uint64_t* out_job_id) {
|
||||
return submit_to_queue(&g_pool.read_q, job, out_job_id);
|
||||
}
|
||||
|
||||
thread_pool_status_t thread_pool_submit_write(const thread_pool_job_t* job, uint64_t* out_job_id) {
|
||||
return submit_to_queue(&g_pool.write_q, job, out_job_id);
|
||||
}
|
||||
|
||||
int thread_pool_execute_count_sync(const char* sql, const char** bind_params, int bind_param_count, int* out_count) {
|
||||
if (!sql || !out_count) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!thread_pool_is_running()) {
|
||||
return db_count_with_sql(sql, bind_params, bind_param_count, out_count);
|
||||
}
|
||||
|
||||
thread_pool_count_payload_t* payload = calloc(1, sizeof(*payload));
|
||||
if (!payload) return -1;
|
||||
|
||||
payload->sql = strdup(sql);
|
||||
payload->bind_param_count = bind_param_count;
|
||||
if (!payload->sql) {
|
||||
free_count_payload(payload);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (bind_param_count > 0) {
|
||||
payload->bind_params = calloc((size_t)bind_param_count, sizeof(char*));
|
||||
if (!payload->bind_params) {
|
||||
free_count_payload(payload);
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < bind_param_count; i++) {
|
||||
const char* v = (bind_params && bind_params[i]) ? bind_params[i] : "";
|
||||
payload->bind_params[i] = strdup(v);
|
||||
if (!payload->bind_params[i]) {
|
||||
free_count_payload(payload);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thread_pool_wait_ctx_t wait_ctx;
|
||||
wait_ctx_init(&wait_ctx);
|
||||
|
||||
thread_pool_job_t job;
|
||||
memset(&job, 0, sizeof(job));
|
||||
job.type = THREAD_POOL_JOB_COUNT_QUERY;
|
||||
job.payload = payload;
|
||||
job.payload_free = free_count_payload;
|
||||
job.result_cb = wait_ctx_result_cb;
|
||||
job.result_cb_ctx = &wait_ctx;
|
||||
|
||||
thread_pool_status_t submit_rc = thread_pool_submit_read(&job, NULL);
|
||||
if (submit_rc != THREAD_POOL_STATUS_OK) {
|
||||
free_count_payload(payload);
|
||||
wait_ctx_destroy(&wait_ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
wait_ctx_wait(&wait_ctx);
|
||||
|
||||
int rc = -1;
|
||||
if (wait_ctx.status == THREAD_POOL_STATUS_OK && wait_ctx.result_data) {
|
||||
thread_pool_count_result_t* result = (thread_pool_count_result_t*)wait_ctx.result_data;
|
||||
*out_count = result->count;
|
||||
free(result);
|
||||
rc = 0;
|
||||
}
|
||||
|
||||
wait_ctx_destroy(&wait_ctx);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int thread_pool_execute_req_sync(const char* sql, const char** bind_params, int bind_param_count,
|
||||
int row_limit, thread_pool_req_result_t** out_result) {
|
||||
if (!sql || !out_result) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!thread_pool_is_running()) {
|
||||
db_stmt_t* stmt = NULL;
|
||||
if (db_prepare(sql, &stmt) != DB_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < bind_param_count; i++) {
|
||||
const char* v = (bind_params && bind_params[i]) ? bind_params[i] : "";
|
||||
(void)db_bind_text_param(stmt, i + 1, v);
|
||||
}
|
||||
|
||||
int cap = (row_limit > 0) ? row_limit : 500;
|
||||
if (cap < 1) cap = 1;
|
||||
|
||||
thread_pool_req_result_t* result = calloc(1, sizeof(*result));
|
||||
if (!result) {
|
||||
db_finalize_stmt(stmt);
|
||||
return -1;
|
||||
}
|
||||
|
||||
result->event_json_rows = calloc((size_t)cap, sizeof(char*));
|
||||
if (!result->event_json_rows) {
|
||||
free(result);
|
||||
db_finalize_stmt(stmt);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int step_rc = DB_OK;
|
||||
while ((step_rc = db_step_stmt(stmt)) == DB_ROW) {
|
||||
if (result->row_count >= cap) break;
|
||||
const char* row = db_column_text_value(stmt, 0);
|
||||
if (!row) continue;
|
||||
|
||||
result->event_json_rows[result->row_count] = strdup(row);
|
||||
if (!result->event_json_rows[result->row_count]) {
|
||||
break;
|
||||
}
|
||||
result->row_count++;
|
||||
}
|
||||
|
||||
db_finalize_stmt(stmt);
|
||||
*out_result = result;
|
||||
return 0;
|
||||
}
|
||||
|
||||
thread_pool_req_payload_t* payload = calloc(1, sizeof(*payload));
|
||||
if (!payload) return -1;
|
||||
|
||||
payload->sql = strdup(sql);
|
||||
payload->bind_param_count = bind_param_count;
|
||||
payload->row_limit = row_limit;
|
||||
if (!payload->sql) {
|
||||
free_req_payload(payload);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (bind_param_count > 0) {
|
||||
payload->bind_params = calloc((size_t)bind_param_count, sizeof(char*));
|
||||
if (!payload->bind_params) {
|
||||
free_req_payload(payload);
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < bind_param_count; i++) {
|
||||
const char* v = (bind_params && bind_params[i]) ? bind_params[i] : "";
|
||||
payload->bind_params[i] = strdup(v);
|
||||
if (!payload->bind_params[i]) {
|
||||
free_req_payload(payload);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thread_pool_wait_ctx_t wait_ctx;
|
||||
wait_ctx_init(&wait_ctx);
|
||||
|
||||
thread_pool_job_t job;
|
||||
memset(&job, 0, sizeof(job));
|
||||
job.type = THREAD_POOL_JOB_REQ_QUERY;
|
||||
job.payload = payload;
|
||||
job.payload_free = free_req_payload;
|
||||
job.result_cb = wait_ctx_result_cb;
|
||||
job.result_cb_ctx = &wait_ctx;
|
||||
|
||||
thread_pool_status_t submit_rc = thread_pool_submit_read(&job, NULL);
|
||||
if (submit_rc != THREAD_POOL_STATUS_OK) {
|
||||
free_req_payload(payload);
|
||||
wait_ctx_destroy(&wait_ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
wait_ctx_wait(&wait_ctx);
|
||||
|
||||
int rc = -1;
|
||||
if (wait_ctx.status == THREAD_POOL_STATUS_OK && wait_ctx.result_data) {
|
||||
*out_result = (thread_pool_req_result_t*)wait_ctx.result_data;
|
||||
rc = 0;
|
||||
}
|
||||
|
||||
wait_ctx_destroy(&wait_ctx);
|
||||
return rc;
|
||||
}
|
||||
|
||||
void thread_pool_free_req_result(thread_pool_req_result_t* result) {
|
||||
if (!result) return;
|
||||
if (result->event_json_rows) {
|
||||
for (int i = 0; i < result->row_count; i++) {
|
||||
free(result->event_json_rows[i]);
|
||||
}
|
||||
free(result->event_json_rows);
|
||||
}
|
||||
free(result);
|
||||
}
|
||||
|
||||
int thread_pool_execute_store_event_sync(const thread_pool_store_event_payload_t* payload,
|
||||
thread_pool_store_event_result_t* out_result) {
|
||||
if (!payload || !out_result || !payload->id || !payload->pubkey || !payload->content || !payload->sig) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!thread_pool_is_running()) {
|
||||
int step_rc = DB_ERROR;
|
||||
int extended_errcode = 0;
|
||||
if (db_insert_event_with_json(payload->id,
|
||||
payload->pubkey,
|
||||
payload->created_at,
|
||||
payload->kind,
|
||||
payload->event_type ? payload->event_type : "regular",
|
||||
payload->content,
|
||||
payload->sig,
|
||||
payload->tags_json ? payload->tags_json : "[]",
|
||||
payload->event_json ? payload->event_json : "{}",
|
||||
&step_rc,
|
||||
&extended_errcode) != 0) {
|
||||
return -1;
|
||||
}
|
||||
out_result->step_rc = step_rc;
|
||||
out_result->extended_errcode = extended_errcode;
|
||||
return 0;
|
||||
}
|
||||
|
||||
thread_pool_store_event_payload_t* payload_copy = calloc(1, sizeof(*payload_copy));
|
||||
if (!payload_copy) return -1;
|
||||
|
||||
payload_copy->id = payload->id ? strdup(payload->id) : NULL;
|
||||
payload_copy->pubkey = payload->pubkey ? strdup(payload->pubkey) : NULL;
|
||||
payload_copy->created_at = payload->created_at;
|
||||
payload_copy->kind = payload->kind;
|
||||
payload_copy->event_type = payload->event_type ? strdup(payload->event_type) : strdup("regular");
|
||||
payload_copy->content = payload->content ? strdup(payload->content) : NULL;
|
||||
payload_copy->sig = payload->sig ? strdup(payload->sig) : NULL;
|
||||
payload_copy->tags_json = payload->tags_json ? strdup(payload->tags_json) : strdup("[]");
|
||||
payload_copy->event_json = payload->event_json ? strdup(payload->event_json) : strdup("{}");
|
||||
|
||||
if (!payload_copy->id || !payload_copy->pubkey || !payload_copy->event_type ||
|
||||
!payload_copy->content || !payload_copy->sig || !payload_copy->tags_json || !payload_copy->event_json) {
|
||||
free_store_event_payload(payload_copy);
|
||||
return -1;
|
||||
}
|
||||
|
||||
thread_pool_wait_ctx_t wait_ctx;
|
||||
wait_ctx_init(&wait_ctx);
|
||||
|
||||
thread_pool_job_t job;
|
||||
memset(&job, 0, sizeof(job));
|
||||
job.type = THREAD_POOL_JOB_STORE_EVENT;
|
||||
job.payload = payload_copy;
|
||||
job.payload_free = free_store_event_payload;
|
||||
job.result_cb = wait_ctx_result_cb;
|
||||
job.result_cb_ctx = &wait_ctx;
|
||||
|
||||
thread_pool_status_t submit_rc = thread_pool_submit_write(&job, NULL);
|
||||
if (submit_rc != THREAD_POOL_STATUS_OK) {
|
||||
free_store_event_payload(payload_copy);
|
||||
wait_ctx_destroy(&wait_ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
wait_ctx_wait(&wait_ctx);
|
||||
|
||||
int rc = -1;
|
||||
if (wait_ctx.status == THREAD_POOL_STATUS_OK && wait_ctx.result_data) {
|
||||
thread_pool_store_event_result_t* result = (thread_pool_store_event_result_t*)wait_ctx.result_data;
|
||||
out_result->step_rc = result->step_rc;
|
||||
out_result->extended_errcode = result->extended_errcode;
|
||||
free(result);
|
||||
rc = 0;
|
||||
}
|
||||
|
||||
wait_ctx_destroy(&wait_ctx);
|
||||
return rc;
|
||||
}
|
||||
|
||||
__attribute__((constructor))
|
||||
static void thread_pool_state_init_once(void) {
|
||||
pthread_mutex_init(&g_pool.state_mutex, NULL);
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
#ifndef THREAD_POOL_H
|
||||
#define THREAD_POOL_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
THREAD_POOL_JOB_REQ_QUERY = 0,
|
||||
THREAD_POOL_JOB_COUNT_QUERY,
|
||||
THREAD_POOL_JOB_STORE_EVENT,
|
||||
THREAD_POOL_JOB_DELETE_EVENT,
|
||||
THREAD_POOL_JOB_CUSTOM
|
||||
} thread_pool_job_type_t;
|
||||
|
||||
typedef enum {
|
||||
THREAD_POOL_STATUS_OK = 0,
|
||||
THREAD_POOL_STATUS_NOT_IMPLEMENTED,
|
||||
THREAD_POOL_STATUS_QUEUE_FULL,
|
||||
THREAD_POOL_STATUS_SHUTTING_DOWN,
|
||||
THREAD_POOL_STATUS_INTERNAL_ERROR
|
||||
} thread_pool_status_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t job_id;
|
||||
thread_pool_job_type_t type;
|
||||
thread_pool_status_t status;
|
||||
void* session;
|
||||
void* result_data;
|
||||
size_t result_size;
|
||||
const char* message;
|
||||
} thread_pool_result_t;
|
||||
|
||||
typedef void (*thread_pool_result_cb)(const thread_pool_result_t* result, void* user_ctx);
|
||||
typedef void (*thread_pool_payload_free_cb)(void* payload);
|
||||
typedef void (*thread_pool_wake_loop_cb)(void* wake_ctx);
|
||||
|
||||
typedef struct {
|
||||
thread_pool_job_type_t type;
|
||||
void* session;
|
||||
void* payload;
|
||||
size_t payload_size;
|
||||
thread_pool_payload_free_cb payload_free;
|
||||
thread_pool_result_cb result_cb;
|
||||
void* result_cb_ctx;
|
||||
} thread_pool_job_t;
|
||||
|
||||
typedef struct {
|
||||
int reader_threads;
|
||||
int max_queue_depth;
|
||||
const char* db_path;
|
||||
thread_pool_wake_loop_cb wake_loop_cb;
|
||||
void* wake_loop_ctx;
|
||||
} thread_pool_config_t;
|
||||
|
||||
typedef struct {
|
||||
char* sql;
|
||||
char** bind_params;
|
||||
int bind_param_count;
|
||||
} thread_pool_count_payload_t;
|
||||
|
||||
typedef struct {
|
||||
char* sql;
|
||||
char** bind_params;
|
||||
int bind_param_count;
|
||||
int row_limit;
|
||||
} thread_pool_req_payload_t;
|
||||
|
||||
typedef struct {
|
||||
char* id;
|
||||
char* pubkey;
|
||||
long long created_at;
|
||||
int kind;
|
||||
char* event_type;
|
||||
char* content;
|
||||
char* sig;
|
||||
char* tags_json;
|
||||
char* event_json;
|
||||
} thread_pool_store_event_payload_t;
|
||||
|
||||
typedef struct {
|
||||
int count;
|
||||
} thread_pool_count_result_t;
|
||||
|
||||
typedef struct {
|
||||
char** event_json_rows;
|
||||
int row_count;
|
||||
} thread_pool_req_result_t;
|
||||
|
||||
typedef struct {
|
||||
int step_rc;
|
||||
int extended_errcode;
|
||||
} thread_pool_store_event_result_t;
|
||||
|
||||
int thread_pool_init(const thread_pool_config_t* config);
|
||||
void thread_pool_shutdown(void);
|
||||
int thread_pool_is_running(void);
|
||||
|
||||
thread_pool_status_t thread_pool_submit_read(const thread_pool_job_t* job, uint64_t* out_job_id);
|
||||
thread_pool_status_t thread_pool_submit_write(const thread_pool_job_t* job, uint64_t* out_job_id);
|
||||
|
||||
int thread_pool_execute_count_sync(const char* sql, const char** bind_params, int bind_param_count, int* out_count);
|
||||
int thread_pool_execute_req_sync(const char* sql, const char** bind_params, int bind_param_count,
|
||||
int row_limit, thread_pool_req_result_t** out_result);
|
||||
void thread_pool_free_req_result(thread_pool_req_result_t* result);
|
||||
int thread_pool_execute_store_event_sync(const thread_pool_store_event_payload_t* payload,
|
||||
thread_pool_store_event_result_t* out_result);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // THREAD_POOL_H
|
||||
+1054
-147
File diff suppressed because it is too large
Load Diff
@@ -19,6 +19,11 @@
|
||||
#define MALFORMED_REQUEST_BLOCK_DURATION 3600 // 1 hour in seconds
|
||||
#define RATE_LIMIT_CLEANUP_INTERVAL 300 // 5 minutes
|
||||
|
||||
// Maximum number of messages allowed in a per-client write queue.
|
||||
// When exceeded, new messages are dropped to prevent unbounded memory growth
|
||||
// under high-traffic or slow-client conditions.
|
||||
#define MAX_MESSAGE_QUEUE_SIZE 500
|
||||
|
||||
// Filter validation constants
|
||||
#define MAX_FILTERS_PER_REQUEST 10
|
||||
#define MAX_AUTHORS_PER_FILTER 1000
|
||||
@@ -84,8 +89,19 @@ struct per_session_data {
|
||||
int db_queries_executed; // Total SELECT queries executed by this connection
|
||||
int db_rows_returned; // Total rows returned across all queries
|
||||
time_t query_tracking_start; // When connection was established (for rate calculation)
|
||||
|
||||
// Session activity tracking for idle connection banning
|
||||
int session_active; // 1 if client sent REQ or EVENT, 0 otherwise
|
||||
int idle_timeout_sec; // Timeout value for this session (copied from config)
|
||||
int is_websocket; // 1 if this is a WebSocket connection, 0 for HTTP
|
||||
};
|
||||
|
||||
// Get current active WebSocket connection count
|
||||
int get_active_connection_count(void);
|
||||
|
||||
// Actual relay port bound by libwebsockets at runtime (after fallback/retry logic)
|
||||
extern int g_relay_port;
|
||||
|
||||
// NIP-11 HTTP session data structure for managing buffer lifetime
|
||||
struct nip11_session_data {
|
||||
int type; // 0 for NIP-11
|
||||
@@ -102,6 +118,10 @@ int start_websocket_relay(int port_override, int strict_port);
|
||||
int queue_message(struct lws* wsi, struct per_session_data* pss, const char* message, size_t length, enum lws_write_protocol type);
|
||||
int process_message_queue(struct lws* wsi, struct per_session_data* pss);
|
||||
|
||||
// Zero-copy variant: caller allocates (LWS_PRE + length) bytes, writes message at buf+LWS_PRE,
|
||||
// then passes ownership to the queue. The queue will free buf when done. No memcpy performed.
|
||||
int queue_message_take_ownership(struct lws* wsi, struct per_session_data* pss, unsigned char* buf, size_t length, enum lws_write_protocol type);
|
||||
|
||||
// Auth rules checking function from request_validator.c
|
||||
int check_database_auth_rules(const char *pubkey, const char *operation, const char *resource_hash);
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
2026-04-01 09:57:22 - ==========================================
|
||||
2026-04-01 09:57:22 - C-Relay Comprehensive Test Suite Runner
|
||||
2026-04-01 09:57:22 - ==========================================
|
||||
2026-04-01 09:57:22 - Relay URL: ws://127.0.0.1:8888
|
||||
2026-04-01 09:57:22 - Log file: test_results_20260401_095722.log
|
||||
2026-04-01 09:57:22 - Report file: test_report_20260401_095722.html
|
||||
2026-04-01 09:57:22 -
|
||||
2026-04-01 09:57:22 - Checking relay status at ws://127.0.0.1:8888...
|
||||
2026-04-01 09:57:22 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m
|
||||
2026-04-01 09:57:22 -
|
||||
2026-04-01 09:57:22 - Starting comprehensive test execution...
|
||||
2026-04-01 09:57:22 -
|
||||
2026-04-01 09:57:22 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m
|
||||
2026-04-01 09:57:22 - ==========================================
|
||||
2026-04-01 09:57:22 - Running Test Suite: SQL Injection Tests
|
||||
2026-04-01 09:57:22 - Description: Comprehensive SQL injection vulnerability testing
|
||||
2026-04-01 09:57:22 - ==========================================
|
||||
2026-04-01 09:57:22 - \033[0;31mERROR: Test script sql_injection_tests.sh not found\033[0m
|
||||
@@ -0,0 +1,18 @@
|
||||
2026-04-01 11:01:47 - ==========================================
|
||||
2026-04-01 11:01:47 - C-Relay Comprehensive Test Suite Runner
|
||||
2026-04-01 11:01:47 - ==========================================
|
||||
2026-04-01 11:01:47 - Relay URL: ws://127.0.0.1:8888
|
||||
2026-04-01 11:01:47 - Log file: test_results_20260401_110147.log
|
||||
2026-04-01 11:01:47 - Report file: test_report_20260401_110147.html
|
||||
2026-04-01 11:01:47 -
|
||||
2026-04-01 11:01:47 - Checking relay status at ws://127.0.0.1:8888...
|
||||
2026-04-01 11:01:47 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m
|
||||
2026-04-01 11:01:47 -
|
||||
2026-04-01 11:01:47 - Starting comprehensive test execution...
|
||||
2026-04-01 11:01:47 -
|
||||
2026-04-01 11:01:47 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m
|
||||
2026-04-01 11:01:47 - ==========================================
|
||||
2026-04-01 11:01:47 - Running Test Suite: SQL Injection Tests
|
||||
2026-04-01 11:01:47 - Description: Comprehensive SQL injection vulnerability testing
|
||||
2026-04-01 11:01:47 - ==========================================
|
||||
2026-04-01 11:01:47 - \033[0;31mERROR: Test script sql_injection_tests.sh not found\033[0m
|
||||
+15
-6
@@ -54,6 +54,8 @@ BASELINE_KIND1=0
|
||||
BASELINE_KIND0=0
|
||||
BASELINE_KIND30001=0
|
||||
BASELINE_AUTHOR=0
|
||||
BASELINE_AUTHOR_KIND0=0
|
||||
BASELINE_AUTHOR_KIND30001=0
|
||||
BASELINE_TYPE_REGULAR=0
|
||||
BASELINE_TEST_NIP45=0
|
||||
BASELINE_KINDS_01=0
|
||||
@@ -253,9 +255,11 @@ run_count_test() {
|
||||
|
||||
publish_event "$regular1" "regular" "Regular event #1"
|
||||
|
||||
# Now that we have the pubkey, get the author baseline
|
||||
# Now that we have the pubkey, get author baselines
|
||||
local test_pubkey=$(echo "$regular1" | jq -r '.pubkey' 2>/dev/null)
|
||||
BASELINE_AUTHOR=$(get_baseline_count "{\"authors\":[\"$test_pubkey\"]}" "events by test author")
|
||||
BASELINE_AUTHOR_KIND0=$(get_baseline_count "{\"authors\":[\"$test_pubkey\"],\"kinds\":[0]}" "kind 0 events by test author")
|
||||
BASELINE_AUTHOR_KIND30001=$(get_baseline_count "{\"authors\":[\"$test_pubkey\"],\"kinds\":[30001]}" "kind 30001 events by test author")
|
||||
|
||||
publish_event "$regular2" "regular" "Regular event #2"
|
||||
publish_event "$regular3" "regular" "Regular event #3"
|
||||
@@ -314,10 +318,13 @@ run_count_test() {
|
||||
fi
|
||||
|
||||
# Test 3: Count events by author (pubkey)
|
||||
# BASELINE_AUTHOR includes the first regular event, we add 2 more regular
|
||||
# Replaceable and addressable replace existing events from this author
|
||||
# BASELINE_AUTHOR is measured after regular1 is already published.
|
||||
# Net additions after baseline:
|
||||
# +2 regular (regular2, regular3)
|
||||
# +(1 - BASELINE_AUTHOR_KIND0) from replaceable upsert behavior
|
||||
# +(1 - BASELINE_AUTHOR_KIND30001) from addressable replacement behavior
|
||||
local test_pubkey=$(echo "$regular1" | jq -r '.pubkey' 2>/dev/null)
|
||||
local expected_author=$((2 + BASELINE_AUTHOR))
|
||||
local expected_author=$((BASELINE_AUTHOR + 2 + (1 - BASELINE_AUTHOR_KIND0) + (1 - BASELINE_AUTHOR_KIND30001)))
|
||||
if ! test_count "count_author" "{\"authors\":[\"$test_pubkey\"]}" "Count events by specific author" "$expected_author"; then
|
||||
((test_failures++))
|
||||
fi
|
||||
@@ -340,8 +347,10 @@ run_count_test() {
|
||||
fi
|
||||
|
||||
# Test 6: Count multiple kinds
|
||||
# BASELINE_KINDS_01 + 3 regular events = total for kinds 0+1
|
||||
local expected_kinds_01=$((3 + BASELINE_KINDS_01))
|
||||
# Net additions for kinds 0+1 after baseline:
|
||||
# +3 kind-1 regular events
|
||||
# +(1 - BASELINE_AUTHOR_KIND0) for kind-0 replaceable upsert behavior
|
||||
local expected_kinds_01=$((BASELINE_KINDS_01 + 3 + (1 - BASELINE_AUTHOR_KIND0)))
|
||||
if ! test_count "count_multi_kinds" '{"kinds":[0,1]}' "Count multiple kinds (0,1)" "$expected_kinds_01"; then
|
||||
((test_failures++))
|
||||
fi
|
||||
|
||||
@@ -8,7 +8,7 @@ set -e
|
||||
# Configuration
|
||||
RELAY_HOST="127.0.0.1"
|
||||
RELAY_PORT="8888"
|
||||
TEST_TIMEOUT=5
|
||||
TEST_TIMEOUT=10
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
@@ -34,7 +34,7 @@ test_websocket_message() {
|
||||
|
||||
# Send message via websocat and capture response
|
||||
local response
|
||||
response=$(echo "$message" | timeout $TEST_TIMEOUT websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null || echo 'CONNECTION_FAILED')
|
||||
response=$(echo "$message" | timeout $TEST_TIMEOUT websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1 || echo 'CONNECTION_FAILED')
|
||||
|
||||
if [[ "$response" == "CONNECTION_FAILED" ]]; then
|
||||
echo -e "${RED}FAILED${NC} - Could not connect to relay"
|
||||
@@ -42,8 +42,8 @@ test_websocket_message() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$response" == "TIMEOUT" ]]; then
|
||||
echo -e "${RED}FAILED${NC} - Connection timeout"
|
||||
if [[ -z "$response" ]]; then
|
||||
echo -e "${RED}FAILED${NC} - Empty response"
|
||||
FAILED_TESTS=$((FAILED_TESTS + 1))
|
||||
return 1
|
||||
fi
|
||||
@@ -119,8 +119,9 @@ test_websocket_message "Invalid author type" '["REQ","sub1",{"authors":[123]}]'
|
||||
# Test 6: Invalid author hex
|
||||
test_websocket_message "Invalid author hex" '["REQ","sub1",{"authors":["invalid-hex"]}]' "error: invalid author hex string"
|
||||
|
||||
# Test 7: Too many authors
|
||||
test_websocket_message "Too many authors" '["REQ","sub1",{"authors":["a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a"]}]' "error: too many authors"
|
||||
# Test 7: Too many authors (exceed MAX_AUTHORS_PER_FILTER with compact payload)
|
||||
too_many_authors=$(yes '"a"' | head -n 1001 | paste -sd, -)
|
||||
test_websocket_message "Too many authors" "[\"REQ\",\"sub1\",{\"authors\":[${too_many_authors}]}]" "error: too many authors"
|
||||
|
||||
echo
|
||||
echo "=== Testing IDs Validation ==="
|
||||
@@ -131,8 +132,9 @@ test_websocket_message "Invalid ID type" '["REQ","sub1",{"ids":[123]}]' "error:
|
||||
# Test 9: Invalid ID hex
|
||||
test_websocket_message "Invalid ID hex" '["REQ","sub1",{"ids":["invalid-hex"]}]' "error: invalid id hex string"
|
||||
|
||||
# Test 10: Too many IDs
|
||||
test_websocket_message "Too many IDs" '["REQ","sub1",{"ids":["a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a"]}]' "error: too many ids"
|
||||
# Test 10: Too many IDs (exceed MAX_IDS_PER_FILTER with compact payload)
|
||||
too_many_ids=$(yes '"a"' | head -n 1001 | paste -sd, -)
|
||||
test_websocket_message "Too many IDs" "[\"REQ\",\"sub1\",{\"ids\":[${too_many_ids}]}]" "error: too many ids"
|
||||
|
||||
echo
|
||||
echo "=== Testing Kinds Validation ==="
|
||||
@@ -146,8 +148,9 @@ test_websocket_message "Negative kind" '["REQ","sub1",{"kinds":[-1]}]' "error: i
|
||||
# Test 13: Too large kind
|
||||
test_websocket_message "Too large kind" '["REQ","sub1",{"kinds":[70000]}]' "error: invalid kind value"
|
||||
|
||||
# Test 14: Too many kinds
|
||||
test_websocket_message "Too many kinds" '["REQ","sub1",{"kinds":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52]}]' "error: too many kinds"
|
||||
# Test 14: Too many kinds (exceed MAX_KINDS_PER_FILTER=500)
|
||||
too_many_kinds=$(seq -s, 1 501)
|
||||
test_websocket_message "Too many kinds" "[\"REQ\",\"sub1\",{\"kinds\":[${too_many_kinds}]}]" "error: too many kinds"
|
||||
|
||||
echo
|
||||
echo "=== Testing Timestamp Validation ==="
|
||||
@@ -194,8 +197,9 @@ echo "=== Testing Tag Filter Validation ==="
|
||||
# Test 25: Invalid tag filter type
|
||||
test_websocket_message "Invalid tag filter type" '["REQ","sub1",{"#e":"not-an-array"}]' "error: #e must be an array"
|
||||
|
||||
# Test 26: Too many tag values
|
||||
test_websocket_message "Too many tag values" '["REQ","sub1",{"#e":['$(printf '"a%.0s",' {1..101})'"a"]}]' "error: too many #e values"
|
||||
# Test 26: Too many tag values (exceed MAX_TAG_VALUES_PER_FILTER=1000)
|
||||
too_many_tag_values=$(yes '"a"' | head -n 1001 | paste -sd, -)
|
||||
test_websocket_message "Too many tag values" "[\"REQ\",\"sub1\",{\"#e\":[${too_many_tag_values}]}]" "error: too many #e values"
|
||||
|
||||
# Test 27: Tag value too long
|
||||
test_websocket_message "Tag value too long" '["REQ","sub1",{"#e":["'$(printf 'a%.0s' {1..1025})'"]}]' "error: #e value too long"
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import WebSocket from 'ws';
|
||||
import {
|
||||
finalizeEvent,
|
||||
generateSecretKey,
|
||||
getPublicKey,
|
||||
nip19,
|
||||
nip42,
|
||||
} from 'nostr-tools';
|
||||
|
||||
const RELAY_URL = process.env.RELAY_URL || 'ws://127.0.0.1:7777';
|
||||
const EVENT_COUNT = Number(process.env.EVENT_COUNT || 5);
|
||||
const DELAY_MS = Number(process.env.DELAY_MS || 2000);
|
||||
const RECIPIENT_PUBKEY_ENV = process.env.RECIPIENT_PUBKEY || '';
|
||||
const SECRET_INPUT = process.env.NOSTR_SECRET_KEY || '';
|
||||
const OVERALL_TIMEOUT_MS = Number(process.env.OVERALL_TIMEOUT_MS || 120000);
|
||||
|
||||
function now() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function log(msg, extra = null) {
|
||||
if (extra !== null) {
|
||||
console.log(`[${now()}] ${msg}`, extra);
|
||||
} else {
|
||||
console.log(`[${now()}] ${msg}`);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeHexSecret(secretInput) {
|
||||
if (!secretInput) return null;
|
||||
|
||||
const raw = secretInput.trim();
|
||||
if (/^[0-9a-fA-F]{64}$/.test(raw)) {
|
||||
return raw.toLowerCase();
|
||||
}
|
||||
|
||||
if (raw.startsWith('nsec1')) {
|
||||
const decoded = nip19.decode(raw);
|
||||
if (decoded.type !== 'nsec') {
|
||||
throw new Error('NOSTR_SECRET_KEY is nsec-like but did not decode as nsec');
|
||||
}
|
||||
if (typeof decoded.data === 'string') {
|
||||
return decoded.data.toLowerCase();
|
||||
}
|
||||
if (decoded.data instanceof Uint8Array) {
|
||||
return Buffer.from(decoded.data).toString('hex').toLowerCase();
|
||||
}
|
||||
throw new Error('Unsupported nsec decoded payload type');
|
||||
}
|
||||
|
||||
throw new Error('NOSTR_SECRET_KEY must be 64-char hex or nsec1...');
|
||||
}
|
||||
|
||||
const hexSecret = normalizeHexSecret(SECRET_INPUT) || Buffer.from(generateSecretKey()).toString('hex');
|
||||
const secretKey = Buffer.from(hexSecret, 'hex');
|
||||
const senderPubkey = getPublicKey(secretKey);
|
||||
const recipientPubkey = RECIPIENT_PUBKEY_ENV || senderPubkey;
|
||||
|
||||
let ws;
|
||||
let currentChallenge = null;
|
||||
let authenticated = false;
|
||||
let authAttempts = 0;
|
||||
let sentAccepted = 0;
|
||||
let nextSeq = 1;
|
||||
let pendingEvent = null;
|
||||
let waitingForAuth = false;
|
||||
let finished = false;
|
||||
let overallTimer = null;
|
||||
|
||||
function makeKind4Event(seq) {
|
||||
const eventTemplate = {
|
||||
kind: 4,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [['p', recipientPubkey]],
|
||||
content: `kind4 disconnect probe seq=${seq} ts=${Date.now()}`,
|
||||
pubkey: senderPubkey,
|
||||
};
|
||||
return finalizeEvent(eventTemplate, secretKey);
|
||||
}
|
||||
|
||||
function sendJson(arr) {
|
||||
const payload = JSON.stringify(arr);
|
||||
ws.send(payload);
|
||||
}
|
||||
|
||||
function sendAuth(challenge) {
|
||||
const authTemplate = nip42.makeAuthEvent(RELAY_URL, challenge);
|
||||
const signedAuth = finalizeEvent(authTemplate, secretKey);
|
||||
authAttempts += 1;
|
||||
log(`Sending AUTH attempt #${authAttempts} with challenge ${challenge}`);
|
||||
sendJson(['AUTH', signedAuth]);
|
||||
}
|
||||
|
||||
function scheduleNext() {
|
||||
if (nextSeq > EVENT_COUNT) {
|
||||
log(`All ${EVENT_COUNT} kind-4 events were accepted without disconnect`);
|
||||
cleanupAndExit(0);
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (!finished) {
|
||||
publishNext();
|
||||
}
|
||||
}, DELAY_MS);
|
||||
}
|
||||
|
||||
function publishNext() {
|
||||
if (waitingForAuth) {
|
||||
log('Still waiting for auth completion, postponing publish');
|
||||
return;
|
||||
}
|
||||
|
||||
const ev = makeKind4Event(nextSeq);
|
||||
pendingEvent = ev;
|
||||
log(`Publishing kind-4 seq=${nextSeq} id=${ev.id.slice(0, 12)}...`);
|
||||
sendJson(['EVENT', ev]);
|
||||
}
|
||||
|
||||
function retryPendingAfterAuth() {
|
||||
if (!pendingEvent) return;
|
||||
waitingForAuth = false;
|
||||
log(`Retrying pending kind-4 id=${pendingEvent.id.slice(0, 12)}... after auth`);
|
||||
sendJson(['EVENT', pendingEvent]);
|
||||
}
|
||||
|
||||
function handleAuthRequiredSignal(source, msg) {
|
||||
waitingForAuth = true;
|
||||
log(`Relay signaled auth-required via ${source}: ${msg}`);
|
||||
if (currentChallenge) {
|
||||
sendAuth(currentChallenge);
|
||||
} else {
|
||||
log('No AUTH challenge received yet; waiting for relay AUTH challenge message');
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupAndExit(code) {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
if (overallTimer) clearTimeout(overallTimer);
|
||||
|
||||
const summary = {
|
||||
relay: RELAY_URL,
|
||||
sentAccepted,
|
||||
expected: EVENT_COUNT,
|
||||
authAttempts,
|
||||
authenticated,
|
||||
senderPubkey,
|
||||
recipientPubkey,
|
||||
};
|
||||
|
||||
if (code === 0) {
|
||||
log('PASS: Connection remained stable through repeated kind-4 publish cycle', summary);
|
||||
} else {
|
||||
log('FAIL: Relay disconnected or test did not complete successfully', summary);
|
||||
}
|
||||
|
||||
try {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.close();
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
function main() {
|
||||
log('Starting kind-4 disconnect probe', {
|
||||
relay: RELAY_URL,
|
||||
eventCount: EVENT_COUNT,
|
||||
delayMs: DELAY_MS,
|
||||
senderPubkey,
|
||||
recipientPubkey,
|
||||
});
|
||||
|
||||
ws = new WebSocket(RELAY_URL);
|
||||
|
||||
overallTimer = setTimeout(() => {
|
||||
log(`Overall timeout reached (${OVERALL_TIMEOUT_MS}ms)`);
|
||||
cleanupAndExit(1);
|
||||
}, OVERALL_TIMEOUT_MS);
|
||||
|
||||
ws.on('open', () => {
|
||||
log('WebSocket connected');
|
||||
publishNext();
|
||||
});
|
||||
|
||||
ws.on('message', (raw) => {
|
||||
const text = raw.toString();
|
||||
let msg;
|
||||
try {
|
||||
msg = JSON.parse(text);
|
||||
} catch {
|
||||
log(`Non-JSON relay message: ${text}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const t = msg?.[0];
|
||||
|
||||
if (t === 'AUTH') {
|
||||
currentChallenge = msg?.[1] || null;
|
||||
log(`Received AUTH challenge: ${currentChallenge}`);
|
||||
if (currentChallenge) sendAuth(currentChallenge);
|
||||
return;
|
||||
}
|
||||
|
||||
if (t === 'NOTICE') {
|
||||
const notice = String(msg?.[1] || '');
|
||||
log(`NOTICE: ${notice}`);
|
||||
|
||||
if (notice.toLowerCase().includes('auth-required')) {
|
||||
handleAuthRequiredSignal('NOTICE', notice);
|
||||
return;
|
||||
}
|
||||
|
||||
if (notice.toLowerCase().includes('authentication successful')) {
|
||||
authenticated = true;
|
||||
log('Relay confirmed NIP-42 authentication success');
|
||||
retryPendingAfterAuth();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (t === 'OK') {
|
||||
const eventId = msg?.[1];
|
||||
const ok = !!msg?.[2];
|
||||
const reason = String(msg?.[3] || '');
|
||||
log(`OK for ${String(eventId).slice(0, 12)}... accepted=${ok} reason=${reason}`);
|
||||
|
||||
if (!ok && reason.toLowerCase().includes('auth-required')) {
|
||||
handleAuthRequiredSignal('OK', reason);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingEvent && eventId === pendingEvent.id) {
|
||||
if (ok) {
|
||||
sentAccepted += 1;
|
||||
nextSeq += 1;
|
||||
pendingEvent = null;
|
||||
waitingForAuth = false;
|
||||
scheduleNext();
|
||||
} else {
|
||||
log(`Pending event rejected: ${reason}`);
|
||||
cleanupAndExit(1);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
log('Relay message', msg);
|
||||
});
|
||||
|
||||
ws.on('close', (code, reasonBuf) => {
|
||||
const reason = reasonBuf?.toString?.() || '';
|
||||
log(`WebSocket closed code=${code} reason=${reason}`);
|
||||
if (!finished) {
|
||||
if (sentAccepted >= EVENT_COUNT) {
|
||||
cleanupAndExit(0);
|
||||
} else {
|
||||
cleanupAndExit(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('error', (err) => {
|
||||
log(`WebSocket error: ${err.message}`);
|
||||
cleanupAndExit(1);
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,87 @@
|
||||
=== NIP-42 Authentication Test Started ===
|
||||
2026-04-01 10:00:44 - Starting NIP-42 authentication tests
|
||||
[34m[1m[INFO][0m === Starting NIP-42 Authentication Tests ===
|
||||
[34m[1m[INFO][0m Checking dependencies...
|
||||
[33m[1m[WARNING][0m wscat not found. Some manual WebSocket tests will be skipped
|
||||
[33m[1m[WARNING][0m Install with: npm install -g wscat
|
||||
[32m[1m[SUCCESS][0m Dependencies check complete
|
||||
[34m[1m[INFO][0m Test 1: Checking NIP-42 support in relay info
|
||||
[32m[1m[SUCCESS][0m NIP-42 is advertised in supported NIPs
|
||||
2026-04-01 10:00:44 - Supported NIPs: 1,2,4,9,11,12,13,15,16,20,22,33,40,42,50,70
|
||||
[34m[1m[INFO][0m Test 2: Testing AUTH challenge generation
|
||||
[33m[1m[WARNING][0m Could not extract admin private key from relay.log - using manual test approach
|
||||
[34m[1m[INFO][0m Manual test: Connect to relay and send an event without auth to trigger challenge
|
||||
[34m[1m[INFO][0m Test 3: Testing complete NIP-42 authentication flow
|
||||
[34m[1m[INFO][0m Generated test keypair: test_pubkey
|
||||
[34m[1m[INFO][0m Attempting to publish event without authentication...
|
||||
[34m[1m[INFO][0m Publishing test event to relay...
|
||||
2026-04-01 10:00:45 - Event publish result: connecting to localhost:8888... ok.
|
||||
{"kind":1,"id":"28174ed72f9ef5484a6596db6c6be181d2ed10e46d8c22a9f7a267becbc9a47a","pubkey":"b5f9adc3b362c0e81569a46648a3996708d6b47754d36c617683f18e1f1daf6b","created_at":1775052045,"tags":[],"content":"NIP-42 test event - should require auth","sig":"0041347b233a21d6542a263634f52f8ca7e3b5ecf07bc37c86f390f1a7ab0bc2051306b14aa70edff020bd7311b259d03c2c35a035b285f9e19051a924546666"}
|
||||
publishing to ws://localhost:8888... success.
|
||||
[32m[1m[SUCCESS][0m Relay requested authentication as expected
|
||||
[34m[1m[INFO][0m Test 4: Testing WebSocket AUTH message handling
|
||||
[33m[1m[WARNING][0m Skipping WebSocket tests - wscat not available
|
||||
[34m[1m[INFO][0m Test 5: Testing NIP-42 configuration options
|
||||
[34m[1m[INFO][0m Retrieving current relay configuration...
|
||||
[33m[1m[WARNING][0m Could not retrieve configuration events
|
||||
[34m[1m[INFO][0m Test 6: Testing NIP-42 performance and stability
|
||||
[34m[1m[INFO][0m Testing multiple authentication attempts...
|
||||
2026-04-01 10:00:46 - Attempt 1: .195528002s - connecting to localhost:8888... ok.
|
||||
{"kind":1,"id":"72526c57e50d721bfcdcca23856f2c5d3021c3100f0121538d9d829042e9b8b5","pubkey":"29af457d7eea750c11c836dfbeed3076fa0bf1f1b67a404ee4c25733d9b54238","created_at":1775052046,"tags":[],"content":"Performance test event 1","sig":"1a10db86a39aaf47bda6d0cca5f888691bae32bfd85df8c178dddbf24861503a491bfe4da8d66dfe3e5e0afec7bf8644e7eb69c9f051c601673ae13284f9ab31"}
|
||||
publishing to ws://localhost:8888... success.
|
||||
2026-04-01 10:00:46 - Attempt 2: .185198180s - connecting to localhost:8888... ok.
|
||||
{"kind":1,"id":"84eeb907205806bb88fc353bb83f1bf0149f3c93c18695306d8480d36752878b","pubkey":"29af457d7eea750c11c836dfbeed3076fa0bf1f1b67a404ee4c25733d9b54238","created_at":1775052046,"tags":[],"content":"Performance test event 2","sig":"e568dbca3113966f61e69eff05576d296fac42b5ff2ab7341778acc02cf6e9440b59bf651a8a2995d89180a62ec20ffc1143e211cf2960c5abaa358a82992ac6"}
|
||||
publishing to ws://localhost:8888... success.
|
||||
2026-04-01 10:00:47 - Attempt 3: .197159540s - connecting to localhost:8888... ok.
|
||||
{"kind":1,"id":"0874716ba0b2a845d379e703226513f90db6228ce958369f4dce5aff3df64c5a","pubkey":"29af457d7eea750c11c836dfbeed3076fa0bf1f1b67a404ee4c25733d9b54238","created_at":1775052046,"tags":[],"content":"Performance test event 3","sig":"e74e885eb464998bddf655c24c95a18d7299908c63fb82a47f8d14af65992f90e73794a9f289b550bc1dec1c0298112d2b25941be7e26e3f17dd66b4c5dc742e"}
|
||||
publishing to ws://localhost:8888... success.
|
||||
2026-04-01 10:00:47 - Attempt 4: .202520319s - connecting to localhost:8888... ok.
|
||||
{"kind":1,"id":"5614d1b795cd028e096720418bb4dbc64870d442c6295b9bb2931564a332099a","pubkey":"29af457d7eea750c11c836dfbeed3076fa0bf1f1b67a404ee4c25733d9b54238","created_at":1775052047,"tags":[],"content":"Performance test event 4","sig":"1c0645a791ab9df573b152b782884bcb5ec2a18b8f4e1c18d7dc2175977b7d1d572674169ea1d2b3e3fa8d228de90f6cc281d56a3ef471ba4f9510e920ed586e"}
|
||||
publishing to ws://localhost:8888... success.
|
||||
2026-04-01 10:00:47 - Attempt 5: .222530325s - connecting to localhost:8888... ok.
|
||||
{"kind":1,"id":"9139f1eb39c31c43cb9f02e60a0f75a0f99f6c7d9693a69a225c8b3e530f774a","pubkey":"29af457d7eea750c11c836dfbeed3076fa0bf1f1b67a404ee4c25733d9b54238","created_at":1775052047,"tags":[],"content":"Performance test event 5","sig":"bd245a7629c33619c0ed426f631e0c2413adb4488c281bea09886ceb6407b40e247d42f82e374e2f53ee5281f0d8361661a4c613c6414c59d09e5bb23eeeb02d"}
|
||||
publishing to ws://localhost:8888... success.
|
||||
[32m[1m[SUCCESS][0m Performance test completed: 5/5 successful responses
|
||||
[34m[1m[INFO][0m Test 7: Testing kind-specific NIP-42 authentication requirements
|
||||
[34m[1m[INFO][0m Generated test keypair for kind-specific tests: test_pubkey
|
||||
[34m[1m[INFO][0m Testing kind 1 event (regular note) - should work without authentication...
|
||||
2026-04-01 10:00:48 - Kind 1 event result: connecting to localhost:8888... ok.
|
||||
{"kind":1,"id":"def386c66576a91f144a7321e9520a3ed46f9fecc56d9e79d32a162f249cace9","pubkey":"7c9b00749c966e50d18d4b807b44d4612bf9a8f67925fac3f00d21918b6e7f9c","created_at":1775052048,"tags":[],"content":"Regular note - should not require auth","sig":"7ee8fbf45e96d040df1a688e9bf028faaa49b81b26eeae6452b3a32cd9fd941c27b41734f14268d3deef14dbb19e514d29789211fffb27bfa9b73d20cddcc83e"}
|
||||
publishing to ws://localhost:8888... success.
|
||||
[32m[1m[SUCCESS][0m Kind 1 event accepted without authentication (correct behavior)
|
||||
[34m[1m[INFO][0m Testing kind 4 event (direct message) - should require authentication...
|
||||
2026-04-01 10:00:58 - Kind 4 event result: connecting to localhost:8888... ok.
|
||||
{"kind":4,"id":"a2f2d176a835d17a0c9229c0ce3b1ba61a5eb81308e4a3b3446634f468721238","pubkey":"7c9b00749c966e50d18d4b807b44d4612bf9a8f67925fac3f00d21918b6e7f9c","created_at":1775052048,"tags":[["p,test_pubkey"]],"content":"This is a direct message - should require auth","sig":"441f9caba6ebcec6b6c55d13ffd9571f3fe441a54ddd0692f2868429a5087378d791c97289b8b990560dcf40f5c66e86ba6b41f1de78220a5f389c1f119bb887"}
|
||||
publishing to ws://localhost:8888...
|
||||
[32m[1m[SUCCESS][0m Kind 4 event requested authentication (correct behavior for DMs)
|
||||
[34m[1m[INFO][0m Testing kind 14 event (chat message) - should require authentication...
|
||||
2026-04-01 10:01:09 - Kind 14 event result: connecting to localhost:8888... ok.
|
||||
{"kind":14,"id":"e234a6faa63f95cd24f8596fbfac573730533bb0abcde47b4d341ff0acace657","pubkey":"7c9b00749c966e50d18d4b807b44d4612bf9a8f67925fac3f00d21918b6e7f9c","created_at":1775052059,"tags":[["p,test_pubkey"]],"content":"Chat message - should require auth","sig":"fd6ba0ddaa7e2379aa49c5cda6599d0a38264799b228a8e907b948bef645080603f7f910c587f6cfb1ef1bac946c47dc681fdab6abd248376cd552bcd535ef9e"}
|
||||
publishing to ws://localhost:8888...
|
||||
[32m[1m[SUCCESS][0m Kind 14 event requested authentication (correct behavior for DMs)
|
||||
[34m[1m[INFO][0m Testing other event kinds - should work without authentication...
|
||||
2026-04-01 10:01:09 - Kind 0 event result: connecting to localhost:8888... ok.
|
||||
{"kind":0,"id":"d87b69e8a2c8f6d98229b55cde3f0fe1b70c6cafd080e4462209849338eafc76","pubkey":"7c9b00749c966e50d18d4b807b44d4612bf9a8f67925fac3f00d21918b6e7f9c","created_at":1775052069,"tags":[],"content":"Test event kind 0 - should not require auth","sig":"e3222bd4882c881d039e6790a6a8d9f8e20a71d0f9d5089e98604acaa4feff7c1b1b2d9b4171d640da3bba955408a9391a0fd52334cc4a4165fa932e5163f9dd"}
|
||||
publishing to ws://localhost:8888... success.
|
||||
[32m[1m[SUCCESS][0m Kind 0 event accepted without authentication (correct)
|
||||
2026-04-01 10:01:09 - Kind 3 event result: connecting to localhost:8888... ok.
|
||||
{"kind":3,"id":"ac0d45d7db2818bb5e674d4760711349928cf3d5dc8f1fc3717d9e7274e26fb0","pubkey":"7c9b00749c966e50d18d4b807b44d4612bf9a8f67925fac3f00d21918b6e7f9c","created_at":1775052069,"tags":[],"content":"Test event kind 3 - should not require auth","sig":"b65b317c81bc733ee2ff5921430b34d722e8d177147c5d1cddf14036573223582a3b7232810fc13169eb7a61ab3b99cd37ce13991673fce142d1ea10c702305e"}
|
||||
publishing to ws://localhost:8888... success.
|
||||
[32m[1m[SUCCESS][0m Kind 3 event accepted without authentication (correct)
|
||||
2026-04-01 10:01:10 - Kind 7 event result: connecting to localhost:8888... ok.
|
||||
{"kind":7,"id":"ad7e77948a4742abfca2d63c5bb748f3db3940c85841f521b3b7d634bf09955a","pubkey":"7c9b00749c966e50d18d4b807b44d4612bf9a8f67925fac3f00d21918b6e7f9c","created_at":1775052070,"tags":[],"content":"Test event kind 7 - should not require auth","sig":"8a5e5a03956cec04d56934314a0876fccbd221546432aaccdbc249b5f9cedc221219abf6e2856b98675de205afaa0e91ea642933ec6724576a40db43d17aa758"}
|
||||
publishing to ws://localhost:8888... success.
|
||||
[32m[1m[SUCCESS][0m Kind 7 event accepted without authentication (correct)
|
||||
[34m[1m[INFO][0m Kind-specific authentication test completed
|
||||
[34m[1m[INFO][0m === NIP-42 Test Results Summary ===
|
||||
[32m[1m[SUCCESS][0m Dependencies: PASS
|
||||
[32m[1m[SUCCESS][0m NIP-42 Support: PASS
|
||||
[32m[1m[SUCCESS][0m Auth Challenge: PASS
|
||||
[32m[1m[SUCCESS][0m Auth Flow: PASS
|
||||
[32m[1m[SUCCESS][0m WebSocket AUTH: PASS
|
||||
[32m[1m[SUCCESS][0m Configuration: PASS
|
||||
[32m[1m[SUCCESS][0m Performance: PASS
|
||||
[32m[1m[SUCCESS][0m Kind-Specific Auth: PASS
|
||||
[32m[1m[SUCCESS][0m All NIP-42 tests completed successfully!
|
||||
[32m[1m[SUCCESS][0m NIP-42 authentication implementation is working correctly
|
||||
[34m[1m[INFO][0m === NIP-42 Authentication Tests Complete ===
|
||||
@@ -0,0 +1,392 @@
|
||||
2026-04-01 09:11:07 - ==========================================
|
||||
2026-04-01 09:11:07 - C-Relay Comprehensive Test Suite Runner
|
||||
2026-04-01 09:11:07 - ==========================================
|
||||
2026-04-01 09:11:07 - Relay URL: ws://127.0.0.1:8888
|
||||
2026-04-01 09:11:07 - Log file: test_results_20260401_091107.log
|
||||
2026-04-01 09:11:07 - Report file: test_report_20260401_091107.html
|
||||
2026-04-01 09:11:07 -
|
||||
2026-04-01 09:11:07 - Checking relay status at ws://127.0.0.1:8888...
|
||||
2026-04-01 09:11:07 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m
|
||||
2026-04-01 09:11:07 -
|
||||
2026-04-01 09:11:07 - Starting comprehensive test execution...
|
||||
2026-04-01 09:11:07 -
|
||||
2026-04-01 09:11:07 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m
|
||||
2026-04-01 09:11:07 - ==========================================
|
||||
2026-04-01 09:11:07 - Running Test Suite: SQL Injection Tests
|
||||
2026-04-01 09:11:07 - Description: Comprehensive SQL injection vulnerability testing
|
||||
2026-04-01 09:11:07 - ==========================================
|
||||
==========================================
|
||||
C-Relay SQL Injection Test Suite
|
||||
==========================================
|
||||
Testing against relay at ws://127.0.0.1:8888
|
||||
|
||||
=== Basic Connectivity Test ===
|
||||
Testing Basic connectivity... [0;32mPASSED[0m - Valid query works
|
||||
|
||||
=== Authors Filter SQL Injection Tests ===
|
||||
Testing Authors filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== IDs Filter SQL Injection Tests ===
|
||||
Testing IDs filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Kinds Filter SQL Injection Tests ===
|
||||
Testing Kinds filter with string injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Kinds filter with negative value... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Kinds filter with very large value... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Search Filter SQL Injection Tests ===
|
||||
Testing Search filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing Search filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing Search filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing Search filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing Search filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Tag Filter SQL Injection Tests ===
|
||||
Testing #e tag filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
|
||||
=== Timestamp Filter SQL Injection Tests ===
|
||||
Testing Since parameter injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Until parameter injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Limit Parameter SQL Injection Tests ===
|
||||
Testing Limit parameter injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Limit with UNION... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Complex Multi-Filter SQL Injection Tests ===
|
||||
Testing Multi-filter with authors injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Multi-filter with search injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Multi-filter with tag injection... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
|
||||
=== COUNT Message SQL Injection Tests ===
|
||||
Testing COUNT with authors payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing COUNT with authors payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: /*... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: /*... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: */... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: */... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: /**/... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: /**/... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: #... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing COUNT with authors payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing COUNT with authors payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing COUNT with authors payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Edge Case SQL Injection Tests ===
|
||||
Testing Empty string injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Null byte injection... [0;32mPASSED[0m - SQL injection blocked (silently rejected)
|
||||
Testing Unicode injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Very long injection payload... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Subscription ID SQL Injection Tests ===
|
||||
Testing Subscription ID injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Subscription ID with quotes... [0;32mPASSED[0m - SQL injection blocked (silently rejected)
|
||||
|
||||
=== CLOSE Message SQL Injection Tests ===
|
||||
Testing CLOSE with injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Test Results ===
|
||||
Total tests: 318
|
||||
Passed: [0;32m318[0m
|
||||
Failed: [0;31m0[0m
|
||||
[0;32m✓ All SQL injection tests passed![0m
|
||||
The relay appears to be protected against SQL injection attacks.
|
||||
2026-04-01 09:11:14 - \033[0;32m✓ SQL Injection Tests PASSED\033[0m (Duration: 7s)
|
||||
2026-04-01 09:11:14 - ==========================================
|
||||
2026-04-01 09:11:14 - Running Test Suite: Filter Validation Tests
|
||||
2026-04-01 09:11:14 - Description: Input validation for REQ and COUNT messages
|
||||
2026-04-01 09:11:14 - ==========================================
|
||||
=== C-Relay Filter Validation Tests ===
|
||||
Testing against relay at ws://127.0.0.1:8888
|
||||
|
||||
Testing Valid REQ message... [0;32mPASSED[0m
|
||||
Testing Valid COUNT message... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Filter Array Validation ===
|
||||
Testing Non-object filter... [0;32mPASSED[0m
|
||||
Testing Too many filters... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Authors Validation ===
|
||||
Testing Invalid author type... [0;32mPASSED[0m
|
||||
Testing Invalid author hex... [0;32mPASSED[0m
|
||||
Testing Too many authors... [0;31mFAILED[0m - Expected error 'error: too many authors', got: ["NOTICE", "error: invalid author hex string"]
|
||||
2026-04-01 09:11:14 - \033[0;31m✗ Filter Validation Tests FAILED\033[0m (Duration: 0s)
|
||||
@@ -0,0 +1,733 @@
|
||||
2026-04-01 09:57:29 - ==========================================
|
||||
2026-04-01 09:57:30 - C-Relay Comprehensive Test Suite Runner
|
||||
2026-04-01 09:57:30 - ==========================================
|
||||
2026-04-01 09:57:30 - Relay URL: ws://127.0.0.1:8888
|
||||
2026-04-01 09:57:30 - Log file: test_results_20260401_095729.log
|
||||
2026-04-01 09:57:30 - Report file: test_report_20260401_095729.html
|
||||
2026-04-01 09:57:30 -
|
||||
2026-04-01 09:57:30 - Checking relay status at ws://127.0.0.1:8888...
|
||||
2026-04-01 09:57:30 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m
|
||||
2026-04-01 09:57:30 -
|
||||
2026-04-01 09:57:30 - Starting comprehensive test execution...
|
||||
2026-04-01 09:57:30 -
|
||||
2026-04-01 09:57:30 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m
|
||||
2026-04-01 09:57:30 - ==========================================
|
||||
2026-04-01 09:57:30 - Running Test Suite: SQL Injection Tests
|
||||
2026-04-01 09:57:30 - Description: Comprehensive SQL injection vulnerability testing
|
||||
2026-04-01 09:57:30 - ==========================================
|
||||
==========================================
|
||||
C-Relay SQL Injection Test Suite
|
||||
==========================================
|
||||
Testing against relay at ws://127.0.0.1:8888
|
||||
|
||||
=== Basic Connectivity Test ===
|
||||
Testing Basic connectivity... [0;32mPASSED[0m - Valid query works
|
||||
|
||||
=== Authors Filter SQL Injection Tests ===
|
||||
Testing Authors filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== IDs Filter SQL Injection Tests ===
|
||||
Testing IDs filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Kinds Filter SQL Injection Tests ===
|
||||
Testing Kinds filter with string injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Kinds filter with negative value... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Kinds filter with very large value... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Search Filter SQL Injection Tests ===
|
||||
Testing Search filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing Search filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing Search filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing Search filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing Search filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Tag Filter SQL Injection Tests ===
|
||||
Testing #e tag filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
|
||||
=== Timestamp Filter SQL Injection Tests ===
|
||||
Testing Since parameter injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Until parameter injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Limit Parameter SQL Injection Tests ===
|
||||
Testing Limit parameter injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Limit with UNION... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Complex Multi-Filter SQL Injection Tests ===
|
||||
Testing Multi-filter with authors injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Multi-filter with search injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Multi-filter with tag injection... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
|
||||
=== COUNT Message SQL Injection Tests ===
|
||||
Testing COUNT with authors payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing COUNT with authors payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: /*... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: /*... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: */... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: */... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: /**/... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: /**/... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: #... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing COUNT with authors payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing COUNT with authors payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing COUNT with authors payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Edge Case SQL Injection Tests ===
|
||||
Testing Empty string injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Null byte injection... [0;32mPASSED[0m - SQL injection blocked (silently rejected)
|
||||
Testing Unicode injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Very long injection payload... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Subscription ID SQL Injection Tests ===
|
||||
Testing Subscription ID injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Subscription ID with quotes... [0;32mPASSED[0m - SQL injection blocked (silently rejected)
|
||||
|
||||
=== CLOSE Message SQL Injection Tests ===
|
||||
Testing CLOSE with injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Test Results ===
|
||||
Total tests: 318
|
||||
Passed: [0;32m318[0m
|
||||
Failed: [0;31m0[0m
|
||||
[0;32m✓ All SQL injection tests passed![0m
|
||||
The relay appears to be protected against SQL injection attacks.
|
||||
2026-04-01 09:57:36 - \033[0;32m✓ SQL Injection Tests PASSED\033[0m (Duration: 6s)
|
||||
2026-04-01 09:57:36 - ==========================================
|
||||
2026-04-01 09:57:36 - Running Test Suite: Filter Validation Tests
|
||||
2026-04-01 09:57:36 - Description: Input validation for REQ and COUNT messages
|
||||
2026-04-01 09:57:36 - ==========================================
|
||||
=== C-Relay Filter Validation Tests ===
|
||||
Testing against relay at ws://127.0.0.1:8888
|
||||
|
||||
Testing Valid REQ message... [0;32mPASSED[0m
|
||||
Testing Valid COUNT message... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Filter Array Validation ===
|
||||
Testing Non-object filter... [0;32mPASSED[0m
|
||||
Testing Too many filters... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Authors Validation ===
|
||||
Testing Invalid author type... [0;32mPASSED[0m
|
||||
Testing Invalid author hex... [0;32mPASSED[0m
|
||||
Testing Too many authors... [0;32mPASSED[0m
|
||||
|
||||
=== Testing IDs Validation ===
|
||||
Testing Invalid ID type... [0;32mPASSED[0m
|
||||
Testing Invalid ID hex... [0;32mPASSED[0m
|
||||
Testing Too many IDs... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Kinds Validation ===
|
||||
Testing Invalid kind type... [0;32mPASSED[0m
|
||||
Testing Negative kind... [0;32mPASSED[0m
|
||||
Testing Too large kind... [0;32mPASSED[0m
|
||||
Testing Too many kinds... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Timestamp Validation ===
|
||||
Testing Invalid since type... [0;32mPASSED[0m
|
||||
Testing Negative since... [0;32mPASSED[0m
|
||||
Testing Invalid until type... [0;32mPASSED[0m
|
||||
Testing Negative until... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Limit Validation ===
|
||||
Testing Invalid limit type... [0;32mPASSED[0m
|
||||
Testing Negative limit... [0;32mPASSED[0m
|
||||
Testing Too large limit... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Search Validation ===
|
||||
Testing Invalid search type... [0;32mPASSED[0m
|
||||
Testing Search too long... [0;32mPASSED[0m
|
||||
Testing Search SQL injection... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Tag Filter Validation ===
|
||||
Testing Invalid tag filter type... [0;32mPASSED[0m
|
||||
Testing Too many tag values... [0;32mPASSED[0m
|
||||
Testing Tag value too long... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Rate Limiting ===
|
||||
Testing rate limiting with malformed requests... [1;33mUNCERTAIN[0m - Rate limiting may not have triggered (this could be normal)
|
||||
|
||||
=== Test Results ===
|
||||
Total tests: 28
|
||||
Passed: [0;32m28[0m
|
||||
Failed: [0;31m0[0m
|
||||
[0;32mAll tests passed![0m
|
||||
2026-04-01 09:57:39 - \033[0;32m✓ Filter Validation Tests PASSED\033[0m (Duration: 3s)
|
||||
2026-04-01 09:57:39 - ==========================================
|
||||
2026-04-01 09:57:39 - Running Test Suite: Subscription Validation Tests
|
||||
2026-04-01 09:57:39 - Description: Subscription ID and message validation
|
||||
2026-04-01 09:57:39 - ==========================================
|
||||
Testing subscription ID validation fixes...
|
||||
Testing malformed subscription IDs...
|
||||
Empty ID test: Connection failed (expected)
|
||||
Long ID test: Connection failed (expected)
|
||||
Invalid chars test: Connection failed (expected)
|
||||
NULL ID test: Connection failed (expected)
|
||||
Valid ID test: Failed
|
||||
Testing CLOSE message validation...
|
||||
CLOSE empty ID test: Connection failed (expected)
|
||||
CLOSE valid ID test: Failed
|
||||
Subscription validation tests completed.
|
||||
2026-04-01 09:57:39 - \033[0;32m✓ Subscription Validation Tests PASSED\033[0m (Duration: 0s)
|
||||
2026-04-01 09:57:39 - ==========================================
|
||||
2026-04-01 09:57:39 - Running Test Suite: Memory Corruption Tests
|
||||
2026-04-01 09:57:39 - Description: Buffer overflow and memory safety testing
|
||||
2026-04-01 09:57:39 - ==========================================
|
||||
==========================================
|
||||
C-Relay Memory Corruption Test Suite
|
||||
==========================================
|
||||
Testing against relay at ws://127.0.0.1:8888
|
||||
Note: These tests may cause the relay to crash if vulnerabilities exist
|
||||
|
||||
=== Basic Connectivity Test ===
|
||||
Testing Basic connectivity... [0;32mPASSED[0m - No memory corruption detected
|
||||
|
||||
=== Subscription ID Memory Corruption Tests ===
|
||||
Testing Empty subscription ID... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Very long subscription ID (1KB)... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Very long subscription ID (10KB)... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Subscription ID with null bytes... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Subscription ID with special chars... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Unicode subscription ID... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Subscription ID with path traversal... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
|
||||
=== Filter Array Memory Corruption Tests ===
|
||||
Testing Too many filters (50)... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
|
||||
=== Concurrent Access Memory Tests ===
|
||||
Testing Concurrent subscription creation... ["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
|
||||
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
|
||||
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
|
||||
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
|
||||
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
|
||||
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
|
||||
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
|
||||
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
|
||||
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
|
||||
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
|
||||
[0;32mPASSED[0m - Concurrent access handled safely
|
||||
Testing Concurrent CLOSE operations...
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[0;32mPASSED[0m - Concurrent access handled safely
|
||||
|
||||
=== Malformed JSON Memory Tests ===
|
||||
Testing Unclosed JSON object... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Mismatched brackets... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Extra closing brackets... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Null bytes in JSON... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
|
||||
=== Large Message Memory Tests ===
|
||||
Testing Very large filter array... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Very long search term... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
|
||||
=== Test Results ===
|
||||
Total tests: 17
|
||||
Passed: [0;32m17[0m
|
||||
Failed: [0;31m0[0m
|
||||
[0;32m✓ All memory corruption tests passed![0m
|
||||
The relay appears to handle memory safely.
|
||||
2026-04-01 09:57:40 - \033[0;32m✓ Memory Corruption Tests PASSED\033[0m (Duration: 1s)
|
||||
2026-04-01 09:57:40 - ==========================================
|
||||
2026-04-01 09:57:40 - Running Test Suite: Input Validation Tests
|
||||
2026-04-01 09:57:40 - Description: Comprehensive input boundary testing
|
||||
2026-04-01 09:57:40 - ==========================================
|
||||
==========================================
|
||||
C-Relay Input Validation Test Suite
|
||||
==========================================
|
||||
Testing against relay at ws://127.0.0.1:8888
|
||||
|
||||
=== Basic Connectivity Test ===
|
||||
Testing Basic connectivity... [0;32mPASSED[0m - Input accepted correctly
|
||||
|
||||
=== Message Type Validation ===
|
||||
Testing Invalid message type - string... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Invalid message type - number... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Invalid message type - null... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Invalid message type - object... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Empty message type... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Very long message type... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Message Structure Validation ===
|
||||
Testing Too few arguments... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Too many arguments... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Non-array message... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Empty array... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Nested arrays incorrectly... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Subscription ID Boundary Tests ===
|
||||
Testing Valid subscription ID... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Empty subscription ID... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Subscription ID with spaces... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Subscription ID with newlines... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Subscription ID with tabs... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Subscription ID with control chars... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Unicode subscription ID... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Very long subscription ID... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Filter Object Validation ===
|
||||
Testing Valid empty filter... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Non-object filter... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Null filter... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Array filter... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Filter with invalid keys... [0;32mPASSED[0m - Input accepted correctly
|
||||
|
||||
=== Authors Field Validation ===
|
||||
Testing Valid authors array... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Empty authors array... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Non-array authors... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Invalid hex in authors... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Short pubkey in authors... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== IDs Field Validation ===
|
||||
Testing Valid ids array... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Empty ids array... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Non-array ids... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Kinds Field Validation ===
|
||||
Testing Valid kinds array... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Empty kinds array... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Non-array kinds... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing String in kinds... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Timestamp Field Validation ===
|
||||
Testing Valid since timestamp... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Valid until timestamp... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing String since timestamp... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Negative timestamp... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Limit Field Validation ===
|
||||
Testing Valid limit... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Zero limit... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing String limit... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Negative limit... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Multiple Filters ===
|
||||
Testing Two valid filters... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Many filters... [0;32mPASSED[0m - Input accepted correctly
|
||||
|
||||
=== Test Results ===
|
||||
Total tests: 47
|
||||
Passed: 47
|
||||
Failed: 0
|
||||
[0;32m✓ All input validation tests passed![0m
|
||||
The relay properly validates input.
|
||||
2026-04-01 09:57:41 - \033[0;32m✓ Input Validation Tests PASSED\033[0m (Duration: 1s)
|
||||
2026-04-01 09:57:41 -
|
||||
2026-04-01 09:57:41 - \033[0;34m=== PERFORMANCE TEST SUITES ===\033[0m
|
||||
2026-04-01 09:57:41 - ==========================================
|
||||
2026-04-01 09:57:41 - Running Test Suite: Subscription Limit Tests
|
||||
2026-04-01 09:57:41 - Description: Subscription limit enforcement testing
|
||||
2026-04-01 09:57:41 - ==========================================
|
||||
=== Subscription Limit Test ===
|
||||
[INFO] Testing relay at: ws://127.0.0.1:8888
|
||||
[INFO] Note: This test assumes default subscription limits (max 25 per client)
|
||||
|
||||
=== Test 1: Basic Connectivity ===
|
||||
[INFO] Testing basic WebSocket connection...
|
||||
[PASS] Basic connectivity works
|
||||
|
||||
=== Test 2: Subscription Limit Enforcement ===
|
||||
[INFO] Testing subscription limits by creating multiple subscriptions...
|
||||
[INFO] Creating multiple subscriptions within a single connection...
|
||||
[INFO] Hit subscription limit at subscription 2
|
||||
[PASS] Subscription limit enforcement working (limit hit after 1 subscriptions)
|
||||
|
||||
=== Test Complete ===
|
||||
2026-04-01 09:57:42 - \033[0;32m✓ Subscription Limit Tests PASSED\033[0m (Duration: 1s)
|
||||
2026-04-01 09:57:42 - ==========================================
|
||||
2026-04-01 09:57:42 - Running Test Suite: Load Testing
|
||||
2026-04-01 09:57:42 - Description: High concurrent connection testing
|
||||
2026-04-01 09:57:42 - ==========================================
|
||||
==========================================
|
||||
C-Relay Load Testing Suite
|
||||
==========================================
|
||||
Testing against relay at ws://127.0.0.1:8888
|
||||
|
||||
=== Basic Connectivity Test ===
|
||||
[0;32m✓ Relay is accessible[0m
|
||||
|
||||
==========================================
|
||||
Load Test: Light Load Test
|
||||
Description: Basic load test with moderate concurrent connections
|
||||
Concurrent clients: 10
|
||||
Messages per client: 5
|
||||
==========================================
|
||||
Launching 10 clients...
|
||||
All clients completed. Processing results...
|
||||
|
||||
=== Load Test Results ===
|
||||
Test duration: 1s
|
||||
Total connections attempted: 10
|
||||
Successful connections: 10
|
||||
Failed connections: 0
|
||||
Connection success rate: 100%
|
||||
Messages expected: 50
|
||||
Messages sent: 50
|
||||
Messages received: 100
|
||||
[0;32m✓ EXCELLENT: High connection success rate[0m
|
||||
|
||||
Checking relay responsiveness... [0;32m✓ Relay is still responsive[0m
|
||||
|
||||
==========================================
|
||||
Load Test: Medium Load Test
|
||||
Description: Moderate load test with higher concurrency
|
||||
Concurrent clients: 25
|
||||
Messages per client: 10
|
||||
==========================================
|
||||
Launching 25 clients...
|
||||
All clients completed. Processing results...
|
||||
|
||||
=== Load Test Results ===
|
||||
Test duration: 4s
|
||||
Total connections attempted: 35
|
||||
Successful connections: 25
|
||||
Failed connections: 0
|
||||
Connection success rate: 71%
|
||||
Messages expected: 250
|
||||
Messages sent: 250
|
||||
Messages received: 500
|
||||
[0;31m✗ POOR: Low connection success rate[0m
|
||||
|
||||
Checking relay responsiveness... [0;32m✓ Relay is still responsive[0m
|
||||
|
||||
==========================================
|
||||
Load Test: Heavy Load Test
|
||||
Description: Heavy load test with high concurrency
|
||||
Concurrent clients: 50
|
||||
Messages per client: 20
|
||||
==========================================
|
||||
Launching 50 clients...
|
||||
All clients completed. Processing results...
|
||||
|
||||
=== Load Test Results ===
|
||||
Test duration: 15s
|
||||
Total connections attempted: 85
|
||||
Successful connections: 50
|
||||
Failed connections: 0
|
||||
Connection success rate: 58%
|
||||
Messages expected: 1000
|
||||
Messages sent: 1000
|
||||
Messages received: 2000
|
||||
[0;31m✗ POOR: Low connection success rate[0m
|
||||
|
||||
Checking relay responsiveness... [0;32m✓ Relay is still responsive[0m
|
||||
|
||||
==========================================
|
||||
Load Test: Stress Test
|
||||
Description: Maximum load test to find breaking point
|
||||
Concurrent clients: 100
|
||||
Messages per client: 50
|
||||
==========================================
|
||||
Launching 100 clients...
|
||||
All clients completed. Processing results...
|
||||
|
||||
=== Load Test Results ===
|
||||
Test duration: 64s
|
||||
Total connections attempted: 185
|
||||
Successful connections: 100
|
||||
Failed connections: 0
|
||||
Connection success rate: 54%
|
||||
Messages expected: 5000
|
||||
Messages sent: 5000
|
||||
Messages received: 7500
|
||||
[0;31m✗ POOR: Low connection success rate[0m
|
||||
|
||||
Checking relay responsiveness... [0;32m✓ Relay is still responsive[0m
|
||||
|
||||
==========================================
|
||||
Load Testing Complete
|
||||
==========================================
|
||||
All load tests completed. Check individual test results above.
|
||||
If any tests failed, the relay may need optimization or have resource limits.
|
||||
2026-04-01 09:59:07 - \033[0;32m✓ Load Testing PASSED\033[0m (Duration: 85s)
|
||||
2026-04-01 09:59:07 - ==========================================
|
||||
2026-04-01 09:59:07 - Running Test Suite: Stress Testing
|
||||
2026-04-01 09:59:07 - Description: Resource usage and stability testing
|
||||
2026-04-01 09:59:07 - ==========================================
|
||||
2026-04-01 09:59:07 - \033[0;31mERROR: Test script stress_tests.sh not found\033[0m
|
||||
@@ -0,0 +1,733 @@
|
||||
2026-04-01 11:02:00 - ==========================================
|
||||
2026-04-01 11:02:00 - C-Relay Comprehensive Test Suite Runner
|
||||
2026-04-01 11:02:00 - ==========================================
|
||||
2026-04-01 11:02:00 - Relay URL: ws://127.0.0.1:8888
|
||||
2026-04-01 11:02:00 - Log file: test_results_20260401_110200.log
|
||||
2026-04-01 11:02:00 - Report file: test_report_20260401_110200.html
|
||||
2026-04-01 11:02:00 -
|
||||
2026-04-01 11:02:00 - Checking relay status at ws://127.0.0.1:8888...
|
||||
2026-04-01 11:02:00 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m
|
||||
2026-04-01 11:02:00 -
|
||||
2026-04-01 11:02:00 - Starting comprehensive test execution...
|
||||
2026-04-01 11:02:00 -
|
||||
2026-04-01 11:02:00 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m
|
||||
2026-04-01 11:02:00 - ==========================================
|
||||
2026-04-01 11:02:00 - Running Test Suite: SQL Injection Tests
|
||||
2026-04-01 11:02:00 - Description: Comprehensive SQL injection vulnerability testing
|
||||
2026-04-01 11:02:00 - ==========================================
|
||||
==========================================
|
||||
C-Relay SQL Injection Test Suite
|
||||
==========================================
|
||||
Testing against relay at ws://127.0.0.1:8888
|
||||
|
||||
=== Basic Connectivity Test ===
|
||||
Testing Basic connectivity... [0;32mPASSED[0m - Valid query works
|
||||
|
||||
=== Authors Filter SQL Injection Tests ===
|
||||
Testing Authors filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== IDs Filter SQL Injection Tests ===
|
||||
Testing IDs filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Kinds Filter SQL Injection Tests ===
|
||||
Testing Kinds filter with string injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Kinds filter with negative value... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Kinds filter with very large value... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Search Filter SQL Injection Tests ===
|
||||
Testing Search filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing Search filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing Search filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing Search filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing Search filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Tag Filter SQL Injection Tests ===
|
||||
Testing #e tag filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
|
||||
=== Timestamp Filter SQL Injection Tests ===
|
||||
Testing Since parameter injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Until parameter injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Limit Parameter SQL Injection Tests ===
|
||||
Testing Limit parameter injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Limit with UNION... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Complex Multi-Filter SQL Injection Tests ===
|
||||
Testing Multi-filter with authors injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Multi-filter with search injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Multi-filter with tag injection... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
|
||||
=== COUNT Message SQL Injection Tests ===
|
||||
Testing COUNT with authors payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing COUNT with authors payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: /*... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: /*... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: */... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: */... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: /**/... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: /**/... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: #... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing COUNT with authors payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing COUNT with authors payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing COUNT with authors payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Edge Case SQL Injection Tests ===
|
||||
Testing Empty string injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Null byte injection... [0;32mPASSED[0m - SQL injection blocked (silently rejected)
|
||||
Testing Unicode injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Very long injection payload... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Subscription ID SQL Injection Tests ===
|
||||
Testing Subscription ID injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Subscription ID with quotes... [0;32mPASSED[0m - SQL injection blocked (silently rejected)
|
||||
|
||||
=== CLOSE Message SQL Injection Tests ===
|
||||
Testing CLOSE with injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Test Results ===
|
||||
Total tests: 318
|
||||
Passed: [0;32m318[0m
|
||||
Failed: [0;31m0[0m
|
||||
[0;32m✓ All SQL injection tests passed![0m
|
||||
The relay appears to be protected against SQL injection attacks.
|
||||
2026-04-01 11:02:07 - \033[0;32m✓ SQL Injection Tests PASSED\033[0m (Duration: 7s)
|
||||
2026-04-01 11:02:07 - ==========================================
|
||||
2026-04-01 11:02:07 - Running Test Suite: Filter Validation Tests
|
||||
2026-04-01 11:02:07 - Description: Input validation for REQ and COUNT messages
|
||||
2026-04-01 11:02:07 - ==========================================
|
||||
=== C-Relay Filter Validation Tests ===
|
||||
Testing against relay at ws://127.0.0.1:8888
|
||||
|
||||
Testing Valid REQ message... [0;32mPASSED[0m
|
||||
Testing Valid COUNT message... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Filter Array Validation ===
|
||||
Testing Non-object filter... [0;32mPASSED[0m
|
||||
Testing Too many filters... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Authors Validation ===
|
||||
Testing Invalid author type... [0;32mPASSED[0m
|
||||
Testing Invalid author hex... [0;32mPASSED[0m
|
||||
Testing Too many authors... [0;32mPASSED[0m
|
||||
|
||||
=== Testing IDs Validation ===
|
||||
Testing Invalid ID type... [0;32mPASSED[0m
|
||||
Testing Invalid ID hex... [0;32mPASSED[0m
|
||||
Testing Too many IDs... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Kinds Validation ===
|
||||
Testing Invalid kind type... [0;32mPASSED[0m
|
||||
Testing Negative kind... [0;32mPASSED[0m
|
||||
Testing Too large kind... [0;32mPASSED[0m
|
||||
Testing Too many kinds... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Timestamp Validation ===
|
||||
Testing Invalid since type... [0;32mPASSED[0m
|
||||
Testing Negative since... [0;32mPASSED[0m
|
||||
Testing Invalid until type... [0;32mPASSED[0m
|
||||
Testing Negative until... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Limit Validation ===
|
||||
Testing Invalid limit type... [0;32mPASSED[0m
|
||||
Testing Negative limit... [0;32mPASSED[0m
|
||||
Testing Too large limit... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Search Validation ===
|
||||
Testing Invalid search type... [0;32mPASSED[0m
|
||||
Testing Search too long... [0;32mPASSED[0m
|
||||
Testing Search SQL injection... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Tag Filter Validation ===
|
||||
Testing Invalid tag filter type... [0;32mPASSED[0m
|
||||
Testing Too many tag values... [0;32mPASSED[0m
|
||||
Testing Tag value too long... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Rate Limiting ===
|
||||
Testing rate limiting with malformed requests... [1;33mUNCERTAIN[0m - Rate limiting may not have triggered (this could be normal)
|
||||
|
||||
=== Test Results ===
|
||||
Total tests: 28
|
||||
Passed: [0;32m28[0m
|
||||
Failed: [0;31m0[0m
|
||||
[0;32mAll tests passed![0m
|
||||
2026-04-01 11:02:10 - \033[0;32m✓ Filter Validation Tests PASSED\033[0m (Duration: 3s)
|
||||
2026-04-01 11:02:10 - ==========================================
|
||||
2026-04-01 11:02:10 - Running Test Suite: Subscription Validation Tests
|
||||
2026-04-01 11:02:10 - Description: Subscription ID and message validation
|
||||
2026-04-01 11:02:10 - ==========================================
|
||||
Testing subscription ID validation fixes...
|
||||
Testing malformed subscription IDs...
|
||||
Empty ID test: Connection failed (expected)
|
||||
Long ID test: Connection failed (expected)
|
||||
Invalid chars test: Connection failed (expected)
|
||||
NULL ID test: Connection failed (expected)
|
||||
Valid ID test: Failed
|
||||
Testing CLOSE message validation...
|
||||
CLOSE empty ID test: Connection failed (expected)
|
||||
CLOSE valid ID test: Failed
|
||||
Subscription validation tests completed.
|
||||
2026-04-01 11:02:10 - \033[0;32m✓ Subscription Validation Tests PASSED\033[0m (Duration: 0s)
|
||||
2026-04-01 11:02:10 - ==========================================
|
||||
2026-04-01 11:02:10 - Running Test Suite: Memory Corruption Tests
|
||||
2026-04-01 11:02:10 - Description: Buffer overflow and memory safety testing
|
||||
2026-04-01 11:02:10 - ==========================================
|
||||
==========================================
|
||||
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... [0;32mPASSED[0m - No memory corruption detected
|
||||
|
||||
=== Subscription ID Memory Corruption Tests ===
|
||||
Testing Empty subscription ID... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Very long subscription ID (1KB)... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Very long subscription ID (10KB)... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Subscription ID with null bytes... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Subscription ID with special chars... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Unicode subscription ID... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Subscription ID with path traversal... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
|
||||
=== Filter Array Memory Corruption Tests ===
|
||||
Testing Too many filters (50)... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
|
||||
=== Concurrent Access Memory Tests ===
|
||||
Testing Concurrent subscription creation... ["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}]
|
||||
["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}]
|
||||
["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}]
|
||||
["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}]
|
||||
["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}]
|
||||
["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}]
|
||||
["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}]
|
||||
["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}]
|
||||
["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}]
|
||||
["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}]
|
||||
[0;32mPASSED[0m - Concurrent access handled safely
|
||||
Testing Concurrent CLOSE operations...
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[0;32mPASSED[0m - Concurrent access handled safely
|
||||
|
||||
=== Malformed JSON Memory Tests ===
|
||||
Testing Unclosed JSON object... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Mismatched brackets... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Extra closing brackets... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Null bytes in JSON... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
|
||||
=== Large Message Memory Tests ===
|
||||
Testing Very large filter array... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Very long search term... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
|
||||
=== Test Results ===
|
||||
Total tests: 17
|
||||
Passed: [0;32m17[0m
|
||||
Failed: [0;31m0[0m
|
||||
[0;32m✓ All memory corruption tests passed![0m
|
||||
The relay appears to handle memory safely.
|
||||
2026-04-01 11:02:11 - \033[0;32m✓ Memory Corruption Tests PASSED\033[0m (Duration: 1s)
|
||||
2026-04-01 11:02:11 - ==========================================
|
||||
2026-04-01 11:02:11 - Running Test Suite: Input Validation Tests
|
||||
2026-04-01 11:02:11 - Description: Comprehensive input boundary testing
|
||||
2026-04-01 11:02:11 - ==========================================
|
||||
==========================================
|
||||
C-Relay Input Validation Test Suite
|
||||
==========================================
|
||||
Testing against relay at ws://127.0.0.1:8888
|
||||
|
||||
=== Basic Connectivity Test ===
|
||||
Testing Basic connectivity... [0;32mPASSED[0m - Input accepted correctly
|
||||
|
||||
=== Message Type Validation ===
|
||||
Testing Invalid message type - string... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Invalid message type - number... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Invalid message type - null... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Invalid message type - object... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Empty message type... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Very long message type... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Message Structure Validation ===
|
||||
Testing Too few arguments... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Too many arguments... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Non-array message... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Empty array... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Nested arrays incorrectly... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Subscription ID Boundary Tests ===
|
||||
Testing Valid subscription ID... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Empty subscription ID... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Subscription ID with spaces... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Subscription ID with newlines... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Subscription ID with tabs... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Subscription ID with control chars... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Unicode subscription ID... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Very long subscription ID... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Filter Object Validation ===
|
||||
Testing Valid empty filter... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Non-object filter... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Null filter... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Array filter... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Filter with invalid keys... [0;32mPASSED[0m - Input accepted correctly
|
||||
|
||||
=== Authors Field Validation ===
|
||||
Testing Valid authors array... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Empty authors array... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Non-array authors... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Invalid hex in authors... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Short pubkey in authors... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== IDs Field Validation ===
|
||||
Testing Valid ids array... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Empty ids array... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Non-array ids... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Kinds Field Validation ===
|
||||
Testing Valid kinds array... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Empty kinds array... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Non-array kinds... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing String in kinds... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Timestamp Field Validation ===
|
||||
Testing Valid since timestamp... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Valid until timestamp... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing String since timestamp... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Negative timestamp... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Limit Field Validation ===
|
||||
Testing Valid limit... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Zero limit... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing String limit... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Negative limit... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Multiple Filters ===
|
||||
Testing Two valid filters... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Many filters... [0;32mPASSED[0m - Input accepted correctly
|
||||
|
||||
=== Test Results ===
|
||||
Total tests: 47
|
||||
Passed: 47
|
||||
Failed: 0
|
||||
[0;32m✓ All input validation tests passed![0m
|
||||
The relay properly validates input.
|
||||
2026-04-01 11:02:12 - \033[0;32m✓ Input Validation Tests PASSED\033[0m (Duration: 1s)
|
||||
2026-04-01 11:02:12 -
|
||||
2026-04-01 11:02:12 - \033[0;34m=== PERFORMANCE TEST SUITES ===\033[0m
|
||||
2026-04-01 11:02:12 - ==========================================
|
||||
2026-04-01 11:02:12 - Running Test Suite: Subscription Limit Tests
|
||||
2026-04-01 11:02:12 - Description: Subscription limit enforcement testing
|
||||
2026-04-01 11:02:12 - ==========================================
|
||||
=== Subscription Limit Test ===
|
||||
[INFO] Testing relay at: ws://127.0.0.1:8888
|
||||
[INFO] Note: This test assumes default subscription limits (max 25 per client)
|
||||
|
||||
=== Test 1: Basic Connectivity ===
|
||||
[INFO] Testing basic WebSocket connection...
|
||||
[PASS] Basic connectivity works
|
||||
|
||||
=== Test 2: Subscription Limit Enforcement ===
|
||||
[INFO] Testing subscription limits by creating multiple subscriptions...
|
||||
[INFO] Creating multiple subscriptions within a single connection...
|
||||
[INFO] Hit subscription limit at subscription 2
|
||||
[PASS] Subscription limit enforcement working (limit hit after 1 subscriptions)
|
||||
|
||||
=== Test Complete ===
|
||||
2026-04-01 11:02:12 - \033[0;32m✓ Subscription Limit Tests PASSED\033[0m (Duration: 0s)
|
||||
2026-04-01 11:02:12 - ==========================================
|
||||
2026-04-01 11:02:12 - Running Test Suite: Load Testing
|
||||
2026-04-01 11:02:12 - Description: High concurrent connection testing
|
||||
2026-04-01 11:02:12 - ==========================================
|
||||
==========================================
|
||||
C-Relay Load Testing Suite
|
||||
==========================================
|
||||
Testing against relay at ws://127.0.0.1:8888
|
||||
|
||||
=== Basic Connectivity Test ===
|
||||
[0;32m✓ Relay is accessible[0m
|
||||
|
||||
==========================================
|
||||
Load Test: Light Load Test
|
||||
Description: Basic load test with moderate concurrent connections
|
||||
Concurrent clients: 10
|
||||
Messages per client: 5
|
||||
==========================================
|
||||
Launching 10 clients...
|
||||
All clients completed. Processing results...
|
||||
|
||||
=== Load Test Results ===
|
||||
Test duration: 1s
|
||||
Total connections attempted: 10
|
||||
Successful connections: 10
|
||||
Failed connections: 0
|
||||
Connection success rate: 100%
|
||||
Messages expected: 50
|
||||
Messages sent: 50
|
||||
Messages received: 100
|
||||
[0;32m✓ EXCELLENT: High connection success rate[0m
|
||||
|
||||
Checking relay responsiveness... [0;32m✓ Relay is still responsive[0m
|
||||
|
||||
==========================================
|
||||
Load Test: Medium Load Test
|
||||
Description: Moderate load test with higher concurrency
|
||||
Concurrent clients: 25
|
||||
Messages per client: 10
|
||||
==========================================
|
||||
Launching 25 clients...
|
||||
All clients completed. Processing results...
|
||||
|
||||
=== Load Test Results ===
|
||||
Test duration: 4s
|
||||
Total connections attempted: 35
|
||||
Successful connections: 25
|
||||
Failed connections: 0
|
||||
Connection success rate: 71%
|
||||
Messages expected: 250
|
||||
Messages sent: 250
|
||||
Messages received: 500
|
||||
[0;31m✗ POOR: Low connection success rate[0m
|
||||
|
||||
Checking relay responsiveness... [0;32m✓ Relay is still responsive[0m
|
||||
|
||||
==========================================
|
||||
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: 14s
|
||||
Total connections attempted: 85
|
||||
Successful connections: 50
|
||||
Failed connections: 0
|
||||
Connection success rate: 58%
|
||||
Messages expected: 1000
|
||||
Messages sent: 1000
|
||||
Messages received: 2000
|
||||
[0;31m✗ POOR: Low connection success rate[0m
|
||||
|
||||
Checking relay responsiveness... [0;32m✓ Relay is still responsive[0m
|
||||
|
||||
==========================================
|
||||
Load Test: Stress Test
|
||||
Description: Maximum load test to find breaking point
|
||||
Concurrent clients: 100
|
||||
Messages per client: 50
|
||||
==========================================
|
||||
Launching 100 clients...
|
||||
All clients completed. Processing results...
|
||||
|
||||
=== Load Test Results ===
|
||||
Test duration: 64s
|
||||
Total connections attempted: 185
|
||||
Successful connections: 100
|
||||
Failed connections: 0
|
||||
Connection success rate: 54%
|
||||
Messages expected: 5000
|
||||
Messages sent: 5000
|
||||
Messages received: 7500
|
||||
[0;31m✗ POOR: Low connection success rate[0m
|
||||
|
||||
Checking relay responsiveness... [0;32m✓ Relay is still responsive[0m
|
||||
|
||||
==========================================
|
||||
Load Testing Complete
|
||||
==========================================
|
||||
All load tests completed. Check individual test results above.
|
||||
If any tests failed, the relay may need optimization or have resource limits.
|
||||
2026-04-01 11:03:37 - \033[0;32m✓ Load Testing PASSED\033[0m (Duration: 85s)
|
||||
2026-04-01 11:03:37 - ==========================================
|
||||
2026-04-01 11:03:37 - Running Test Suite: Stress Testing
|
||||
2026-04-01 11:03:37 - Description: Resource usage and stability testing
|
||||
2026-04-01 11:03:37 - ==========================================
|
||||
2026-04-01 11:03:37 - \033[0;31mERROR: Test script stress_tests.sh not found\033[0m
|
||||
@@ -0,0 +1,733 @@
|
||||
2026-04-01 11:29:24 - ==========================================
|
||||
2026-04-01 11:29:24 - C-Relay Comprehensive Test Suite Runner
|
||||
2026-04-01 11:29:24 - ==========================================
|
||||
2026-04-01 11:29:24 - Relay URL: ws://127.0.0.1:8888
|
||||
2026-04-01 11:29:24 - Log file: test_results_20260401_112924.log
|
||||
2026-04-01 11:29:24 - Report file: test_report_20260401_112924.html
|
||||
2026-04-01 11:29:24 -
|
||||
2026-04-01 11:29:24 - Checking relay status at ws://127.0.0.1:8888...
|
||||
2026-04-01 11:29:25 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m
|
||||
2026-04-01 11:29:25 -
|
||||
2026-04-01 11:29:25 - Starting comprehensive test execution...
|
||||
2026-04-01 11:29:25 -
|
||||
2026-04-01 11:29:25 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m
|
||||
2026-04-01 11:29:25 - ==========================================
|
||||
2026-04-01 11:29:25 - Running Test Suite: SQL Injection Tests
|
||||
2026-04-01 11:29:25 - Description: Comprehensive SQL injection vulnerability testing
|
||||
2026-04-01 11:29:25 - ==========================================
|
||||
==========================================
|
||||
C-Relay SQL Injection Test Suite
|
||||
==========================================
|
||||
Testing against relay at ws://127.0.0.1:8888
|
||||
|
||||
=== Basic Connectivity Test ===
|
||||
Testing Basic connectivity... [0;32mPASSED[0m - Valid query works
|
||||
|
||||
=== Authors Filter SQL Injection Tests ===
|
||||
Testing Authors filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Authors filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== IDs Filter SQL Injection Tests ===
|
||||
Testing IDs filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing IDs filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Kinds Filter SQL Injection Tests ===
|
||||
Testing Kinds filter with string injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Kinds filter with negative value... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Kinds filter with very large value... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Search Filter SQL Injection Tests ===
|
||||
Testing Search filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing Search filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing Search filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing Search filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing Search filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Search filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Tag Filter SQL Injection Tests ===
|
||||
Testing #e tag filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #e tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #p tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #t tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #r tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: /*... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: */... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: /**/... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing #d tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
|
||||
=== Timestamp Filter SQL Injection Tests ===
|
||||
Testing Since parameter injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Until parameter injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Limit Parameter SQL Injection Tests ===
|
||||
Testing Limit parameter injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Limit with UNION... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Complex Multi-Filter SQL Injection Tests ===
|
||||
Testing Multi-filter with authors injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Multi-filter with search injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Multi-filter with tag injection... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
|
||||
=== COUNT Message SQL Injection Tests ===
|
||||
Testing COUNT with authors payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '; DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' OR '1'='1... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing COUNT with authors payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1; SELECT * FROM sqlite_master; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' OR 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: admin'--... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' UNION SELECT password FROM users --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' AND SLEEP(5) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' AND 1=CAST((SELECT version()) AS INT) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' UNION SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' UNION SELECT 1,2,3 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' UNION ALL SELECT NULL,NULL,NULL --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '; SELECT * FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '; DELETE FROM events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: /*... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: /*... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: */... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: */... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: /**/... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: /**/... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: #... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: #... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing COUNT with authors payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: 0x53514C5F494E4A454354494F4E... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing COUNT with authors payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: J1NSTCBJTkpFQ1RJT04gLS0=... [0;32mPASSED[0m - SQL injection blocked (query sanitized)
|
||||
Testing COUNT with authors payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '))); DROP TABLE events; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ')) UNION SELECT NULL; --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' AND 1=1 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' AND 1=2 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with authors payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing COUNT with search payload: '; EXEC master..xp_cmdshell 'net user' --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - 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'); --... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Edge Case SQL Injection Tests ===
|
||||
Testing Empty string injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Null byte injection... [0;32mPASSED[0m - SQL injection blocked (silently rejected)
|
||||
Testing Unicode injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Very long injection payload... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Subscription ID SQL Injection Tests ===
|
||||
Testing Subscription ID injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
Testing Subscription ID with quotes... [0;32mPASSED[0m - SQL injection blocked (silently rejected)
|
||||
|
||||
=== CLOSE Message SQL Injection Tests ===
|
||||
Testing CLOSE with injection... [0;32mPASSED[0m - SQL injection blocked (rejected with error)
|
||||
|
||||
=== Test Results ===
|
||||
Total tests: 318
|
||||
Passed: [0;32m318[0m
|
||||
Failed: [0;31m0[0m
|
||||
[0;32m✓ All SQL injection tests passed![0m
|
||||
The relay appears to be protected against SQL injection attacks.
|
||||
2026-04-01 11:29:32 - \033[0;32m✓ SQL Injection Tests PASSED\033[0m (Duration: 7s)
|
||||
2026-04-01 11:29:32 - ==========================================
|
||||
2026-04-01 11:29:32 - Running Test Suite: Filter Validation Tests
|
||||
2026-04-01 11:29:32 - Description: Input validation for REQ and COUNT messages
|
||||
2026-04-01 11:29:32 - ==========================================
|
||||
=== C-Relay Filter Validation Tests ===
|
||||
Testing against relay at ws://127.0.0.1:8888
|
||||
|
||||
Testing Valid REQ message... [0;32mPASSED[0m
|
||||
Testing Valid COUNT message... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Filter Array Validation ===
|
||||
Testing Non-object filter... [0;32mPASSED[0m
|
||||
Testing Too many filters... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Authors Validation ===
|
||||
Testing Invalid author type... [0;32mPASSED[0m
|
||||
Testing Invalid author hex... [0;32mPASSED[0m
|
||||
Testing Too many authors... [0;32mPASSED[0m
|
||||
|
||||
=== Testing IDs Validation ===
|
||||
Testing Invalid ID type... [0;32mPASSED[0m
|
||||
Testing Invalid ID hex... [0;32mPASSED[0m
|
||||
Testing Too many IDs... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Kinds Validation ===
|
||||
Testing Invalid kind type... [0;32mPASSED[0m
|
||||
Testing Negative kind... [0;32mPASSED[0m
|
||||
Testing Too large kind... [0;32mPASSED[0m
|
||||
Testing Too many kinds... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Timestamp Validation ===
|
||||
Testing Invalid since type... [0;32mPASSED[0m
|
||||
Testing Negative since... [0;32mPASSED[0m
|
||||
Testing Invalid until type... [0;32mPASSED[0m
|
||||
Testing Negative until... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Limit Validation ===
|
||||
Testing Invalid limit type... [0;32mPASSED[0m
|
||||
Testing Negative limit... [0;32mPASSED[0m
|
||||
Testing Too large limit... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Search Validation ===
|
||||
Testing Invalid search type... [0;32mPASSED[0m
|
||||
Testing Search too long... [0;32mPASSED[0m
|
||||
Testing Search SQL injection... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Tag Filter Validation ===
|
||||
Testing Invalid tag filter type... [0;32mPASSED[0m
|
||||
Testing Too many tag values... [0;32mPASSED[0m
|
||||
Testing Tag value too long... [0;32mPASSED[0m
|
||||
|
||||
=== Testing Rate Limiting ===
|
||||
Testing rate limiting with malformed requests... [1;33mUNCERTAIN[0m - Rate limiting may not have triggered (this could be normal)
|
||||
|
||||
=== Test Results ===
|
||||
Total tests: 28
|
||||
Passed: [0;32m28[0m
|
||||
Failed: [0;31m0[0m
|
||||
[0;32mAll tests passed![0m
|
||||
2026-04-01 11:29:35 - \033[0;32m✓ Filter Validation Tests PASSED\033[0m (Duration: 3s)
|
||||
2026-04-01 11:29:35 - ==========================================
|
||||
2026-04-01 11:29:35 - Running Test Suite: Subscription Validation Tests
|
||||
2026-04-01 11:29:35 - Description: Subscription ID and message validation
|
||||
2026-04-01 11:29:35 - ==========================================
|
||||
Testing subscription ID validation fixes...
|
||||
Testing malformed subscription IDs...
|
||||
Empty ID test: Connection failed (expected)
|
||||
Long ID test: Connection failed (expected)
|
||||
Invalid chars test: Connection failed (expected)
|
||||
NULL ID test: Connection failed (expected)
|
||||
Valid ID test: Failed
|
||||
Testing CLOSE message validation...
|
||||
CLOSE empty ID test: Connection failed (expected)
|
||||
CLOSE valid ID test: Failed
|
||||
Subscription validation tests completed.
|
||||
2026-04-01 11:29:35 - \033[0;32m✓ Subscription Validation Tests PASSED\033[0m (Duration: 0s)
|
||||
2026-04-01 11:29:35 - ==========================================
|
||||
2026-04-01 11:29:35 - Running Test Suite: Memory Corruption Tests
|
||||
2026-04-01 11:29:35 - Description: Buffer overflow and memory safety testing
|
||||
2026-04-01 11:29:35 - ==========================================
|
||||
==========================================
|
||||
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... [0;32mPASSED[0m - No memory corruption detected
|
||||
|
||||
=== Subscription ID Memory Corruption Tests ===
|
||||
Testing Empty subscription ID... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Very long subscription ID (1KB)... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Very long subscription ID (10KB)... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Subscription ID with null bytes... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Subscription ID with special chars... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Unicode subscription ID... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Subscription ID with path traversal... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
|
||||
=== Filter Array Memory Corruption Tests ===
|
||||
Testing Too many filters (50)... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
|
||||
=== Concurrent Access Memory Tests ===
|
||||
Testing Concurrent subscription creation... ["EVENT","concurrent_1775057376377657551",{"pubkey":"7b013abdf0cbc8f994e66018539fbcb750f3be118a70d8bdcf3b3e86dd2ebb40","created_at":1775057288,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"210f21884a3581064f4d181bc10e4088b5161216ddc7088f5c39e570240687b3","sig":"a04a697e6b3a53a1df4e3d7d06da2483f07d45c97a939076bd8b28216425a854af5f7e463542adbed9b71af68d36d7e0296be0048c3156a51ed0defa685a5171"}]
|
||||
["EVENT","concurrent_1775057376377657551",{"pubkey":"7b013abdf0cbc8f994e66018539fbcb750f3be118a70d8bdcf3b3e86dd2ebb40","created_at":1775057288,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"210f21884a3581064f4d181bc10e4088b5161216ddc7088f5c39e570240687b3","sig":"a04a697e6b3a53a1df4e3d7d06da2483f07d45c97a939076bd8b28216425a854af5f7e463542adbed9b71af68d36d7e0296be0048c3156a51ed0defa685a5171"}]
|
||||
["EVENT","concurrent_1775057376377657551",{"pubkey":"7b013abdf0cbc8f994e66018539fbcb750f3be118a70d8bdcf3b3e86dd2ebb40","created_at":1775057288,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"210f21884a3581064f4d181bc10e4088b5161216ddc7088f5c39e570240687b3","sig":"a04a697e6b3a53a1df4e3d7d06da2483f07d45c97a939076bd8b28216425a854af5f7e463542adbed9b71af68d36d7e0296be0048c3156a51ed0defa685a5171"}]
|
||||
["EVENT","concurrent_1775057376377657551",{"pubkey":"7b013abdf0cbc8f994e66018539fbcb750f3be118a70d8bdcf3b3e86dd2ebb40","created_at":1775057288,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"210f21884a3581064f4d181bc10e4088b5161216ddc7088f5c39e570240687b3","sig":"a04a697e6b3a53a1df4e3d7d06da2483f07d45c97a939076bd8b28216425a854af5f7e463542adbed9b71af68d36d7e0296be0048c3156a51ed0defa685a5171"}]
|
||||
["EVENT","concurrent_1775057376377657551",{"pubkey":"7b013abdf0cbc8f994e66018539fbcb750f3be118a70d8bdcf3b3e86dd2ebb40","created_at":1775057288,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"210f21884a3581064f4d181bc10e4088b5161216ddc7088f5c39e570240687b3","sig":"a04a697e6b3a53a1df4e3d7d06da2483f07d45c97a939076bd8b28216425a854af5f7e463542adbed9b71af68d36d7e0296be0048c3156a51ed0defa685a5171"}]
|
||||
["EVENT","concurrent_1775057376377657551",{"pubkey":"7b013abdf0cbc8f994e66018539fbcb750f3be118a70d8bdcf3b3e86dd2ebb40","created_at":1775057288,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"210f21884a3581064f4d181bc10e4088b5161216ddc7088f5c39e570240687b3","sig":"a04a697e6b3a53a1df4e3d7d06da2483f07d45c97a939076bd8b28216425a854af5f7e463542adbed9b71af68d36d7e0296be0048c3156a51ed0defa685a5171"}]
|
||||
["EVENT","concurrent_1775057376377657551",{"pubkey":"7b013abdf0cbc8f994e66018539fbcb750f3be118a70d8bdcf3b3e86dd2ebb40","created_at":1775057288,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"210f21884a3581064f4d181bc10e4088b5161216ddc7088f5c39e570240687b3","sig":"a04a697e6b3a53a1df4e3d7d06da2483f07d45c97a939076bd8b28216425a854af5f7e463542adbed9b71af68d36d7e0296be0048c3156a51ed0defa685a5171"}]
|
||||
["EVENT","concurrent_1775057376377657551",{"pubkey":"7b013abdf0cbc8f994e66018539fbcb750f3be118a70d8bdcf3b3e86dd2ebb40","created_at":1775057288,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"210f21884a3581064f4d181bc10e4088b5161216ddc7088f5c39e570240687b3","sig":"a04a697e6b3a53a1df4e3d7d06da2483f07d45c97a939076bd8b28216425a854af5f7e463542adbed9b71af68d36d7e0296be0048c3156a51ed0defa685a5171"}]
|
||||
["EVENT","concurrent_1775057376377657551",{"pubkey":"7b013abdf0cbc8f994e66018539fbcb750f3be118a70d8bdcf3b3e86dd2ebb40","created_at":1775057288,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"210f21884a3581064f4d181bc10e4088b5161216ddc7088f5c39e570240687b3","sig":"a04a697e6b3a53a1df4e3d7d06da2483f07d45c97a939076bd8b28216425a854af5f7e463542adbed9b71af68d36d7e0296be0048c3156a51ed0defa685a5171"}]
|
||||
["EVENT","concurrent_1775057376377657551",{"pubkey":"7b013abdf0cbc8f994e66018539fbcb750f3be118a70d8bdcf3b3e86dd2ebb40","created_at":1775057288,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"210f21884a3581064f4d181bc10e4088b5161216ddc7088f5c39e570240687b3","sig":"a04a697e6b3a53a1df4e3d7d06da2483f07d45c97a939076bd8b28216425a854af5f7e463542adbed9b71af68d36d7e0296be0048c3156a51ed0defa685a5171"}]
|
||||
[0;32mPASSED[0m - Concurrent access handled safely
|
||||
Testing Concurrent CLOSE operations...
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[0;32mPASSED[0m - Concurrent access handled safely
|
||||
|
||||
=== Malformed JSON Memory Tests ===
|
||||
Testing Unclosed JSON object... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Mismatched brackets... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Extra closing brackets... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Null bytes in JSON... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
|
||||
=== Large Message Memory Tests ===
|
||||
Testing Very large filter array... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
Testing Very long search term... [1;33mUNCERTAIN[0m - Expected error but got normal response
|
||||
|
||||
=== Test Results ===
|
||||
Total tests: 17
|
||||
Passed: [0;32m17[0m
|
||||
Failed: [0;31m0[0m
|
||||
[0;32m✓ All memory corruption tests passed![0m
|
||||
The relay appears to handle memory safely.
|
||||
2026-04-01 11:29:36 - \033[0;32m✓ Memory Corruption Tests PASSED\033[0m (Duration: 1s)
|
||||
2026-04-01 11:29:36 - ==========================================
|
||||
2026-04-01 11:29:36 - Running Test Suite: Input Validation Tests
|
||||
2026-04-01 11:29:36 - Description: Comprehensive input boundary testing
|
||||
2026-04-01 11:29:36 - ==========================================
|
||||
==========================================
|
||||
C-Relay Input Validation Test Suite
|
||||
==========================================
|
||||
Testing against relay at ws://127.0.0.1:8888
|
||||
|
||||
=== Basic Connectivity Test ===
|
||||
Testing Basic connectivity... [0;32mPASSED[0m - Input accepted correctly
|
||||
|
||||
=== Message Type Validation ===
|
||||
Testing Invalid message type - string... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Invalid message type - number... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Invalid message type - null... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Invalid message type - object... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Empty message type... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Very long message type... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Message Structure Validation ===
|
||||
Testing Too few arguments... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Too many arguments... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Non-array message... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Empty array... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Nested arrays incorrectly... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Subscription ID Boundary Tests ===
|
||||
Testing Valid subscription ID... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Empty subscription ID... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Subscription ID with spaces... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Subscription ID with newlines... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Subscription ID with tabs... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Subscription ID with control chars... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Unicode subscription ID... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Very long subscription ID... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Filter Object Validation ===
|
||||
Testing Valid empty filter... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Non-object filter... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Null filter... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Array filter... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Filter with invalid keys... [0;32mPASSED[0m - Input accepted correctly
|
||||
|
||||
=== Authors Field Validation ===
|
||||
Testing Valid authors array... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Empty authors array... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Non-array authors... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Invalid hex in authors... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Short pubkey in authors... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== IDs Field Validation ===
|
||||
Testing Valid ids array... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Empty ids array... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Non-array ids... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Kinds Field Validation ===
|
||||
Testing Valid kinds array... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Empty kinds array... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Non-array kinds... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing String in kinds... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Timestamp Field Validation ===
|
||||
Testing Valid since timestamp... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Valid until timestamp... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing String since timestamp... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Negative timestamp... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Limit Field Validation ===
|
||||
Testing Valid limit... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Zero limit... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing String limit... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
Testing Negative limit... [0;32mPASSED[0m - Invalid input properly rejected
|
||||
|
||||
=== Multiple Filters ===
|
||||
Testing Two valid filters... [0;32mPASSED[0m - Input accepted correctly
|
||||
Testing Many filters... [0;32mPASSED[0m - Input accepted correctly
|
||||
|
||||
=== Test Results ===
|
||||
Total tests: 47
|
||||
Passed: 47
|
||||
Failed: 0
|
||||
[0;32m✓ All input validation tests passed![0m
|
||||
The relay properly validates input.
|
||||
2026-04-01 11:29:38 - \033[0;32m✓ Input Validation Tests PASSED\033[0m (Duration: 2s)
|
||||
2026-04-01 11:29:38 -
|
||||
2026-04-01 11:29:38 - \033[0;34m=== PERFORMANCE TEST SUITES ===\033[0m
|
||||
2026-04-01 11:29:38 - ==========================================
|
||||
2026-04-01 11:29:38 - Running Test Suite: Subscription Limit Tests
|
||||
2026-04-01 11:29:38 - Description: Subscription limit enforcement testing
|
||||
2026-04-01 11:29:38 - ==========================================
|
||||
=== Subscription Limit Test ===
|
||||
[INFO] Testing relay at: ws://127.0.0.1:8888
|
||||
[INFO] Note: This test assumes default subscription limits (max 25 per client)
|
||||
|
||||
=== Test 1: Basic Connectivity ===
|
||||
[INFO] Testing basic WebSocket connection...
|
||||
[PASS] Basic connectivity works
|
||||
|
||||
=== Test 2: Subscription Limit Enforcement ===
|
||||
[INFO] Testing subscription limits by creating multiple subscriptions...
|
||||
[INFO] Creating multiple subscriptions within a single connection...
|
||||
[INFO] Hit subscription limit at subscription 2
|
||||
[PASS] Subscription limit enforcement working (limit hit after 1 subscriptions)
|
||||
|
||||
=== Test Complete ===
|
||||
2026-04-01 11:29:38 - \033[0;32m✓ Subscription Limit Tests PASSED\033[0m (Duration: 0s)
|
||||
2026-04-01 11:29:38 - ==========================================
|
||||
2026-04-01 11:29:38 - Running Test Suite: Load Testing
|
||||
2026-04-01 11:29:38 - Description: High concurrent connection testing
|
||||
2026-04-01 11:29:38 - ==========================================
|
||||
==========================================
|
||||
C-Relay Load Testing Suite
|
||||
==========================================
|
||||
Testing against relay at ws://127.0.0.1:8888
|
||||
|
||||
=== Basic Connectivity Test ===
|
||||
[0;32m✓ Relay is accessible[0m
|
||||
|
||||
==========================================
|
||||
Load Test: Light Load Test
|
||||
Description: Basic load test with moderate concurrent connections
|
||||
Concurrent clients: 10
|
||||
Messages per client: 5
|
||||
==========================================
|
||||
Launching 10 clients...
|
||||
All clients completed. Processing results...
|
||||
|
||||
=== Load Test Results ===
|
||||
Test duration: 1s
|
||||
Total connections attempted: 10
|
||||
Successful connections: 10
|
||||
Failed connections: 0
|
||||
Connection success rate: 100%
|
||||
Messages expected: 50
|
||||
Messages sent: 50
|
||||
Messages received: 100
|
||||
[0;32m✓ EXCELLENT: High connection success rate[0m
|
||||
|
||||
Checking relay responsiveness... [0;32m✓ Relay is still responsive[0m
|
||||
|
||||
==========================================
|
||||
Load Test: Medium Load Test
|
||||
Description: Moderate load test with higher concurrency
|
||||
Concurrent clients: 25
|
||||
Messages per client: 10
|
||||
==========================================
|
||||
Launching 25 clients...
|
||||
All clients completed. Processing results...
|
||||
|
||||
=== Load Test Results ===
|
||||
Test duration: 4s
|
||||
Total connections attempted: 35
|
||||
Successful connections: 25
|
||||
Failed connections: 0
|
||||
Connection success rate: 71%
|
||||
Messages expected: 250
|
||||
Messages sent: 250
|
||||
Messages received: 500
|
||||
[0;31m✗ POOR: Low connection success rate[0m
|
||||
|
||||
Checking relay responsiveness... [0;32m✓ Relay is still responsive[0m
|
||||
|
||||
==========================================
|
||||
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: 14s
|
||||
Total connections attempted: 85
|
||||
Successful connections: 50
|
||||
Failed connections: 0
|
||||
Connection success rate: 58%
|
||||
Messages expected: 1000
|
||||
Messages sent: 1000
|
||||
Messages received: 2000
|
||||
[0;31m✗ POOR: Low connection success rate[0m
|
||||
|
||||
Checking relay responsiveness... [0;32m✓ Relay is still responsive[0m
|
||||
|
||||
==========================================
|
||||
Load Test: Stress Test
|
||||
Description: Maximum load test to find breaking point
|
||||
Concurrent clients: 100
|
||||
Messages per client: 50
|
||||
==========================================
|
||||
Launching 100 clients...
|
||||
All clients completed. Processing results...
|
||||
|
||||
=== Load Test Results ===
|
||||
Test duration: 64s
|
||||
Total connections attempted: 185
|
||||
Successful connections: 100
|
||||
Failed connections: 0
|
||||
Connection success rate: 54%
|
||||
Messages expected: 5000
|
||||
Messages sent: 5000
|
||||
Messages received: 7500
|
||||
[0;31m✗ POOR: Low connection success rate[0m
|
||||
|
||||
Checking relay responsiveness... [0;32m✓ Relay is still responsive[0m
|
||||
|
||||
==========================================
|
||||
Load Testing Complete
|
||||
==========================================
|
||||
All load tests completed. Check individual test results above.
|
||||
If any tests failed, the relay may need optimization or have resource limits.
|
||||
2026-04-01 11:31:03 - \033[0;32m✓ Load Testing PASSED\033[0m (Duration: 85s)
|
||||
2026-04-01 11:31:03 - ==========================================
|
||||
2026-04-01 11:31:03 - Running Test Suite: Stress Testing
|
||||
2026-04-01 11:31:03 - Description: Resource usage and stability testing
|
||||
2026-04-01 11:31:03 - ==========================================
|
||||
2026-04-01 11:31:03 - \033[0;31mERROR: Test script stress_tests.sh not found\033[0m
|
||||
Executable
+183
@@ -0,0 +1,183 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Thread-level CPU profiler for c-relay on remote server
|
||||
# - Samples per-thread CPU every N seconds using /proc/<pid>/task/*/stat deltas
|
||||
# - Optionally runs perf record in parallel for callgraph data
|
||||
# - Pulls artifacts locally and generates a summary report
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REMOTE_HOST="${REMOTE_HOST:-ubuntu@laantungir.com}"
|
||||
DURATION="${DURATION:-600}" # seconds (default 10 minutes)
|
||||
INTERVAL="${INTERVAL:-10}" # seconds between samples
|
||||
OUTDIR_BASE="${OUTDIR_BASE:-thread_profile_runs}"
|
||||
RUN_TS="$(date -u +%Y%m%d_%H%M%S)"
|
||||
RUN_DIR="${OUTDIR_BASE}/${RUN_TS}"
|
||||
LOCAL_CSV="${RUN_DIR}/thread_samples.csv"
|
||||
LOCAL_REPORT="${RUN_DIR}/thread_summary.txt"
|
||||
REMOTE_DIR="/tmp/c_relay_thread_profile_${RUN_TS}"
|
||||
ENABLE_PERF="${ENABLE_PERF:-1}" # 1=run perf, 0=skip
|
||||
|
||||
mkdir -p "${RUN_DIR}"
|
||||
|
||||
echo "=========================================="
|
||||
echo "C-Relay Thread CPU Profiler"
|
||||
echo "=========================================="
|
||||
echo "Remote host : ${REMOTE_HOST}"
|
||||
echo "Duration : ${DURATION}s"
|
||||
echo "Interval : ${INTERVAL}s"
|
||||
echo "Run dir : ${RUN_DIR}"
|
||||
echo "Perf record : ${ENABLE_PERF}"
|
||||
echo ""
|
||||
|
||||
ssh "${REMOTE_HOST}" "bash -s" <<EOF
|
||||
set -euo pipefail
|
||||
|
||||
REMOTE_DIR="${REMOTE_DIR}"
|
||||
DURATION="${DURATION}"
|
||||
INTERVAL="${INTERVAL}"
|
||||
ENABLE_PERF="${ENABLE_PERF}"
|
||||
|
||||
mkdir -p "\${REMOTE_DIR}"
|
||||
CSV_FILE="\${REMOTE_DIR}/thread_samples.csv"
|
||||
PERF_FILE="\${REMOTE_DIR}/perf.data"
|
||||
LOG_FILE="\${REMOTE_DIR}/run.log"
|
||||
|
||||
PID=\$(pgrep -f '/usr/local/bin/c_relay/c_relay|c_relay' | head -1 || true)
|
||||
if [ -z "\${PID}" ]; then
|
||||
echo "ERROR: c_relay process not found (checked by cmdline pattern)" | tee -a "\${LOG_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Profiling PID: \${PID}" | tee -a "\${LOG_FILE}"
|
||||
CLK_TCK=\$(getconf CLK_TCK)
|
||||
echo "CLK_TCK=\${CLK_TCK}" | tee -a "\${LOG_FILE}"
|
||||
|
||||
echo "timestamp,tid,thread_name,cpu_pct,delta_ticks,total_ticks" > "\${CSV_FILE}"
|
||||
|
||||
# Start perf in background (if enabled and available)
|
||||
PERF_PID=""
|
||||
if [ "\${ENABLE_PERF}" = "1" ]; then
|
||||
if command -v perf >/dev/null 2>&1; then
|
||||
if sudo -n true >/dev/null 2>&1; then
|
||||
echo "Starting perf record in background..." | tee -a "\${LOG_FILE}"
|
||||
sudo perf record -g -p "\${PID}" -o "\${PERF_FILE}" -- sleep "\${DURATION}" >/dev/null 2>&1 &
|
||||
PERF_PID=\$!
|
||||
PERF_OWNER="\$(id -un)"
|
||||
echo "perf_pid=\${PERF_PID}" | tee -a "\${LOG_FILE}"
|
||||
else
|
||||
echo "Skipping perf: sudo -n not available" | tee -a "\${LOG_FILE}"
|
||||
fi
|
||||
else
|
||||
echo "Skipping perf: command not found" | tee -a "\${LOG_FILE}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Capture previous totals by tid
|
||||
declare -A PREV_TOTAL
|
||||
capture_totals() {
|
||||
local pid="\$1"
|
||||
for statf in /proc/\${pid}/task/*/stat; do
|
||||
[ -f "\${statf}" ] || continue
|
||||
local tid
|
||||
tid=\${statf%/stat}
|
||||
tid=\${tid##*/}
|
||||
local total
|
||||
total=\$(awk '{print \$14+\$15}' "\${statf}" 2>/dev/null || echo 0)
|
||||
PREV_TOTAL[\${tid}]="\${total}"
|
||||
done
|
||||
}
|
||||
|
||||
capture_totals "\${PID}"
|
||||
|
||||
START_TS=\$(date +%s)
|
||||
END_TS=\$((START_TS + DURATION))
|
||||
|
||||
while [ \$(date +%s) -lt \${END_TS} ]; do
|
||||
NOW=\$(date +%s)
|
||||
|
||||
for statf in /proc/\${PID}/task/*/stat; do
|
||||
[ -f "\${statf}" ] || continue
|
||||
|
||||
tid=\${statf%/stat}
|
||||
tid=\${tid##*/}
|
||||
total=\$(awk '{print \$14+\$15}' "\${statf}" 2>/dev/null || echo 0)
|
||||
prev=\${PREV_TOTAL[\${tid}]:-\${total}}
|
||||
delta=\$((total - prev))
|
||||
|
||||
if [ \${delta} -lt 0 ]; then
|
||||
delta=0
|
||||
fi
|
||||
|
||||
PREV_TOTAL[\${tid}]="\${total}"
|
||||
|
||||
name_file="/proc/\${PID}/task/\${tid}/comm"
|
||||
if [ -f "\${name_file}" ]; then
|
||||
tname=\$(tr -d '\n' < "\${name_file}")
|
||||
else
|
||||
tname="unknown"
|
||||
fi
|
||||
|
||||
cpu_pct=\$(awk -v d="\${delta}" -v hz="\${CLK_TCK}" -v iv="\${INTERVAL}" 'BEGIN { printf "%.2f", (d / hz) * 100.0 / iv }')
|
||||
echo "\${NOW},\${tid},\${tname},\${cpu_pct},\${delta},\${total}" >> "\${CSV_FILE}"
|
||||
done
|
||||
|
||||
sleep "\${INTERVAL}"
|
||||
done
|
||||
|
||||
if [ -n "\${PERF_PID}" ]; then
|
||||
wait "\${PERF_PID}" || true
|
||||
if [ -f "\${PERF_FILE}" ]; then
|
||||
sudo perf report --stdio -i "\${PERF_FILE}" --sort=comm,symbol --no-children -n > "\${REMOTE_DIR}/perf_report.txt" 2>/dev/null || true
|
||||
sudo chown "\${PERF_OWNER}:\${PERF_OWNER}" "\${PERF_FILE}" "\${REMOTE_DIR}/perf_report.txt" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Done. Artifacts in \${REMOTE_DIR}" | tee -a "\${LOG_FILE}"
|
||||
EOF
|
||||
|
||||
echo "Fetching artifacts..."
|
||||
scp -q "${REMOTE_HOST}:${REMOTE_DIR}/thread_samples.csv" "${LOCAL_CSV}"
|
||||
scp -q "${REMOTE_HOST}:${REMOTE_DIR}/run.log" "${RUN_DIR}/run.log" || true
|
||||
scp -q "${REMOTE_HOST}:${REMOTE_DIR}/perf.data" "${RUN_DIR}/perf.data" || true
|
||||
scp -q "${REMOTE_HOST}:${REMOTE_DIR}/perf_report.txt" "${RUN_DIR}/perf_report.txt" || true
|
||||
|
||||
echo "Generating summary..."
|
||||
{
|
||||
echo "=========================================="
|
||||
echo "Thread CPU Summary (avg + max over run)"
|
||||
echo "=========================================="
|
||||
echo "Run timestamp (UTC): ${RUN_TS}"
|
||||
echo "Duration: ${DURATION}s, Interval: ${INTERVAL}s"
|
||||
echo ""
|
||||
awk -F, '
|
||||
NR==1 {next}
|
||||
{
|
||||
key=$2"|"$3;
|
||||
cpu=$4+0;
|
||||
sum[key]+=cpu;
|
||||
cnt[key]++;
|
||||
if (cpu > max[key]) max[key]=cpu;
|
||||
}
|
||||
END {
|
||||
printf("%-10s %-18s %12s %12s\n", "TID", "THREAD", "AVG_CPU%", "MAX_CPU%");
|
||||
for (k in sum) {
|
||||
split(k, a, "|");
|
||||
avg=sum[k]/cnt[k];
|
||||
printf("%-10s %-18s %12.2f %12.2f\n", a[1], a[2], avg, max[k]);
|
||||
}
|
||||
}
|
||||
' "${LOCAL_CSV}" | sort -k3,3nr
|
||||
} | tee "${LOCAL_REPORT}"
|
||||
|
||||
echo ""
|
||||
echo "Completed. Files:"
|
||||
echo " ${LOCAL_CSV}"
|
||||
echo " ${LOCAL_REPORT}"
|
||||
if [ -f "${RUN_DIR}/perf_report.txt" ]; then
|
||||
echo " ${RUN_DIR}/perf_report.txt"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Top 20 summary:"
|
||||
head -n 22 "${LOCAL_REPORT}"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
Profiling PID: 2040140
|
||||
CLK_TCK=100
|
||||
Starting perf record in background...
|
||||
perf_pid=2040808
|
||||
Done. Artifacts in /tmp/c_relay_thread_profile_20260401_154500
|
||||
@@ -0,0 +1,421 @@
|
||||
timestamp,tid,thread_name,cpu_pct,delta_ticks,total_ticks
|
||||
1775058301,2040140",unknown,0.00,0,3950
|
||||
1775058301,2040150",unknown,0.00,0,9
|
||||
1775058301,2040151",unknown,0.00,0,8
|
||||
1775058301,2040152",unknown,0.00,0,11
|
||||
1775058301,2040153",unknown,0.00,0,11
|
||||
1775058301,2040154",unknown,0.00,0,0
|
||||
1775058301,2040155",unknown,0.00,0,0
|
||||
1775058311,2040140",unknown,62.10,621,4571
|
||||
1775058311,2040150",unknown,0.20,2,11
|
||||
1775058311,2040151",unknown,0.10,1,9
|
||||
1775058311,2040152",unknown,0.10,1,12
|
||||
1775058311,2040153",unknown,0.00,0,11
|
||||
1775058311,2040154",unknown,0.00,0,0
|
||||
1775058311,2040155",unknown,0.00,0,0
|
||||
1775058321,2040140",unknown,65.20,652,5223
|
||||
1775058321,2040150",unknown,0.20,2,13
|
||||
1775058321,2040151",unknown,0.20,2,11
|
||||
1775058321,2040152",unknown,0.30,3,15
|
||||
1775058321,2040153",unknown,0.20,2,13
|
||||
1775058321,2040154",unknown,0.00,0,0
|
||||
1775058321,2040155",unknown,0.00,0,0
|
||||
1775058331,2040140",unknown,83.80,838,6061
|
||||
1775058331,2040150",unknown,0.00,0,13
|
||||
1775058331,2040151",unknown,0.00,0,11
|
||||
1775058331,2040152",unknown,0.00,0,15
|
||||
1775058331,2040153",unknown,0.00,0,13
|
||||
1775058331,2040154",unknown,0.00,0,0
|
||||
1775058331,2040155",unknown,0.00,0,0
|
||||
1775058341,2040140",unknown,100.20,1002,7063
|
||||
1775058341,2040150",unknown,0.00,0,13
|
||||
1775058341,2040151",unknown,0.00,0,11
|
||||
1775058341,2040152",unknown,0.00,0,15
|
||||
1775058341,2040153",unknown,0.00,0,13
|
||||
1775058341,2040154",unknown,0.00,0,0
|
||||
1775058341,2040155",unknown,0.00,0,0
|
||||
1775058351,2040140",unknown,100.30,1003,8066
|
||||
1775058351,2040150",unknown,0.00,0,13
|
||||
1775058351,2040151",unknown,0.00,0,11
|
||||
1775058351,2040152",unknown,0.10,1,16
|
||||
1775058351,2040153",unknown,0.00,0,13
|
||||
1775058351,2040154",unknown,0.00,0,0
|
||||
1775058351,2040155",unknown,0.00,0,0
|
||||
1775058361,2040140",unknown,89.50,895,8961
|
||||
1775058361,2040150",unknown,0.10,1,14
|
||||
1775058361,2040151",unknown,0.00,0,11
|
||||
1775058361,2040152",unknown,0.00,0,16
|
||||
1775058361,2040153",unknown,0.00,0,13
|
||||
1775058361,2040154",unknown,0.00,0,0
|
||||
1775058361,2040155",unknown,0.00,0,0
|
||||
1775058371,2040140",unknown,21.20,212,9173
|
||||
1775058371,2040150",unknown,0.00,0,14
|
||||
1775058371,2040151",unknown,0.00,0,11
|
||||
1775058371,2040152",unknown,0.00,0,16
|
||||
1775058371,2040153",unknown,0.00,0,13
|
||||
1775058371,2040154",unknown,0.00,0,0
|
||||
1775058371,2040155",unknown,0.00,0,0
|
||||
1775058381,2040140",unknown,36.40,364,9537
|
||||
1775058381,2040150",unknown,0.10,1,15
|
||||
1775058381,2040151",unknown,0.10,1,12
|
||||
1775058381,2040152",unknown,0.10,1,17
|
||||
1775058381,2040153",unknown,0.00,0,13
|
||||
1775058381,2040154",unknown,0.00,0,0
|
||||
1775058381,2040155",unknown,0.00,0,0
|
||||
1775058391,2040140",unknown,35.30,353,9890
|
||||
1775058391,2040150",unknown,0.00,0,15
|
||||
1775058391,2040151",unknown,0.00,0,12
|
||||
1775058391,2040152",unknown,0.00,0,17
|
||||
1775058391,2040153",unknown,0.00,0,13
|
||||
1775058391,2040154",unknown,0.00,0,0
|
||||
1775058391,2040155",unknown,0.00,0,0
|
||||
1775058401,2040140",unknown,42.80,428,10318
|
||||
1775058401,2040150",unknown,0.30,3,18
|
||||
1775058401,2040151",unknown,0.20,2,14
|
||||
1775058401,2040152",unknown,0.30,3,20
|
||||
1775058401,2040153",unknown,0.30,3,16
|
||||
1775058401,2040154",unknown,0.00,0,0
|
||||
1775058401,2040155",unknown,0.00,0,0
|
||||
1775058411,2040140",unknown,73.50,735,11053
|
||||
1775058411,2040150",unknown,0.40,4,22
|
||||
1775058411,2040151",unknown,0.20,2,16
|
||||
1775058411,2040152",unknown,0.40,4,24
|
||||
1775058411,2040153",unknown,0.20,2,18
|
||||
1775058411,2040154",unknown,0.00,0,0
|
||||
1775058411,2040155",unknown,0.00,0,0
|
||||
1775058421,2040140",unknown,33.60,336,11389
|
||||
1775058421,2040150",unknown,0.00,0,22
|
||||
1775058421,2040151",unknown,0.00,0,16
|
||||
1775058421,2040152",unknown,0.00,0,24
|
||||
1775058421,2040153",unknown,0.10,1,19
|
||||
1775058421,2040154",unknown,0.00,0,0
|
||||
1775058421,2040155",unknown,0.00,0,0
|
||||
1775058431,2040140",unknown,57.20,572,11961
|
||||
1775058431,2040150",unknown,0.00,0,22
|
||||
1775058431,2040151",unknown,0.10,1,17
|
||||
1775058431,2040152",unknown,0.10,1,25
|
||||
1775058431,2040153",unknown,0.00,0,19
|
||||
1775058431,2040154",unknown,0.00,0,0
|
||||
1775058431,2040155",unknown,0.00,0,0
|
||||
1775058441,2040140",unknown,96.70,967,12928
|
||||
1775058441,2040150",unknown,0.00,0,22
|
||||
1775058441,2040151",unknown,0.00,0,17
|
||||
1775058441,2040152",unknown,0.00,0,25
|
||||
1775058441,2040153",unknown,0.00,0,19
|
||||
1775058441,2040154",unknown,0.00,0,0
|
||||
1775058441,2040155",unknown,0.00,0,0
|
||||
1775058451,2040140",unknown,100.30,1003,13931
|
||||
1775058451,2040150",unknown,0.10,1,23
|
||||
1775058451,2040151",unknown,0.10,1,18
|
||||
1775058451,2040152",unknown,0.10,1,26
|
||||
1775058451,2040153",unknown,0.00,0,19
|
||||
1775058451,2040154",unknown,0.00,0,0
|
||||
1775058451,2040155",unknown,0.00,0,0
|
||||
1775058461,2040140",unknown,98.40,984,14915
|
||||
1775058461,2040150",unknown,0.10,1,24
|
||||
1775058461,2040151",unknown,0.30,3,21
|
||||
1775058461,2040152",unknown,0.10,1,27
|
||||
1775058461,2040153",unknown,0.20,2,21
|
||||
1775058461,2040154",unknown,0.00,0,0
|
||||
1775058461,2040155",unknown,0.00,0,0
|
||||
1775058471,2040140",unknown,62.90,629,15544
|
||||
1775058471,2040150",unknown,0.00,0,24
|
||||
1775058471,2040151",unknown,0.20,2,23
|
||||
1775058471,2040152",unknown,0.00,0,27
|
||||
1775058471,2040153",unknown,0.10,1,22
|
||||
1775058471,2040154",unknown,0.00,0,0
|
||||
1775058471,2040155",unknown,0.20,2,2
|
||||
1775058481,2040140",unknown,83.20,832,16376
|
||||
1775058481,2040150",unknown,0.20,2,26
|
||||
1775058481,2040151",unknown,0.00,0,23
|
||||
1775058481,2040152",unknown,0.20,2,29
|
||||
1775058481,2040153",unknown,0.00,0,22
|
||||
1775058481,2040154",unknown,0.00,0,0
|
||||
1775058481,2040155",unknown,0.00,0,2
|
||||
1775058491,2040140",unknown,100.30,1003,17379
|
||||
1775058491,2040150",unknown,0.00,0,26
|
||||
1775058491,2040151",unknown,0.00,0,23
|
||||
1775058491,2040152",unknown,0.00,0,29
|
||||
1775058491,2040153",unknown,0.00,0,22
|
||||
1775058491,2040154",unknown,0.00,0,0
|
||||
1775058491,2040155",unknown,0.00,0,2
|
||||
1775058501,2040140",unknown,63.30,633,18012
|
||||
1775058501,2040150",unknown,0.10,1,27
|
||||
1775058501,2040151",unknown,0.10,1,24
|
||||
1775058501,2040152",unknown,0.30,3,32
|
||||
1775058501,2040153",unknown,0.20,2,24
|
||||
1775058501,2040154",unknown,0.00,0,0
|
||||
1775058501,2040155",unknown,0.00,0,2
|
||||
1775058511,2040140",unknown,57.40,574,18586
|
||||
1775058511,2040150",unknown,0.00,0,27
|
||||
1775058511,2040151",unknown,0.10,1,25
|
||||
1775058511,2040152",unknown,0.00,0,32
|
||||
1775058511,2040153",unknown,0.00,0,24
|
||||
1775058511,2040154",unknown,0.00,0,0
|
||||
1775058511,2040155",unknown,0.00,0,2
|
||||
1775058521,2040140",unknown,18.70,187,18773
|
||||
1775058521,2040150",unknown,0.10,1,28
|
||||
1775058521,2040151",unknown,0.00,0,25
|
||||
1775058521,2040152",unknown,0.00,0,32
|
||||
1775058521,2040153",unknown,0.00,0,24
|
||||
1775058521,2040154",unknown,0.00,0,0
|
||||
1775058521,2040155",unknown,0.00,0,2
|
||||
1775058531,2040140",unknown,66.20,662,19435
|
||||
1775058531,2040150",unknown,0.00,0,28
|
||||
1775058531,2040151",unknown,0.10,1,26
|
||||
1775058531,2040152",unknown,0.00,0,32
|
||||
1775058531,2040153",unknown,0.00,0,24
|
||||
1775058531,2040154",unknown,0.00,0,0
|
||||
1775058531,2040155",unknown,0.00,0,2
|
||||
1775058541,2040140",unknown,89.10,891,20326
|
||||
1775058541,2040150",unknown,0.00,0,28
|
||||
1775058541,2040151",unknown,0.00,0,26
|
||||
1775058541,2040152",unknown,0.10,1,33
|
||||
1775058541,2040153",unknown,0.00,0,24
|
||||
1775058541,2040154",unknown,0.00,0,0
|
||||
1775058541,2040155",unknown,0.00,0,2
|
||||
1775058551,2040140",unknown,38.10,381,20707
|
||||
1775058551,2040150",unknown,0.10,1,29
|
||||
1775058551,2040151",unknown,0.00,0,26
|
||||
1775058551,2040152",unknown,0.00,0,33
|
||||
1775058551,2040153",unknown,0.00,0,24
|
||||
1775058551,2040154",unknown,0.00,0,0
|
||||
1775058551,2040155",unknown,0.00,0,2
|
||||
1775058562,2040140",unknown,69.20,692,21399
|
||||
1775058562,2040150",unknown,0.00,0,29
|
||||
1775058562,2040151",unknown,0.20,2,28
|
||||
1775058562,2040152",unknown,0.00,0,33
|
||||
1775058562,2040153",unknown,0.10,1,25
|
||||
1775058562,2040154",unknown,0.00,0,0
|
||||
1775058562,2040155",unknown,0.00,0,2
|
||||
1775058572,2040140",unknown,92.80,928,22327
|
||||
1775058572,2040150",unknown,0.00,0,29
|
||||
1775058572,2040151",unknown,0.00,0,28
|
||||
1775058572,2040152",unknown,0.00,0,33
|
||||
1775058572,2040153",unknown,0.00,0,25
|
||||
1775058572,2040154",unknown,0.00,0,0
|
||||
1775058572,2040155",unknown,0.00,0,2
|
||||
1775058582,2040140",unknown,91.30,913,23240
|
||||
1775058582,2040150",unknown,0.10,1,30
|
||||
1775058582,2040151",unknown,0.00,0,28
|
||||
1775058582,2040152",unknown,0.10,1,34
|
||||
1775058582,2040153",unknown,0.00,0,25
|
||||
1775058582,2040154",unknown,0.00,0,0
|
||||
1775058582,2040155",unknown,0.00,0,2
|
||||
1775058592,2040140",unknown,94.60,946,24186
|
||||
1775058592,2040150",unknown,0.00,0,30
|
||||
1775058592,2040151",unknown,0.00,0,28
|
||||
1775058592,2040152",unknown,0.10,1,35
|
||||
1775058592,2040153",unknown,0.10,1,26
|
||||
1775058592,2040154",unknown,0.00,0,0
|
||||
1775058592,2040155",unknown,0.00,0,2
|
||||
1775058602,2040140",unknown,45.50,455,24641
|
||||
1775058602,2040150",unknown,0.00,0,30
|
||||
1775058602,2040151",unknown,0.00,0,28
|
||||
1775058602,2040152",unknown,0.00,0,35
|
||||
1775058602,2040153",unknown,0.00,0,26
|
||||
1775058602,2040154",unknown,0.00,0,0
|
||||
1775058602,2040155",unknown,0.00,0,2
|
||||
1775058612,2040140",unknown,64.50,645,25286
|
||||
1775058612,2040150",unknown,0.00,0,30
|
||||
1775058612,2040151",unknown,0.10,1,29
|
||||
1775058612,2040152",unknown,0.00,0,35
|
||||
1775058612,2040153",unknown,0.10,1,27
|
||||
1775058612,2040154",unknown,0.00,0,0
|
||||
1775058612,2040155",unknown,0.00,0,2
|
||||
1775058622,2040140",unknown,21.10,211,25497
|
||||
1775058622,2040150",unknown,0.00,0,30
|
||||
1775058622,2040151",unknown,0.00,0,29
|
||||
1775058622,2040152",unknown,0.00,0,35
|
||||
1775058622,2040153",unknown,0.00,0,27
|
||||
1775058622,2040154",unknown,0.00,0,0
|
||||
1775058622,2040155",unknown,0.00,0,2
|
||||
1775058632,2040140",unknown,26.60,266,25763
|
||||
1775058632,2040150",unknown,0.20,2,32
|
||||
1775058632,2040151",unknown,0.10,1,30
|
||||
1775058632,2040152",unknown,0.00,0,35
|
||||
1775058632,2040153",unknown,0.00,0,27
|
||||
1775058632,2040154",unknown,0.10,1,1
|
||||
1775058632,2040155",unknown,0.00,0,2
|
||||
1775058642,2040140",unknown,51.80,518,26281
|
||||
1775058642,2040150",unknown,0.00,0,32
|
||||
1775058642,2040151",unknown,0.10,1,31
|
||||
1775058642,2040152",unknown,0.00,0,35
|
||||
1775058642,2040153",unknown,0.00,0,27
|
||||
1775058642,2040154",unknown,0.00,0,1
|
||||
1775058642,2040155",unknown,0.00,0,2
|
||||
1775058652,2040140",unknown,28.40,284,26565
|
||||
1775058652,2040150",unknown,0.00,0,32
|
||||
1775058652,2040151",unknown,0.10,1,32
|
||||
1775058652,2040152",unknown,0.10,1,36
|
||||
1775058652,2040153",unknown,0.20,2,29
|
||||
1775058652,2040154",unknown,0.00,0,1
|
||||
1775058652,2040155",unknown,0.00,0,2
|
||||
1775058662,2040140",unknown,14.30,143,26708
|
||||
1775058662,2040150",unknown,0.20,2,34
|
||||
1775058662,2040151",unknown,0.10,1,33
|
||||
1775058662,2040152",unknown,0.40,4,40
|
||||
1775058662,2040153",unknown,0.30,3,32
|
||||
1775058662,2040154",unknown,0.00,0,1
|
||||
1775058662,2040155",unknown,0.00,0,2
|
||||
1775058672,2040140",unknown,69.20,692,27400
|
||||
1775058672,2040150",unknown,0.20,2,36
|
||||
1775058672,2040151",unknown,0.00,0,33
|
||||
1775058672,2040152",unknown,0.20,2,42
|
||||
1775058672,2040153",unknown,0.10,1,33
|
||||
1775058672,2040154",unknown,0.00,0,1
|
||||
1775058672,2040155",unknown,0.00,0,2
|
||||
1775058682,2040140",unknown,71.10,711,28111
|
||||
1775058682,2040150",unknown,0.30,3,39
|
||||
1775058682,2040151",unknown,0.20,2,35
|
||||
1775058682,2040152",unknown,0.00,0,42
|
||||
1775058682,2040153",unknown,0.10,1,34
|
||||
1775058682,2040154",unknown,0.00,0,1
|
||||
1775058682,2040155",unknown,0.00,0,2
|
||||
1775058692,2040140",unknown,99.40,994,29105
|
||||
1775058692,2040150",unknown,0.00,0,39
|
||||
1775058692,2040151",unknown,0.00,0,35
|
||||
1775058692,2040152",unknown,0.00,0,42
|
||||
1775058692,2040153",unknown,0.00,0,34
|
||||
1775058692,2040154",unknown,0.00,0,1
|
||||
1775058692,2040155",unknown,0.00,0,2
|
||||
1775058702,2040140",unknown,30.60,306,29411
|
||||
1775058702,2040150",unknown,0.10,1,40
|
||||
1775058702,2040151",unknown,0.10,1,36
|
||||
1775058702,2040152",unknown,0.20,2,44
|
||||
1775058702,2040153",unknown,0.20,2,36
|
||||
1775058702,2040154",unknown,0.00,0,1
|
||||
1775058702,2040155",unknown,0.00,0,2
|
||||
1775058712,2040140",unknown,14.70,147,29558
|
||||
1775058712,2040150",unknown,0.00,0,40
|
||||
1775058712,2040151",unknown,0.00,0,36
|
||||
1775058712,2040152",unknown,0.10,1,45
|
||||
1775058712,2040153",unknown,0.00,0,36
|
||||
1775058712,2040154",unknown,0.10,1,2
|
||||
1775058712,2040155",unknown,0.00,0,2
|
||||
1775058722,2040140",unknown,14.20,142,29700
|
||||
1775058722,2040150",unknown,0.00,0,40
|
||||
1775058722,2040151",unknown,0.10,1,37
|
||||
1775058722,2040152",unknown,0.10,1,46
|
||||
1775058722,2040153",unknown,0.00,0,36
|
||||
1775058722,2040154",unknown,0.00,0,2
|
||||
1775058722,2040155",unknown,0.00,0,2
|
||||
1775058732,2040140",unknown,29.20,292,29992
|
||||
1775058732,2040150",unknown,0.10,1,41
|
||||
1775058732,2040151",unknown,0.00,0,37
|
||||
1775058732,2040152",unknown,0.00,0,46
|
||||
1775058732,2040153",unknown,0.00,0,36
|
||||
1775058732,2040154",unknown,0.00,0,2
|
||||
1775058732,2040155",unknown,0.00,0,2
|
||||
1775058742,2040140",unknown,29.80,298,30290
|
||||
1775058742,2040150",unknown,0.00,0,41
|
||||
1775058742,2040151",unknown,0.00,0,37
|
||||
1775058742,2040152",unknown,0.10,1,47
|
||||
1775058742,2040153",unknown,0.00,0,36
|
||||
1775058742,2040154",unknown,0.00,0,2
|
||||
1775058742,2040155",unknown,0.00,0,2
|
||||
1775058752,2040140",unknown,48.20,482,30772
|
||||
1775058752,2040150",unknown,0.00,0,41
|
||||
1775058752,2040151",unknown,0.00,0,37
|
||||
1775058752,2040152",unknown,0.00,0,47
|
||||
1775058752,2040153",unknown,0.10,1,37
|
||||
1775058752,2040154",unknown,0.00,0,2
|
||||
1775058752,2040155",unknown,0.00,0,2
|
||||
1775058762,2040140",unknown,28.10,281,31053
|
||||
1775058762,2040150",unknown,0.00,0,41
|
||||
1775058762,2040151",unknown,0.00,0,37
|
||||
1775058762,2040152",unknown,0.00,0,47
|
||||
1775058762,2040153",unknown,0.00,0,37
|
||||
1775058762,2040154",unknown,0.00,0,2
|
||||
1775058762,2040155",unknown,0.00,0,2
|
||||
1775058772,2040140",unknown,40.80,408,31461
|
||||
1775058772,2040150",unknown,0.10,1,42
|
||||
1775058772,2040151",unknown,0.10,1,38
|
||||
1775058772,2040152",unknown,0.10,1,48
|
||||
1775058772,2040153",unknown,0.10,1,38
|
||||
1775058772,2040154",unknown,0.00,0,2
|
||||
1775058772,2040155",unknown,0.00,0,2
|
||||
1775058782,2040140",unknown,98.90,989,32450
|
||||
1775058782,2040150",unknown,0.30,3,45
|
||||
1775058782,2040151",unknown,0.00,0,38
|
||||
1775058782,2040152",unknown,0.30,3,51
|
||||
1775058782,2040153",unknown,0.10,1,39
|
||||
1775058782,2040154",unknown,0.00,0,2
|
||||
1775058782,2040155",unknown,0.10,1,3
|
||||
1775058792,2040140",unknown,59.50,595,33045
|
||||
1775058792,2040150",unknown,0.20,2,47
|
||||
1775058792,2040151",unknown,0.10,1,39
|
||||
1775058792,2040152",unknown,0.10,1,52
|
||||
1775058792,2040153",unknown,0.10,1,40
|
||||
1775058792,2040154",unknown,0.00,0,2
|
||||
1775058792,2040155",unknown,0.00,0,3
|
||||
1775058802,2040140",unknown,47.10,471,33516
|
||||
1775058802,2040150",unknown,0.00,0,47
|
||||
1775058802,2040151",unknown,0.00,0,39
|
||||
1775058802,2040152",unknown,0.00,0,52
|
||||
1775058802,2040153",unknown,0.10,1,41
|
||||
1775058802,2040154",unknown,0.00,0,2
|
||||
1775058802,2040155",unknown,0.10,1,4
|
||||
1775058812,2040140",unknown,50.60,506,34022
|
||||
1775058812,2040150",unknown,0.00,0,47
|
||||
1775058812,2040151",unknown,0.00,0,39
|
||||
1775058812,2040152",unknown,0.10,1,53
|
||||
1775058812,2040153",unknown,0.00,0,41
|
||||
1775058812,2040154",unknown,0.00,0,2
|
||||
1775058812,2040155",unknown,0.10,1,5
|
||||
1775058822,2040140",unknown,28.10,281,34303
|
||||
1775058822,2040150",unknown,0.00,0,47
|
||||
1775058822,2040151",unknown,0.00,0,39
|
||||
1775058822,2040152",unknown,0.00,0,53
|
||||
1775058822,2040153",unknown,0.00,0,41
|
||||
1775058822,2040154",unknown,0.00,0,2
|
||||
1775058822,2040155",unknown,0.00,0,5
|
||||
1775058832,2040140",unknown,75.30,753,35056
|
||||
1775058832,2040150",unknown,0.10,1,48
|
||||
1775058832,2040151",unknown,0.10,1,40
|
||||
1775058832,2040152",unknown,0.10,1,54
|
||||
1775058832,2040153",unknown,0.10,1,42
|
||||
1775058832,2040154",unknown,0.00,0,2
|
||||
1775058832,2040155",unknown,0.00,0,5
|
||||
1775058842,2040140",unknown,37.20,372,35428
|
||||
1775058842,2040150",unknown,0.00,0,48
|
||||
1775058842,2040151",unknown,0.00,0,40
|
||||
1775058842,2040152",unknown,0.00,0,54
|
||||
1775058842,2040153",unknown,0.00,0,42
|
||||
1775058842,2040154",unknown,0.00,0,2
|
||||
1775058842,2040155",unknown,0.00,0,5
|
||||
1775058852,2040140",unknown,27.00,270,35698
|
||||
1775058852,2040150",unknown,0.20,2,50
|
||||
1775058852,2040151",unknown,0.20,2,42
|
||||
1775058852,2040152",unknown,0.30,3,57
|
||||
1775058852,2040153",unknown,0.20,2,44
|
||||
1775058852,2040154",unknown,0.00,0,2
|
||||
1775058852,2040155",unknown,0.00,0,5
|
||||
1775058862,2040140",unknown,46.30,463,36161
|
||||
1775058862,2040150",unknown,0.00,0,50
|
||||
1775058862,2040151",unknown,0.10,1,43
|
||||
1775058862,2040152",unknown,0.20,2,59
|
||||
1775058862,2040153",unknown,0.00,0,44
|
||||
1775058862,2040154",unknown,0.00,0,2
|
||||
1775058862,2040155",unknown,0.00,0,5
|
||||
1775058872,2040140",unknown,14.00,140,36301
|
||||
1775058872,2040150",unknown,0.00,0,50
|
||||
1775058872,2040151",unknown,0.00,0,43
|
||||
1775058872,2040152",unknown,0.00,0,59
|
||||
1775058872,2040153",unknown,0.00,0,44
|
||||
1775058872,2040154",unknown,0.00,0,2
|
||||
1775058872,2040155",unknown,0.00,0,5
|
||||
1775058882,2040140",unknown,56.20,562,36863
|
||||
1775058882,2040150",unknown,0.00,0,50
|
||||
1775058882,2040151",unknown,0.10,1,44
|
||||
1775058882,2040152",unknown,0.10,1,60
|
||||
1775058882,2040153",unknown,0.00,0,44
|
||||
1775058882,2040154",unknown,0.00,0,2
|
||||
1775058882,2040155",unknown,0.00,0,5
|
||||
1775058893,2040140",unknown,35.60,356,37219
|
||||
1775058893,2040150",unknown,0.20,2,52
|
||||
1775058893,2040151",unknown,0.10,1,45
|
||||
1775058893,2040152",unknown,0.10,1,61
|
||||
1775058893,2040153",unknown,0.10,1,45
|
||||
1775058893,2040154",unknown,0.00,0,2
|
||||
1775058893,2040155",unknown,0.00,0,5
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 19.
|
@@ -0,0 +1,14 @@
|
||||
==========================================
|
||||
Thread CPU Summary (avg + max over run)
|
||||
==========================================
|
||||
Run timestamp (UTC): 20260401_154500
|
||||
Duration: 600s, Interval: 10s
|
||||
|
||||
2040140" unknown 55.45 100.30
|
||||
2040152" unknown 0.08 0.40
|
||||
2040150" unknown 0.07 0.40
|
||||
2040151" unknown 0.06 0.30
|
||||
2040153" unknown 0.06 0.30
|
||||
2040155" unknown 0.01 0.20
|
||||
2040154" unknown 0.00 0.10
|
||||
TID THREAD AVG_CPU% MAX_CPU%
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
Profiling PID: 2040140
|
||||
CLK_TCK=100
|
||||
Starting perf record in background...
|
||||
perf_pid=2046339
|
||||
Done. Artifacts in /tmp/c_relay_thread_profile_20260401_155603
|
||||
@@ -0,0 +1,372 @@
|
||||
timestamp,tid,thread_name,cpu_pct,delta_ticks,total_ticks
|
||||
1775058964,2040140,lws-main,0.00,0,42849
|
||||
1775058964,2040150,db-read-1,0.00,0,58
|
||||
1775058964,2040151,db-read-2,0.00,0,54
|
||||
1775058964,2040152,db-read-3,0.10,1,68
|
||||
1775058964,2040153,db-read-4,0.00,0,53
|
||||
1775058964,2040154,db-write,0.00,0,2
|
||||
1775058964,2040155,event-worker,0.00,0,5
|
||||
1775058974,2040140,lws-main,59.30,593,43442
|
||||
1775058974,2040150,db-read-1,0.00,0,58
|
||||
1775058974,2040151,db-read-2,0.00,0,54
|
||||
1775058974,2040152,db-read-3,0.10,1,69
|
||||
1775058974,2040153,db-read-4,0.00,0,53
|
||||
1775058974,2040154,db-write,0.00,0,2
|
||||
1775058974,2040155,event-worker,0.00,0,5
|
||||
1775058984,2040140,lws-main,67.40,674,44116
|
||||
1775058984,2040150,db-read-1,0.00,0,58
|
||||
1775058984,2040151,db-read-2,0.00,0,54
|
||||
1775058984,2040152,db-read-3,0.00,0,69
|
||||
1775058984,2040153,db-read-4,0.10,1,54
|
||||
1775058984,2040154,db-write,0.00,0,2
|
||||
1775058984,2040155,event-worker,0.00,0,5
|
||||
1775058994,2040140,lws-main,98.70,987,45103
|
||||
1775058994,2040150,db-read-1,0.10,1,59
|
||||
1775058994,2040151,db-read-2,0.00,0,54
|
||||
1775058994,2040152,db-read-3,0.00,0,69
|
||||
1775058994,2040153,db-read-4,0.00,0,54
|
||||
1775058994,2040154,db-write,0.00,0,2
|
||||
1775058994,2040155,event-worker,0.00,0,5
|
||||
1775059004,2040140,lws-main,71.00,710,45813
|
||||
1775059004,2040150,db-read-1,0.00,0,59
|
||||
1775059004,2040151,db-read-2,0.00,0,54
|
||||
1775059004,2040152,db-read-3,0.10,1,70
|
||||
1775059004,2040153,db-read-4,0.10,1,55
|
||||
1775059004,2040154,db-write,0.00,0,2
|
||||
1775059004,2040155,event-worker,0.00,0,5
|
||||
1775059014,2040140,lws-main,14.70,147,45960
|
||||
1775059014,2040150,db-read-1,0.10,1,60
|
||||
1775059014,2040151,db-read-2,0.30,3,57
|
||||
1775059014,2040152,db-read-3,0.10,1,71
|
||||
1775059014,2040153,db-read-4,0.10,1,56
|
||||
1775059014,2040154,db-write,0.00,0,2
|
||||
1775059014,2040155,event-worker,0.00,0,5
|
||||
1775059024,2040140,lws-main,47.70,477,46437
|
||||
1775059024,2040150,db-read-1,0.00,0,60
|
||||
1775059024,2040151,db-read-2,0.00,0,57
|
||||
1775059024,2040152,db-read-3,0.00,0,71
|
||||
1775059024,2040153,db-read-4,0.00,0,56
|
||||
1775059024,2040154,db-write,0.00,0,2
|
||||
1775059024,2040155,event-worker,0.00,0,5
|
||||
1775059034,2040140,lws-main,99.50,995,47432
|
||||
1775059034,2040150,db-read-1,0.10,1,61
|
||||
1775059034,2040151,db-read-2,0.10,1,58
|
||||
1775059034,2040152,db-read-3,0.10,1,72
|
||||
1775059034,2040153,db-read-4,0.00,0,56
|
||||
1775059034,2040154,db-write,0.10,1,3
|
||||
1775059034,2040155,event-worker,0.00,0,5
|
||||
1775059044,2040140,lws-main,88.50,885,48317
|
||||
1775059044,2040150,db-read-1,0.20,2,63
|
||||
1775059044,2040151,db-read-2,0.10,1,59
|
||||
1775059044,2040152,db-read-3,0.10,1,73
|
||||
1775059044,2040153,db-read-4,0.20,2,58
|
||||
1775059044,2040154,db-write,0.00,0,3
|
||||
1775059044,2040155,event-worker,0.00,0,5
|
||||
1775059054,2040140,lws-main,67.70,677,48994
|
||||
1775059054,2040150,db-read-1,0.00,0,63
|
||||
1775059054,2040151,db-read-2,0.00,0,59
|
||||
1775059054,2040152,db-read-3,0.00,0,73
|
||||
1775059054,2040153,db-read-4,0.00,0,58
|
||||
1775059054,2040154,db-write,0.00,0,3
|
||||
1775059054,2040155,event-worker,0.00,0,5
|
||||
1775059064,2040140,lws-main,53.10,531,49525
|
||||
1775059064,2040150,db-read-1,0.40,4,67
|
||||
1775059064,2040151,db-read-2,0.40,4,63
|
||||
1775059064,2040152,db-read-3,0.20,2,75
|
||||
1775059064,2040153,db-read-4,0.00,0,58
|
||||
1775059064,2040154,db-write,0.00,0,3
|
||||
1775059064,2040155,event-worker,0.00,0,5
|
||||
1775059074,2040140,lws-main,61.60,616,50141
|
||||
1775059074,2040150,db-read-1,0.30,3,70
|
||||
1775059074,2040151,db-read-2,0.20,2,65
|
||||
1775059074,2040152,db-read-3,0.10,1,76
|
||||
1775059074,2040153,db-read-4,0.20,2,60
|
||||
1775059074,2040154,db-write,0.00,0,3
|
||||
1775059074,2040155,event-worker,0.00,0,5
|
||||
1775059084,2040140,lws-main,96.20,962,51103
|
||||
1775059084,2040150,db-read-1,0.00,0,70
|
||||
1775059084,2040151,db-read-2,0.20,2,67
|
||||
1775059084,2040152,db-read-3,0.40,4,80
|
||||
1775059084,2040153,db-read-4,0.10,1,61
|
||||
1775059084,2040154,db-write,0.10,1,4
|
||||
1775059084,2040155,event-worker,0.00,0,5
|
||||
1775059094,2040140,lws-main,53.60,536,51639
|
||||
1775059094,2040150,db-read-1,0.10,1,71
|
||||
1775059094,2040151,db-read-2,0.20,2,69
|
||||
1775059094,2040152,db-read-3,0.00,0,80
|
||||
1775059094,2040153,db-read-4,0.00,0,61
|
||||
1775059094,2040154,db-write,0.00,0,4
|
||||
1775059094,2040155,event-worker,0.00,0,5
|
||||
1775059105,2040140,lws-main,51.40,514,52153
|
||||
1775059105,2040150,db-read-1,0.10,1,72
|
||||
1775059105,2040151,db-read-2,0.00,0,69
|
||||
1775059105,2040152,db-read-3,0.00,0,80
|
||||
1775059105,2040153,db-read-4,0.10,1,62
|
||||
1775059105,2040154,db-write,0.00,0,4
|
||||
1775059105,2040155,event-worker,0.00,0,5
|
||||
1775059115,2040140,lws-main,78.50,785,52938
|
||||
1775059115,2040150,db-read-1,0.00,0,72
|
||||
1775059115,2040151,db-read-2,0.00,0,69
|
||||
1775059115,2040152,db-read-3,0.00,0,80
|
||||
1775059115,2040153,db-read-4,0.00,0,62
|
||||
1775059115,2040154,db-write,0.00,0,4
|
||||
1775059115,2040155,event-worker,0.00,0,5
|
||||
1775059125,2040140,lws-main,40.70,407,53345
|
||||
1775059125,2040150,db-read-1,0.00,0,72
|
||||
1775059125,2040151,db-read-2,0.00,0,69
|
||||
1775059125,2040152,db-read-3,0.10,1,81
|
||||
1775059125,2040153,db-read-4,0.10,1,63
|
||||
1775059125,2040154,db-write,0.00,0,4
|
||||
1775059125,2040155,event-worker,0.00,0,5
|
||||
1775059135,2040140,lws-main,41.00,410,53755
|
||||
1775059135,2040150,db-read-1,0.00,0,72
|
||||
1775059135,2040151,db-read-2,0.00,0,69
|
||||
1775059135,2040152,db-read-3,0.00,0,81
|
||||
1775059135,2040153,db-read-4,0.00,0,63
|
||||
1775059135,2040154,db-write,0.00,0,4
|
||||
1775059135,2040155,event-worker,0.00,0,5
|
||||
1775059145,2040140,lws-main,58.70,587,54342
|
||||
1775059145,2040150,db-read-1,0.00,0,72
|
||||
1775059145,2040151,db-read-2,0.00,0,69
|
||||
1775059145,2040152,db-read-3,0.00,0,81
|
||||
1775059145,2040153,db-read-4,0.00,0,63
|
||||
1775059145,2040154,db-write,0.00,0,4
|
||||
1775059145,2040155,event-worker,0.00,0,5
|
||||
1775059155,2040140,lws-main,28.40,284,54626
|
||||
1775059155,2040150,db-read-1,0.20,2,74
|
||||
1775059155,2040151,db-read-2,0.20,2,71
|
||||
1775059155,2040152,db-read-3,0.10,1,82
|
||||
1775059155,2040153,db-read-4,0.10,1,64
|
||||
1775059155,2040154,db-write,0.00,0,4
|
||||
1775059155,2040155,event-worker,0.00,0,5
|
||||
1775059165,2040140,lws-main,89.80,898,55524
|
||||
1775059165,2040150,db-read-1,0.30,3,77
|
||||
1775059165,2040151,db-read-2,0.20,2,73
|
||||
1775059165,2040152,db-read-3,0.30,3,85
|
||||
1775059165,2040153,db-read-4,0.10,1,65
|
||||
1775059165,2040154,db-write,0.00,0,4
|
||||
1775059165,2040155,event-worker,0.00,0,5
|
||||
1775059175,2040140,lws-main,100.20,1002,56526
|
||||
1775059175,2040150,db-read-1,0.00,0,77
|
||||
1775059175,2040151,db-read-2,0.00,0,73
|
||||
1775059175,2040152,db-read-3,0.10,1,86
|
||||
1775059175,2040153,db-read-4,0.20,2,67
|
||||
1775059175,2040154,db-write,0.00,0,4
|
||||
1775059175,2040155,event-worker,0.00,0,5
|
||||
1775059185,2040140,lws-main,100.20,1002,57528
|
||||
1775059185,2040150,db-read-1,0.20,2,79
|
||||
1775059185,2040151,db-read-2,0.20,2,75
|
||||
1775059185,2040152,db-read-3,0.10,1,87
|
||||
1775059185,2040153,db-read-4,0.20,2,69
|
||||
1775059185,2040154,db-write,0.00,0,4
|
||||
1775059185,2040155,event-worker,0.00,0,5
|
||||
1775059195,2040140,lws-main,75.70,757,58285
|
||||
1775059195,2040150,db-read-1,0.20,2,81
|
||||
1775059195,2040151,db-read-2,0.20,2,77
|
||||
1775059195,2040152,db-read-3,0.20,2,89
|
||||
1775059195,2040153,db-read-4,0.20,2,71
|
||||
1775059195,2040154,db-write,0.00,0,4
|
||||
1775059195,2040155,event-worker,0.00,0,5
|
||||
1775059205,2040140,lws-main,77.70,777,59062
|
||||
1775059205,2040150,db-read-1,0.20,2,83
|
||||
1775059205,2040151,db-read-2,0.30,3,80
|
||||
1775059205,2040152,db-read-3,0.50,5,94
|
||||
1775059205,2040153,db-read-4,0.30,3,74
|
||||
1775059205,2040154,db-write,0.00,0,4
|
||||
1775059205,2040155,event-worker,0.10,1,6
|
||||
1775059215,2040140,lws-main,99.60,996,60058
|
||||
1775059215,2040150,db-read-1,0.40,4,87
|
||||
1775059215,2040151,db-read-2,0.40,4,84
|
||||
1775059215,2040152,db-read-3,0.10,1,95
|
||||
1775059215,2040153,db-read-4,0.20,2,76
|
||||
1775059215,2040154,db-write,0.00,0,4
|
||||
1775059215,2040155,event-worker,0.00,0,6
|
||||
1775059225,2040140,lws-main,100.30,1003,61061
|
||||
1775059225,2040150,db-read-1,0.00,0,87
|
||||
1775059225,2040151,db-read-2,0.10,1,85
|
||||
1775059225,2040152,db-read-3,0.10,1,96
|
||||
1775059225,2040153,db-read-4,0.20,2,78
|
||||
1775059225,2040154,db-write,0.00,0,4
|
||||
1775059225,2040155,event-worker,0.00,0,6
|
||||
1775059235,2040140,lws-main,100.10,1001,62062
|
||||
1775059235,2040150,db-read-1,0.10,1,88
|
||||
1775059235,2040151,db-read-2,0.00,0,85
|
||||
1775059235,2040152,db-read-3,0.00,0,96
|
||||
1775059235,2040153,db-read-4,0.00,0,78
|
||||
1775059235,2040154,db-write,0.00,0,4
|
||||
1775059235,2040155,event-worker,0.00,0,6
|
||||
1775059245,2040140,lws-main,98.10,981,63043
|
||||
1775059245,2040150,db-read-1,0.10,1,89
|
||||
1775059245,2040151,db-read-2,0.00,0,85
|
||||
1775059245,2040152,db-read-3,0.20,2,98
|
||||
1775059245,2040153,db-read-4,0.10,1,79
|
||||
1775059245,2040154,db-write,0.00,0,4
|
||||
1775059245,2040155,event-worker,0.00,0,6
|
||||
1775059255,2040140,lws-main,54.10,541,63584
|
||||
1775059255,2040150,db-read-1,0.20,2,91
|
||||
1775059255,2040151,db-read-2,0.50,5,90
|
||||
1775059255,2040152,db-read-3,0.50,5,103
|
||||
1775059255,2040153,db-read-4,0.50,5,84
|
||||
1775059255,2040154,db-write,0.00,0,4
|
||||
1775059255,2040155,event-worker,0.00,0,6
|
||||
1775059265,2040140,lws-main,82.80,828,64412
|
||||
1775059265,2040150,db-read-1,0.00,0,91
|
||||
1775059265,2040151,db-read-2,0.00,0,90
|
||||
1775059265,2040152,db-read-3,0.00,0,103
|
||||
1775059265,2040153,db-read-4,0.00,0,84
|
||||
1775059265,2040154,db-write,0.00,0,4
|
||||
1775059265,2040155,event-worker,0.00,0,6
|
||||
1775059275,2040140,lws-main,99.90,999,65411
|
||||
1775059275,2040150,db-read-1,0.10,1,92
|
||||
1775059275,2040151,db-read-2,0.00,0,90
|
||||
1775059275,2040152,db-read-3,0.00,0,103
|
||||
1775059275,2040153,db-read-4,0.00,0,84
|
||||
1775059275,2040154,db-write,0.00,0,4
|
||||
1775059275,2040155,event-worker,0.00,0,6
|
||||
1775059285,2040140,lws-main,58.30,583,65994
|
||||
1775059285,2040150,db-read-1,0.00,0,92
|
||||
1775059285,2040151,db-read-2,0.10,1,91
|
||||
1775059285,2040152,db-read-3,0.10,1,104
|
||||
1775059285,2040153,db-read-4,0.00,0,84
|
||||
1775059285,2040154,db-write,0.00,0,4
|
||||
1775059285,2040155,event-worker,0.00,0,6
|
||||
1775059295,2040140,lws-main,42.60,426,66420
|
||||
1775059295,2040150,db-read-1,0.30,3,95
|
||||
1775059295,2040151,db-read-2,0.10,1,92
|
||||
1775059295,2040152,db-read-3,0.10,1,105
|
||||
1775059295,2040153,db-read-4,0.20,2,86
|
||||
1775059295,2040154,db-write,0.00,0,4
|
||||
1775059295,2040155,event-worker,0.00,0,6
|
||||
1775059305,2040140,lws-main,16.20,162,66582
|
||||
1775059305,2040150,db-read-1,0.20,2,97
|
||||
1775059305,2040151,db-read-2,0.00,0,92
|
||||
1775059305,2040152,db-read-3,0.10,1,106
|
||||
1775059305,2040153,db-read-4,0.10,1,87
|
||||
1775059305,2040154,db-write,0.00,0,4
|
||||
1775059305,2040155,event-worker,0.00,0,6
|
||||
1775059315,2040140,lws-main,100.30,1003,67585
|
||||
1775059315,2040150,db-read-1,0.00,0,97
|
||||
1775059315,2040151,db-read-2,0.00,0,92
|
||||
1775059315,2040152,db-read-3,0.00,0,106
|
||||
1775059315,2040153,db-read-4,0.00,0,87
|
||||
1775059315,2040154,db-write,0.00,0,4
|
||||
1775059315,2040155,event-worker,0.00,0,6
|
||||
1775059325,2040140,lws-main,66.00,660,68245
|
||||
1775059325,2040150,db-read-1,0.20,2,99
|
||||
1775059325,2040151,db-read-2,0.50,5,97
|
||||
1775059325,2040152,db-read-3,0.20,2,108
|
||||
1775059325,2040153,db-read-4,0.20,2,89
|
||||
1775059325,2040154,db-write,0.00,0,4
|
||||
1775059325,2040155,event-worker,0.00,0,6
|
||||
1775059335,2040140,lws-main,21.70,217,68462
|
||||
1775059335,2040150,db-read-1,0.00,0,99
|
||||
1775059335,2040151,db-read-2,0.00,0,97
|
||||
1775059335,2040152,db-read-3,0.00,0,108
|
||||
1775059335,2040153,db-read-4,0.00,0,89
|
||||
1775059335,2040154,db-write,0.00,0,4
|
||||
1775059335,2040155,event-worker,0.00,0,6
|
||||
1775059345,2040140,lws-main,47.00,470,68932
|
||||
1775059345,2040150,db-read-1,0.30,3,102
|
||||
1775059345,2040151,db-read-2,0.30,3,100
|
||||
1775059345,2040152,db-read-3,0.90,9,117
|
||||
1775059345,2040153,db-read-4,0.20,2,91
|
||||
1775059345,2040154,db-write,0.00,0,4
|
||||
1775059345,2040155,event-worker,0.00,0,6
|
||||
1775059355,2040140,lws-main,54.40,544,69476
|
||||
1775059355,2040150,db-read-1,0.00,0,102
|
||||
1775059355,2040151,db-read-2,0.00,0,100
|
||||
1775059355,2040152,db-read-3,0.00,0,117
|
||||
1775059355,2040153,db-read-4,0.00,0,91
|
||||
1775059355,2040154,db-write,0.00,0,4
|
||||
1775059355,2040155,event-worker,0.00,0,6
|
||||
1775059365,2040140,lws-main,99.70,997,70473
|
||||
1775059365,2040150,db-read-1,0.10,1,103
|
||||
1775059365,2040151,db-read-2,0.10,1,101
|
||||
1775059365,2040152,db-read-3,0.00,0,117
|
||||
1775059365,2040153,db-read-4,0.00,0,91
|
||||
1775059365,2040154,db-write,0.00,0,4
|
||||
1775059365,2040155,event-worker,0.00,0,6
|
||||
1775059375,2040140,lws-main,46.20,462,70935
|
||||
1775059375,2040150,db-read-1,0.20,2,105
|
||||
1775059375,2040151,db-read-2,0.10,1,102
|
||||
1775059375,2040152,db-read-3,0.40,4,121
|
||||
1775059375,2040153,db-read-4,0.20,2,93
|
||||
1775059375,2040154,db-write,0.00,0,4
|
||||
1775059375,2040155,event-worker,0.00,0,6
|
||||
1775059385,2040140,lws-main,69.00,690,71625
|
||||
1775059385,2040150,db-read-1,0.10,1,106
|
||||
1775059385,2040151,db-read-2,0.00,0,102
|
||||
1775059385,2040152,db-read-3,0.10,1,122
|
||||
1775059385,2040153,db-read-4,0.10,1,94
|
||||
1775059385,2040154,db-write,0.00,0,4
|
||||
1775059385,2040155,event-worker,0.00,0,6
|
||||
1775059395,2040140,lws-main,71.50,715,72340
|
||||
1775059395,2040150,db-read-1,0.00,0,106
|
||||
1775059395,2040151,db-read-2,0.20,2,104
|
||||
1775059395,2040152,db-read-3,0.10,1,123
|
||||
1775059395,2040153,db-read-4,0.30,3,97
|
||||
1775059395,2040154,db-write,0.00,0,4
|
||||
1775059395,2040155,event-worker,0.00,0,6
|
||||
1775059405,2040140,lws-main,100.10,1001,73341
|
||||
1775059405,2040150,db-read-1,0.20,2,108
|
||||
1775059405,2040151,db-read-2,0.00,0,104
|
||||
1775059405,2040152,db-read-3,0.00,0,123
|
||||
1775059405,2040153,db-read-4,0.20,2,99
|
||||
1775059405,2040154,db-write,0.00,0,4
|
||||
1775059405,2040155,event-worker,0.00,0,6
|
||||
1775059415,2040140,lws-main,100.20,1002,74343
|
||||
1775059415,2040150,db-read-1,0.30,3,111
|
||||
1775059415,2040151,db-read-2,0.20,2,106
|
||||
1775059415,2040152,db-read-3,0.20,2,125
|
||||
1775059415,2040153,db-read-4,0.30,3,102
|
||||
1775059415,2040154,db-write,0.10,1,5
|
||||
1775059415,2040155,event-worker,0.00,0,6
|
||||
1775059425,2040140,lws-main,97.40,974,75317
|
||||
1775059425,2040150,db-read-1,0.10,1,112
|
||||
1775059425,2040151,db-read-2,0.10,1,107
|
||||
1775059425,2040152,db-read-3,0.30,3,128
|
||||
1775059425,2040153,db-read-4,0.10,1,103
|
||||
1775059425,2040154,db-write,0.00,0,5
|
||||
1775059425,2040155,event-worker,0.00,0,6
|
||||
1775059435,2040140,lws-main,80.60,806,76123
|
||||
1775059435,2040150,db-read-1,0.10,1,113
|
||||
1775059435,2040151,db-read-2,0.20,2,109
|
||||
1775059435,2040152,db-read-3,0.10,1,129
|
||||
1775059435,2040153,db-read-4,0.10,1,104
|
||||
1775059435,2040154,db-write,0.00,0,5
|
||||
1775059435,2040155,event-worker,0.00,0,6
|
||||
1775059445,2040140,lws-main,40.70,407,76530
|
||||
1775059445,2040150,db-read-1,0.10,1,114
|
||||
1775059445,2040151,db-read-2,0.00,0,109
|
||||
1775059445,2040152,db-read-3,0.20,2,131
|
||||
1775059445,2040153,db-read-4,0.10,1,105
|
||||
1775059445,2040154,db-write,0.00,0,5
|
||||
1775059445,2040155,event-worker,0.00,0,6
|
||||
1775059456,2040140,lws-main,56.00,560,77090
|
||||
1775059456,2040150,db-read-1,0.00,0,114
|
||||
1775059456,2040151,db-read-2,0.20,2,111
|
||||
1775059456,2040152,db-read-3,0.00,0,131
|
||||
1775059456,2040153,db-read-4,0.10,1,106
|
||||
1775059456,2040154,db-write,0.00,0,5
|
||||
1775059456,2040155,event-worker,0.00,0,6
|
||||
1775059466,2040140,lws-main,53.20,532,77622
|
||||
1775059466,2040150,db-read-1,0.00,0,114
|
||||
1775059466,2040151,db-read-2,0.00,0,111
|
||||
1775059466,2040152,db-read-3,0.00,0,131
|
||||
1775059466,2040153,db-read-4,0.00,0,106
|
||||
1775059466,2040154,db-write,0.00,0,5
|
||||
1775059466,2040155,event-worker,0.00,0,6
|
||||
1775059476,2040140,lws-main,96.70,967,78589
|
||||
1775059476,2040150,db-read-1,0.00,0,114
|
||||
1775059476,2040151,db-read-2,0.00,0,111
|
||||
1775059476,2040152,db-read-3,0.00,0,131
|
||||
1775059476,2040153,db-read-4,0.00,0,106
|
||||
1775059476,2040154,db-write,0.00,0,5
|
||||
1775059476,2040155,event-worker,0.00,0,6
|
||||
1775059486,2040140,lws-main,67.60,676,79265
|
||||
1775059486,2040150,db-read-1,0.20,2,116
|
||||
1775059486,2040151,db-read-2,0.00,0,111
|
||||
1775059486,2040152,db-read-3,0.00,0,131
|
||||
1775059486,2040153,db-read-4,0.00,0,106
|
||||
1775059486,2040154,db-write,0.00,0,5
|
||||
1775059486,2040155,event-worker,0.00,0,6
|
||||
|
@@ -0,0 +1,14 @@
|
||||
==========================================
|
||||
Thread CPU Summary (avg + max over run)
|
||||
==========================================
|
||||
Run timestamp (UTC): 20260401_155603
|
||||
Duration: 600s, Interval: 10s
|
||||
|
||||
2040140 lws-main 68.71 100.30
|
||||
2040152 db-read-3 0.12 0.90
|
||||
2040150 db-read-1 0.11 0.40
|
||||
2040151 db-read-2 0.11 0.50
|
||||
2040153 db-read-4 0.10 0.50
|
||||
2040154 db-write 0.01 0.10
|
||||
2040155 event-worker 0.00 0.10
|
||||
TID THREAD AVG_CPU% MAX_CPU%
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
Profiling PID: 2083904
|
||||
CLK_TCK=100
|
||||
Starting perf record in background...
|
||||
perf_pid=2086924
|
||||
Done. Artifacts in /tmp/c_relay_thread_profile_20260401_181751
|
||||
@@ -0,0 +1,211 @@
|
||||
timestamp,tid,thread_name,cpu_pct,delta_ticks,total_ticks
|
||||
1775067472,2083904,lws-main,0.00,0,17710
|
||||
1775067472,2083906,db-read-1,0.00,0,33
|
||||
1775067472,2083907,db-read-2,0.00,0,29
|
||||
1775067472,2083908,db-read-3,0.00,0,31
|
||||
1775067472,2083909,db-read-4,0.00,0,41
|
||||
1775067472,2083910,db-write,0.00,0,0
|
||||
1775067472,2083911,event-worker,0.00,0,0
|
||||
1775067482,2083904,lws-main,53.40,534,18244
|
||||
1775067482,2083906,db-read-1,0.20,2,35
|
||||
1775067482,2083907,db-read-2,0.00,0,29
|
||||
1775067482,2083908,db-read-3,0.10,1,32
|
||||
1775067482,2083909,db-read-4,0.00,0,41
|
||||
1775067482,2083910,db-write,0.00,0,0
|
||||
1775067482,2083911,event-worker,0.00,0,0
|
||||
1775067492,2083904,lws-main,100.30,1003,19247
|
||||
1775067492,2083906,db-read-1,0.00,0,35
|
||||
1775067492,2083907,db-read-2,0.00,0,29
|
||||
1775067492,2083908,db-read-3,0.00,0,32
|
||||
1775067492,2083909,db-read-4,0.00,0,41
|
||||
1775067492,2083910,db-write,0.00,0,0
|
||||
1775067492,2083911,event-worker,0.00,0,0
|
||||
1775067502,2083904,lws-main,100.30,1003,20250
|
||||
1775067502,2083906,db-read-1,0.00,0,35
|
||||
1775067502,2083907,db-read-2,0.00,0,29
|
||||
1775067502,2083908,db-read-3,0.00,0,32
|
||||
1775067502,2083909,db-read-4,0.00,0,41
|
||||
1775067502,2083910,db-write,0.00,0,0
|
||||
1775067502,2083911,event-worker,0.00,0,0
|
||||
1775067512,2083904,lws-main,95.70,957,21207
|
||||
1775067512,2083906,db-read-1,0.00,0,35
|
||||
1775067512,2083907,db-read-2,0.10,1,30
|
||||
1775067512,2083908,db-read-3,0.00,0,32
|
||||
1775067512,2083909,db-read-4,0.00,0,41
|
||||
1775067512,2083910,db-write,0.00,0,0
|
||||
1775067512,2083911,event-worker,0.00,0,0
|
||||
1775067522,2083904,lws-main,73.70,737,21944
|
||||
1775067522,2083906,db-read-1,0.00,0,35
|
||||
1775067522,2083907,db-read-2,0.00,0,30
|
||||
1775067522,2083908,db-read-3,0.00,0,32
|
||||
1775067522,2083909,db-read-4,0.00,0,41
|
||||
1775067522,2083910,db-write,0.00,0,0
|
||||
1775067522,2083911,event-worker,0.00,0,0
|
||||
1775067532,2083904,lws-main,9.80,98,22042
|
||||
1775067532,2083906,db-read-1,0.00,0,35
|
||||
1775067532,2083907,db-read-2,0.00,0,30
|
||||
1775067532,2083908,db-read-3,0.10,1,33
|
||||
1775067532,2083909,db-read-4,0.10,1,42
|
||||
1775067532,2083910,db-write,0.00,0,0
|
||||
1775067532,2083911,event-worker,0.00,0,0
|
||||
1775067542,2083904,lws-main,28.80,288,22330
|
||||
1775067542,2083906,db-read-1,0.20,2,37
|
||||
1775067542,2083907,db-read-2,0.40,4,34
|
||||
1775067542,2083908,db-read-3,0.10,1,34
|
||||
1775067542,2083909,db-read-4,0.20,2,44
|
||||
1775067542,2083910,db-write,0.00,0,0
|
||||
1775067542,2083911,event-worker,0.00,0,0
|
||||
1775067552,2083904,lws-main,37.80,378,22708
|
||||
1775067552,2083906,db-read-1,0.20,2,39
|
||||
1775067552,2083907,db-read-2,0.20,2,36
|
||||
1775067552,2083908,db-read-3,0.40,4,38
|
||||
1775067552,2083909,db-read-4,0.10,1,45
|
||||
1775067552,2083910,db-write,0.00,0,0
|
||||
1775067552,2083911,event-worker,0.00,0,0
|
||||
1775067563,2083904,lws-main,89.40,894,23602
|
||||
1775067563,2083906,db-read-1,0.00,0,39
|
||||
1775067563,2083907,db-read-2,0.10,1,37
|
||||
1775067563,2083908,db-read-3,0.00,0,38
|
||||
1775067563,2083909,db-read-4,0.10,1,46
|
||||
1775067563,2083910,db-write,0.00,0,0
|
||||
1775067563,2083911,event-worker,0.00,0,0
|
||||
1775067573,2083904,lws-main,99.50,995,24597
|
||||
1775067573,2083906,db-read-1,0.00,0,39
|
||||
1775067573,2083907,db-read-2,0.00,0,37
|
||||
1775067573,2083908,db-read-3,0.00,0,38
|
||||
1775067573,2083909,db-read-4,0.00,0,46
|
||||
1775067573,2083910,db-write,0.00,0,0
|
||||
1775067573,2083911,event-worker,0.00,0,0
|
||||
1775067583,2083904,lws-main,100.20,1002,25599
|
||||
1775067583,2083906,db-read-1,0.00,0,39
|
||||
1775067583,2083907,db-read-2,0.00,0,37
|
||||
1775067583,2083908,db-read-3,0.00,0,38
|
||||
1775067583,2083909,db-read-4,0.00,0,46
|
||||
1775067583,2083910,db-write,0.00,0,0
|
||||
1775067583,2083911,event-worker,0.00,0,0
|
||||
1775067593,2083904,lws-main,100.20,1002,26601
|
||||
1775067593,2083906,db-read-1,0.00,0,39
|
||||
1775067593,2083907,db-read-2,0.00,0,37
|
||||
1775067593,2083908,db-read-3,0.00,0,38
|
||||
1775067593,2083909,db-read-4,0.00,0,46
|
||||
1775067593,2083910,db-write,0.00,0,0
|
||||
1775067593,2083911,event-worker,0.00,0,0
|
||||
1775067603,2083904,lws-main,89.20,892,27493
|
||||
1775067603,2083906,db-read-1,0.60,6,45
|
||||
1775067603,2083907,db-read-2,0.20,2,39
|
||||
1775067603,2083908,db-read-3,0.40,4,42
|
||||
1775067603,2083909,db-read-4,0.60,6,52
|
||||
1775067603,2083910,db-write,0.00,0,0
|
||||
1775067603,2083911,event-worker,0.00,0,0
|
||||
1775067613,2083904,lws-main,76.10,761,28254
|
||||
1775067613,2083906,db-read-1,0.00,0,45
|
||||
1775067613,2083907,db-read-2,0.00,0,39
|
||||
1775067613,2083908,db-read-3,0.00,0,42
|
||||
1775067613,2083909,db-read-4,0.00,0,52
|
||||
1775067613,2083910,db-write,0.00,0,0
|
||||
1775067613,2083911,event-worker,0.00,0,0
|
||||
1775067623,2083904,lws-main,97.90,979,29233
|
||||
1775067623,2083906,db-read-1,0.10,1,46
|
||||
1775067623,2083907,db-read-2,0.20,2,41
|
||||
1775067623,2083908,db-read-3,0.00,0,42
|
||||
1775067623,2083909,db-read-4,0.00,0,52
|
||||
1775067623,2083910,db-write,0.00,0,0
|
||||
1775067623,2083911,event-worker,0.00,0,0
|
||||
1775067633,2083904,lws-main,45.40,454,29687
|
||||
1775067633,2083906,db-read-1,0.00,0,46
|
||||
1775067633,2083907,db-read-2,0.00,0,41
|
||||
1775067633,2083908,db-read-3,0.10,1,43
|
||||
1775067633,2083909,db-read-4,0.00,0,52
|
||||
1775067633,2083910,db-write,0.00,0,0
|
||||
1775067633,2083911,event-worker,0.00,0,0
|
||||
1775067643,2083904,lws-main,100.40,1004,30691
|
||||
1775067643,2083906,db-read-1,0.00,0,46
|
||||
1775067643,2083907,db-read-2,0.00,0,41
|
||||
1775067643,2083908,db-read-3,0.00,0,43
|
||||
1775067643,2083909,db-read-4,0.00,0,52
|
||||
1775067643,2083910,db-write,0.00,0,0
|
||||
1775067643,2083911,event-worker,0.00,0,0
|
||||
1775067653,2083904,lws-main,100.20,1002,31693
|
||||
1775067653,2083906,db-read-1,0.10,1,47
|
||||
1775067653,2083907,db-read-2,0.00,0,41
|
||||
1775067653,2083908,db-read-3,0.10,1,44
|
||||
1775067653,2083909,db-read-4,0.10,1,53
|
||||
1775067653,2083910,db-write,0.00,0,0
|
||||
1775067653,2083911,event-worker,0.00,0,0
|
||||
1775067663,2083904,lws-main,68.40,684,32377
|
||||
1775067663,2083906,db-read-1,0.10,1,48
|
||||
1775067663,2083907,db-read-2,0.20,2,43
|
||||
1775067663,2083908,db-read-3,0.10,1,45
|
||||
1775067663,2083909,db-read-4,0.10,1,54
|
||||
1775067663,2083910,db-write,0.00,0,0
|
||||
1775067663,2083911,event-worker,0.00,0,0
|
||||
1775067673,2083904,lws-main,29.60,296,32673
|
||||
1775067673,2083906,db-read-1,0.10,1,49
|
||||
1775067673,2083907,db-read-2,0.10,1,44
|
||||
1775067673,2083908,db-read-3,0.20,2,47
|
||||
1775067673,2083909,db-read-4,0.20,2,56
|
||||
1775067673,2083910,db-write,0.00,0,0
|
||||
1775067673,2083911,event-worker,0.00,0,0
|
||||
1775067683,2083904,lws-main,58.30,583,33256
|
||||
1775067683,2083906,db-read-1,0.40,4,53
|
||||
1775067683,2083907,db-read-2,0.40,4,48
|
||||
1775067683,2083908,db-read-3,0.40,4,51
|
||||
1775067683,2083909,db-read-4,0.60,6,62
|
||||
1775067683,2083910,db-write,0.00,0,0
|
||||
1775067683,2083911,event-worker,0.10,1,1
|
||||
1775067693,2083904,lws-main,52.90,529,33785
|
||||
1775067693,2083906,db-read-1,0.10,1,54
|
||||
1775067693,2083907,db-read-2,0.20,2,50
|
||||
1775067693,2083908,db-read-3,0.10,1,52
|
||||
1775067693,2083909,db-read-4,0.10,1,63
|
||||
1775067693,2083910,db-write,0.00,0,0
|
||||
1775067693,2083911,event-worker,0.00,0,1
|
||||
1775067703,2083904,lws-main,43.10,431,34216
|
||||
1775067703,2083906,db-read-1,0.10,1,55
|
||||
1775067703,2083907,db-read-2,0.00,0,50
|
||||
1775067703,2083908,db-read-3,0.00,0,52
|
||||
1775067703,2083909,db-read-4,0.10,1,64
|
||||
1775067703,2083910,db-write,0.00,0,0
|
||||
1775067703,2083911,event-worker,0.00,0,1
|
||||
1775067713,2083904,lws-main,60.20,602,34818
|
||||
1775067713,2083906,db-read-1,0.00,0,55
|
||||
1775067713,2083907,db-read-2,0.00,0,50
|
||||
1775067713,2083908,db-read-3,0.00,0,52
|
||||
1775067713,2083909,db-read-4,0.00,0,64
|
||||
1775067713,2083910,db-write,0.00,0,0
|
||||
1775067713,2083911,event-worker,0.00,0,1
|
||||
1775067723,2083904,lws-main,100.40,1004,35822
|
||||
1775067723,2083906,db-read-1,0.00,0,55
|
||||
1775067723,2083907,db-read-2,0.00,0,50
|
||||
1775067723,2083908,db-read-3,0.00,0,52
|
||||
1775067723,2083909,db-read-4,0.00,0,64
|
||||
1775067723,2083910,db-write,0.00,0,0
|
||||
1775067723,2083911,event-worker,0.00,0,1
|
||||
1775067733,2083904,lws-main,100.20,1002,36824
|
||||
1775067733,2083906,db-read-1,0.00,0,55
|
||||
1775067733,2083907,db-read-2,0.00,0,50
|
||||
1775067733,2083908,db-read-3,0.00,0,52
|
||||
1775067733,2083909,db-read-4,0.00,0,64
|
||||
1775067733,2083910,db-write,0.00,0,0
|
||||
1775067733,2083911,event-worker,0.00,0,1
|
||||
1775067743,2083904,lws-main,100.20,1002,37826
|
||||
1775067743,2083906,db-read-1,0.00,0,55
|
||||
1775067743,2083907,db-read-2,0.00,0,50
|
||||
1775067743,2083908,db-read-3,0.20,2,54
|
||||
1775067743,2083909,db-read-4,0.00,0,64
|
||||
1775067743,2083910,db-write,0.00,0,0
|
||||
1775067743,2083911,event-worker,0.00,0,1
|
||||
1775067753,2083904,lws-main,93.70,937,38763
|
||||
1775067753,2083906,db-read-1,0.20,2,57
|
||||
1775067753,2083907,db-read-2,0.20,2,52
|
||||
1775067753,2083908,db-read-3,0.10,1,55
|
||||
1775067753,2083909,db-read-4,0.20,2,66
|
||||
1775067753,2083910,db-write,0.10,1,1
|
||||
1775067753,2083911,event-worker,0.00,0,1
|
||||
1775067763,2083904,lws-main,100.10,1001,39764
|
||||
1775067763,2083906,db-read-1,0.00,0,57
|
||||
1775067763,2083907,db-read-2,0.00,0,52
|
||||
1775067763,2083908,db-read-3,0.00,0,55
|
||||
1775067763,2083909,db-read-4,0.00,0,66
|
||||
1775067763,2083910,db-write,0.00,0,1
|
||||
1775067763,2083911,event-worker,0.00,0,1
|
||||
|
@@ -0,0 +1,14 @@
|
||||
==========================================
|
||||
Thread CPU Summary (avg + max over run)
|
||||
==========================================
|
||||
Run timestamp (UTC): 20260401_181751
|
||||
Duration: 300s, Interval: 10s
|
||||
|
||||
2083904 lws-main 73.51 100.40
|
||||
2083906 db-read-1 0.08 0.60
|
||||
2083907 db-read-2 0.08 0.40
|
||||
2083908 db-read-3 0.08 0.40
|
||||
2083909 db-read-4 0.08 0.60
|
||||
2083910 db-write 0.00 0.10
|
||||
2083911 event-worker 0.00 0.10
|
||||
TID THREAD AVG_CPU% MAX_CPU%
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
Profiling PID: 2083904
|
||||
CLK_TCK=100
|
||||
Starting perf record in background...
|
||||
perf_pid=2094527
|
||||
Done. Artifacts in /tmp/c_relay_thread_profile_20260401_183846
|
||||
@@ -0,0 +1,211 @@
|
||||
timestamp,tid,thread_name,cpu_pct,delta_ticks,total_ticks
|
||||
1775068727,2083904,lws-main,0.20,2,102303
|
||||
1775068727,2083906,db-read-1,0.00,0,153
|
||||
1775068727,2083907,db-read-2,0.00,0,145
|
||||
1775068727,2083908,db-read-3,0.00,0,145
|
||||
1775068727,2083909,db-read-4,0.00,0,160
|
||||
1775068727,2083910,db-write,0.00,0,4
|
||||
1775068727,2083911,event-worker,0.00,0,11
|
||||
1775068737,2083904,lws-main,100.20,1002,103305
|
||||
1775068737,2083906,db-read-1,0.00,0,153
|
||||
1775068737,2083907,db-read-2,0.10,1,146
|
||||
1775068737,2083908,db-read-3,0.00,0,145
|
||||
1775068737,2083909,db-read-4,0.00,0,160
|
||||
1775068737,2083910,db-write,0.00,0,4
|
||||
1775068737,2083911,event-worker,0.00,0,11
|
||||
1775068747,2083904,lws-main,100.30,1003,104308
|
||||
1775068747,2083906,db-read-1,0.00,0,153
|
||||
1775068747,2083907,db-read-2,0.00,0,146
|
||||
1775068747,2083908,db-read-3,0.10,1,146
|
||||
1775068747,2083909,db-read-4,0.10,1,161
|
||||
1775068747,2083910,db-write,0.00,0,4
|
||||
1775068747,2083911,event-worker,0.00,0,11
|
||||
1775068757,2083904,lws-main,100.20,1002,105310
|
||||
1775068757,2083906,db-read-1,0.00,0,153
|
||||
1775068757,2083907,db-read-2,0.00,0,146
|
||||
1775068757,2083908,db-read-3,0.00,0,146
|
||||
1775068757,2083909,db-read-4,0.00,0,161
|
||||
1775068757,2083910,db-write,0.00,0,4
|
||||
1775068757,2083911,event-worker,0.00,0,11
|
||||
1775068767,2083904,lws-main,100.20,1002,106312
|
||||
1775068767,2083906,db-read-1,0.00,0,153
|
||||
1775068767,2083907,db-read-2,0.00,0,146
|
||||
1775068767,2083908,db-read-3,0.00,0,146
|
||||
1775068767,2083909,db-read-4,0.00,0,161
|
||||
1775068767,2083910,db-write,0.00,0,4
|
||||
1775068767,2083911,event-worker,0.00,0,11
|
||||
1775068777,2083904,lws-main,81.10,811,107123
|
||||
1775068777,2083906,db-read-1,0.00,0,153
|
||||
1775068777,2083907,db-read-2,0.00,0,146
|
||||
1775068777,2083908,db-read-3,0.10,1,147
|
||||
1775068777,2083909,db-read-4,0.00,0,161
|
||||
1775068777,2083910,db-write,0.00,0,4
|
||||
1775068777,2083911,event-worker,0.00,0,11
|
||||
1775068787,2083904,lws-main,7.40,74,107197
|
||||
1775068787,2083906,db-read-1,0.00,0,153
|
||||
1775068787,2083907,db-read-2,0.00,0,146
|
||||
1775068787,2083908,db-read-3,0.00,0,147
|
||||
1775068787,2083909,db-read-4,0.00,0,161
|
||||
1775068787,2083910,db-write,0.00,0,4
|
||||
1775068787,2083911,event-worker,0.00,0,11
|
||||
1775068797,2083904,lws-main,50.30,503,107700
|
||||
1775068797,2083906,db-read-1,0.30,3,156
|
||||
1775068797,2083907,db-read-2,0.30,3,149
|
||||
1775068797,2083908,db-read-3,0.30,3,150
|
||||
1775068797,2083909,db-read-4,0.20,2,163
|
||||
1775068797,2083910,db-write,0.00,0,4
|
||||
1775068797,2083911,event-worker,0.00,0,11
|
||||
1775068807,2083904,lws-main,58.00,580,108280
|
||||
1775068807,2083906,db-read-1,0.00,0,156
|
||||
1775068807,2083907,db-read-2,0.00,0,149
|
||||
1775068807,2083908,db-read-3,0.00,0,150
|
||||
1775068807,2083909,db-read-4,0.00,0,163
|
||||
1775068807,2083910,db-write,0.00,0,4
|
||||
1775068807,2083911,event-worker,0.00,0,11
|
||||
1775068818,2083904,lws-main,58.40,584,108864
|
||||
1775068818,2083906,db-read-1,0.10,1,157
|
||||
1775068818,2083907,db-read-2,0.10,1,150
|
||||
1775068818,2083908,db-read-3,0.10,1,151
|
||||
1775068818,2083909,db-read-4,0.30,3,166
|
||||
1775068818,2083910,db-write,0.00,0,4
|
||||
1775068818,2083911,event-worker,0.00,0,11
|
||||
1775068828,2083904,lws-main,43.30,433,109297
|
||||
1775068828,2083906,db-read-1,0.00,0,157
|
||||
1775068828,2083907,db-read-2,0.00,0,150
|
||||
1775068828,2083908,db-read-3,0.00,0,151
|
||||
1775068828,2083909,db-read-4,0.20,2,168
|
||||
1775068828,2083910,db-write,0.00,0,4
|
||||
1775068828,2083911,event-worker,0.20,2,13
|
||||
1775068838,2083904,lws-main,29.00,290,109587
|
||||
1775068838,2083906,db-read-1,0.20,2,159
|
||||
1775068838,2083907,db-read-2,0.00,0,150
|
||||
1775068838,2083908,db-read-3,0.10,1,152
|
||||
1775068838,2083909,db-read-4,0.00,0,168
|
||||
1775068838,2083910,db-write,0.00,0,4
|
||||
1775068838,2083911,event-worker,0.00,0,13
|
||||
1775068848,2083904,lws-main,37.00,370,109957
|
||||
1775068848,2083906,db-read-1,0.00,0,159
|
||||
1775068848,2083907,db-read-2,0.10,1,151
|
||||
1775068848,2083908,db-read-3,0.00,0,152
|
||||
1775068848,2083909,db-read-4,0.00,0,168
|
||||
1775068848,2083910,db-write,0.00,0,4
|
||||
1775068848,2083911,event-worker,0.00,0,13
|
||||
1775068858,2083904,lws-main,48.40,484,110441
|
||||
1775068858,2083906,db-read-1,0.10,1,160
|
||||
1775068858,2083907,db-read-2,0.10,1,152
|
||||
1775068858,2083908,db-read-3,0.10,1,153
|
||||
1775068858,2083909,db-read-4,0.20,2,170
|
||||
1775068858,2083910,db-write,0.00,0,4
|
||||
1775068858,2083911,event-worker,0.00,0,13
|
||||
1775068868,2083904,lws-main,45.00,450,110891
|
||||
1775068868,2083906,db-read-1,0.00,0,160
|
||||
1775068868,2083907,db-read-2,0.10,1,153
|
||||
1775068868,2083908,db-read-3,0.00,0,153
|
||||
1775068868,2083909,db-read-4,0.00,0,170
|
||||
1775068868,2083910,db-write,0.00,0,4
|
||||
1775068868,2083911,event-worker,0.00,0,13
|
||||
1775068878,2083904,lws-main,21.00,210,111101
|
||||
1775068878,2083906,db-read-1,0.00,0,160
|
||||
1775068878,2083907,db-read-2,0.00,0,153
|
||||
1775068878,2083908,db-read-3,0.00,0,153
|
||||
1775068878,2083909,db-read-4,0.00,0,170
|
||||
1775068878,2083910,db-write,0.00,0,4
|
||||
1775068878,2083911,event-worker,0.00,0,13
|
||||
1775068888,2083904,lws-main,36.40,364,111465
|
||||
1775068888,2083906,db-read-1,0.00,0,160
|
||||
1775068888,2083907,db-read-2,0.30,3,156
|
||||
1775068888,2083908,db-read-3,0.10,1,154
|
||||
1775068888,2083909,db-read-4,0.00,0,170
|
||||
1775068888,2083910,db-write,0.00,0,4
|
||||
1775068888,2083911,event-worker,0.00,0,13
|
||||
1775068898,2083904,lws-main,44.10,441,111906
|
||||
1775068898,2083906,db-read-1,0.00,0,160
|
||||
1775068898,2083907,db-read-2,0.00,0,156
|
||||
1775068898,2083908,db-read-3,0.00,0,154
|
||||
1775068898,2083909,db-read-4,0.00,0,170
|
||||
1775068898,2083910,db-write,0.00,0,4
|
||||
1775068898,2083911,event-worker,0.00,0,13
|
||||
1775068908,2083904,lws-main,19.70,197,112103
|
||||
1775068908,2083906,db-read-1,0.20,2,162
|
||||
1775068908,2083907,db-read-2,0.10,1,157
|
||||
1775068908,2083908,db-read-3,0.00,0,154
|
||||
1775068908,2083909,db-read-4,0.10,1,171
|
||||
1775068908,2083910,db-write,0.00,0,4
|
||||
1775068908,2083911,event-worker,0.00,0,13
|
||||
1775068918,2083904,lws-main,83.20,832,112935
|
||||
1775068918,2083906,db-read-1,0.30,3,165
|
||||
1775068918,2083907,db-read-2,0.30,3,160
|
||||
1775068918,2083908,db-read-3,0.20,2,156
|
||||
1775068918,2083909,db-read-4,0.20,2,173
|
||||
1775068918,2083910,db-write,0.00,0,4
|
||||
1775068918,2083911,event-worker,0.00,0,13
|
||||
1775068928,2083904,lws-main,37.40,374,113309
|
||||
1775068928,2083906,db-read-1,0.00,0,165
|
||||
1775068928,2083907,db-read-2,0.00,0,160
|
||||
1775068928,2083908,db-read-3,0.10,1,157
|
||||
1775068928,2083909,db-read-4,0.00,0,173
|
||||
1775068928,2083910,db-write,0.00,0,4
|
||||
1775068928,2083911,event-worker,0.00,0,13
|
||||
1775068938,2083904,lws-main,59.80,598,113907
|
||||
1775068938,2083906,db-read-1,0.00,0,165
|
||||
1775068938,2083907,db-read-2,0.00,0,160
|
||||
1775068938,2083908,db-read-3,0.10,1,158
|
||||
1775068938,2083909,db-read-4,0.00,0,173
|
||||
1775068938,2083910,db-write,0.00,0,4
|
||||
1775068938,2083911,event-worker,0.00,0,13
|
||||
1775068948,2083904,lws-main,61.20,612,114519
|
||||
1775068948,2083906,db-read-1,0.30,3,168
|
||||
1775068948,2083907,db-read-2,0.20,2,162
|
||||
1775068948,2083908,db-read-3,0.10,1,159
|
||||
1775068948,2083909,db-read-4,0.40,4,177
|
||||
1775068948,2083910,db-write,0.00,0,4
|
||||
1775068948,2083911,event-worker,0.10,1,14
|
||||
1775068958,2083904,lws-main,65.30,653,115172
|
||||
1775068958,2083906,db-read-1,0.00,0,168
|
||||
1775068958,2083907,db-read-2,0.10,1,163
|
||||
1775068958,2083908,db-read-3,0.10,1,160
|
||||
1775068958,2083909,db-read-4,0.20,2,179
|
||||
1775068958,2083910,db-write,0.00,0,4
|
||||
1775068958,2083911,event-worker,0.00,0,14
|
||||
1775068968,2083904,lws-main,23.00,230,115402
|
||||
1775068968,2083906,db-read-1,0.00,0,168
|
||||
1775068968,2083907,db-read-2,0.00,0,163
|
||||
1775068968,2083908,db-read-3,0.00,0,160
|
||||
1775068968,2083909,db-read-4,0.00,0,179
|
||||
1775068968,2083910,db-write,0.00,0,4
|
||||
1775068968,2083911,event-worker,0.00,0,14
|
||||
1775068978,2083904,lws-main,79.80,798,116200
|
||||
1775068978,2083906,db-read-1,0.40,4,172
|
||||
1775068978,2083907,db-read-2,0.10,1,164
|
||||
1775068978,2083908,db-read-3,0.10,1,161
|
||||
1775068978,2083909,db-read-4,0.20,2,181
|
||||
1775068978,2083910,db-write,0.00,0,4
|
||||
1775068978,2083911,event-worker,0.00,0,14
|
||||
1775068988,2083904,lws-main,70.70,707,116907
|
||||
1775068988,2083906,db-read-1,0.00,0,172
|
||||
1775068988,2083907,db-read-2,0.00,0,164
|
||||
1775068988,2083908,db-read-3,0.10,1,162
|
||||
1775068988,2083909,db-read-4,0.00,0,181
|
||||
1775068988,2083910,db-write,0.00,0,4
|
||||
1775068988,2083911,event-worker,0.00,0,14
|
||||
1775068998,2083904,lws-main,23.00,230,117137
|
||||
1775068998,2083906,db-read-1,0.00,0,172
|
||||
1775068998,2083907,db-read-2,0.00,0,164
|
||||
1775068998,2083908,db-read-3,0.00,0,162
|
||||
1775068998,2083909,db-read-4,0.00,0,181
|
||||
1775068998,2083910,db-write,0.00,0,4
|
||||
1775068998,2083911,event-worker,0.00,0,14
|
||||
1775069008,2083904,lws-main,66.00,660,117797
|
||||
1775069008,2083906,db-read-1,0.30,3,175
|
||||
1775069008,2083907,db-read-2,0.40,4,168
|
||||
1775069008,2083908,db-read-3,0.40,4,166
|
||||
1775069008,2083909,db-read-4,0.30,3,184
|
||||
1775069008,2083910,db-write,0.00,0,4
|
||||
1775069008,2083911,event-worker,0.00,0,14
|
||||
1775069018,2083904,lws-main,80.00,800,118597
|
||||
1775069018,2083906,db-read-1,0.00,0,175
|
||||
1775069018,2083907,db-read-2,0.00,0,168
|
||||
1775069018,2083908,db-read-3,0.00,0,166
|
||||
1775069018,2083909,db-read-4,0.00,0,184
|
||||
1775069018,2083910,db-write,0.00,0,4
|
||||
1775069018,2083911,event-worker,0.00,0,14
|
||||
|
@@ -0,0 +1,14 @@
|
||||
==========================================
|
||||
Thread CPU Summary (avg + max over run)
|
||||
==========================================
|
||||
Run timestamp (UTC): 20260401_183846
|
||||
Duration: 300s, Interval: 10s
|
||||
|
||||
2083904 lws-main 54.32 100.30
|
||||
2083907 db-read-2 0.08 0.40
|
||||
2083909 db-read-4 0.08 0.40
|
||||
2083906 db-read-1 0.07 0.40
|
||||
2083908 db-read-3 0.07 0.40
|
||||
2083911 event-worker 0.01 0.20
|
||||
2083910 db-write 0.00 0.00
|
||||
TID THREAD AVG_CPU% MAX_CPU%
|
||||
Reference in New Issue
Block a user