v1.1.1 - Fix NOTICE message silent failure - add pss parameter to send_notice_message()

Previously, send_notice_message() called queue_message() with NULL pss, causing
all NOTICE messages to fail silently. This affected filter validation errors
(e.g., invalid kinds > 65535 per NIP-01) where clients received no response.

Changes:
- Updated send_notice_message() signature to accept struct per_session_data* pss
- Updated 37 call sites across websockets.c (31) and nip042.c (6)
- Updated forward declarations in main.c, websockets.c, and nip042.c
- Added tests/invalid_kind_test.sh to verify NOTICE responses for invalid filters

Fixes issue where REQ with kinds:[99999] received no response instead of NOTICE.
This commit is contained in:
Your Name
2026-01-31 15:48:29 -04:00
parent 35b1461ff6
commit e8f8e3b0cf
7 changed files with 146 additions and 45 deletions

BIN
c-relay-1.1.0.tar.gz Normal file

Binary file not shown.

View File

@@ -1 +1 @@
1683148
1688521

View File

@@ -132,7 +132,7 @@ static void free_bind_params(char** params, int count) {
int is_authorized_admin_event(cJSON* event, char* error_message, size_t error_size);
// Forward declaration for NOTICE message support
void send_notice_message(struct lws* wsi, const char* message);
void send_notice_message(struct lws* wsi, struct per_session_data* pss, const char* message);
// Forward declarations for NIP-42 authentication functions
void send_nip42_auth_challenge(struct lws* wsi, struct per_session_data* pss);
@@ -207,7 +207,7 @@ void signal_handler(int sig) {
/////////////////////////////////////////////////////////////////////////////////////////
// Send NOTICE message to client (NIP-01)
void send_notice_message(struct lws* wsi, const char* message) {
void send_notice_message(struct lws* wsi, struct per_session_data* pss, const char* message) {
if (!wsi || !message) return;
cJSON* notice_msg = cJSON_CreateArray();
@@ -218,7 +218,7 @@ void send_notice_message(struct lws* wsi, const char* message) {
if (msg_str) {
size_t msg_len = strlen(msg_str);
// Use proper message queue system instead of direct lws_write
if (queue_message(wsi, NULL, msg_str, msg_len, LWS_WRITE_TEXT) != 0) {
if (queue_message(wsi, pss, msg_str, msg_len, LWS_WRITE_TEXT) != 0) {
DEBUG_ERROR("Failed to queue NOTICE message");
}
free(msg_str);

View File

@@ -12,8 +12,8 @@
// Version information (auto-updated by build system)
#define VERSION_MAJOR 1
#define VERSION_MINOR 1
#define VERSION_PATCH 0
#define VERSION "v1.1.0"
#define VERSION_PATCH 1
#define VERSION "v1.1.1"
// Avoid VERSION_MAJOR redefinition warning from nostr_core_lib
#undef VERSION_MAJOR

View File

@@ -16,7 +16,7 @@
// Forward declaration for notice message function
void send_notice_message(struct lws* wsi, const char* message);
void send_notice_message(struct lws* wsi, struct per_session_data* pss, const char* message);
// Forward declarations for NIP-42 functions from request_validator.c
int nostr_nip42_generate_challenge(char *challenge_buffer, size_t buffer_size);
@@ -34,7 +34,7 @@ void send_nip42_auth_challenge(struct lws* wsi, struct per_session_data* pss) {
char challenge[65];
if (nostr_nip42_generate_challenge(challenge, sizeof(challenge)) != 0) {
DEBUG_ERROR("Failed to generate NIP-42 challenge");
send_notice_message(wsi, "Authentication temporarily unavailable");
send_notice_message(wsi, pss, "Authentication temporarily unavailable");
return;
}
@@ -71,7 +71,7 @@ void handle_nip42_auth_signed_event(struct lws* wsi, struct per_session_data* ps
// Serialize event for validation
char* event_json = cJSON_Print(auth_event);
if (!event_json) {
send_notice_message(wsi, "Invalid authentication event format");
send_notice_message(wsi, pss, "Invalid authentication event format");
return;
}
@@ -86,7 +86,7 @@ void handle_nip42_auth_signed_event(struct lws* wsi, struct per_session_data* ps
time_t current_time = time(NULL);
if (current_time > challenge_expires) {
free(event_json);
send_notice_message(wsi, "Authentication challenge expired, please retry");
send_notice_message(wsi, pss, "Authentication challenge expired, please retry");
DEBUG_WARN("NIP-42 authentication failed: challenge expired");
return;
}
@@ -127,7 +127,7 @@ void handle_nip42_auth_signed_event(struct lws* wsi, struct per_session_data* ps
pss->auth_challenge_sent = 0;
pthread_mutex_unlock(&pss->session_lock);
send_notice_message(wsi, "NIP-42 authentication successful");
send_notice_message(wsi, pss, "NIP-42 authentication successful");
} else {
// Authentication failed
char error_msg[256];
@@ -135,7 +135,7 @@ void handle_nip42_auth_signed_event(struct lws* wsi, struct per_session_data* ps
"NIP-42 authentication failed (error code: %d)", result);
DEBUG_WARN(error_msg);
send_notice_message(wsi, "NIP-42 authentication failed - invalid signature or challenge");
send_notice_message(wsi, pss, "NIP-42 authentication failed - invalid signature or challenge");
}
}
@@ -146,5 +146,5 @@ void handle_nip42_auth_challenge_response(struct lws* wsi, struct per_session_da
// NIP-42 doesn't typically use challenge responses from client to server
// This is reserved for potential future use or protocol extensions
DEBUG_WARN("Received unexpected challenge response from client (not part of standard NIP-42 flow)");
send_notice_message(wsi, "Challenge responses are not supported - please send signed authentication event");
send_notice_message(wsi, pss, "Challenge responses are not supported - please send signed authentication event");
}

View File

@@ -94,7 +94,7 @@ void record_malformed_request(struct per_session_data *pss);
int validate_filter_array(cJSON* filters, char* error_message, size_t error_size);
// Forward declarations for NOTICE message support
void send_notice_message(struct lws* wsi, const char* message);
void send_notice_message(struct lws* wsi, struct per_session_data* pss, const char* message);
// Configuration functions from config.c
extern int get_config_bool(const char* key, int default_value);
@@ -475,7 +475,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Check if client is rate limited for malformed requests
if (is_client_rate_limited_for_malformed_requests(pss)) {
send_notice_message(wsi, "error: too many malformed requests - temporarily blocked");
send_notice_message(wsi, pss, "error: too many malformed requests - temporarily blocked");
return 0;
}
@@ -522,7 +522,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
pss->reassembly_size = 0;
pss->reassembly_capacity = 0;
pss->reassembly_active = 0;
send_notice_message(wsi, "error: message too large - memory allocation failed");
send_notice_message(wsi, pss, "error: message too large - memory allocation failed");
return 0;
}
pss->reassembly_buffer = new_buffer;
@@ -895,7 +895,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
if (!pss->auth_challenge_sent) {
send_nip42_auth_challenge(wsi, pss);
} else {
send_notice_message(wsi, "NIP-42 authentication required for subscriptions");
send_notice_message(wsi, pss, "NIP-42 authentication required for subscriptions");
DEBUG_WARN("REQ rejected: NIP-42 authentication required");
}
cJSON_Delete(json);
@@ -917,7 +917,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Validate subscription ID before processing
if (!subscription_id) {
DEBUG_TRACE("REQ rejected: NULL subscription ID");
send_notice_message(wsi, "error: invalid subscription ID");
send_notice_message(wsi, pss, "error: invalid subscription ID");
DEBUG_WARN("REQ rejected: NULL subscription ID");
record_malformed_request(pss);
cJSON_Delete(json);
@@ -929,7 +929,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Validate subscription ID
if (!validate_subscription_id(subscription_id)) {
DEBUG_TRACE("REQ rejected: invalid subscription ID format");
send_notice_message(wsi, "error: invalid subscription ID");
send_notice_message(wsi, pss, "error: invalid subscription ID");
DEBUG_WARN("REQ rejected: invalid subscription ID");
cJSON_Delete(json);
// Note: complete_message points to reassembly_buffer, which is managed separately
@@ -943,7 +943,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
cJSON* filters = cJSON_CreateArray();
if (!filters) {
DEBUG_TRACE("REQ failed: could not create filters array");
send_notice_message(wsi, "error: failed to process filters");
send_notice_message(wsi, pss, "error: failed to process filters");
DEBUG_ERROR("REQ failed: could not create filters array");
cJSON_Delete(json);
// Note: complete_message points to reassembly_buffer, which is managed separately
@@ -967,7 +967,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
char filter_error[512] = {0};
if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) {
DEBUG_TRACE("REQ rejected: filter validation failed - %s", filter_error);
send_notice_message(wsi, filter_error);
send_notice_message(wsi, pss, filter_error);
DEBUG_WARN("REQ rejected: invalid filters");
record_malformed_request(pss);
cJSON_Delete(filters);
@@ -1014,7 +1014,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
cJSON_Delete(eose_response);
}
} else {
send_notice_message(wsi, "error: missing or invalid subscription ID in REQ");
send_notice_message(wsi, pss, "error: missing or invalid subscription ID in REQ");
DEBUG_WARN("REQ rejected: missing or invalid subscription ID");
}
} else if (strcmp(msg_type, "COUNT") == 0) {
@@ -1023,7 +1023,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
if (!pss->auth_challenge_sent) {
send_nip42_auth_challenge(wsi, pss);
} else {
send_notice_message(wsi, "NIP-42 authentication required for count requests");
send_notice_message(wsi, pss, "NIP-42 authentication required for count requests");
DEBUG_WARN("COUNT rejected: NIP-42 authentication required");
}
cJSON_Delete(json);
@@ -1051,7 +1051,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Validate filters before processing
char filter_error[512] = {0};
if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) {
send_notice_message(wsi, filter_error);
send_notice_message(wsi, pss, filter_error);
DEBUG_WARN("COUNT rejected: invalid filters");
record_malformed_request(pss);
cJSON_Delete(filters);
@@ -1074,7 +1074,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Validate subscription ID before processing
if (!subscription_id) {
send_notice_message(wsi, "error: invalid subscription ID in CLOSE");
send_notice_message(wsi, pss, "error: invalid subscription ID in CLOSE");
DEBUG_WARN("CLOSE rejected: NULL subscription ID");
cJSON_Delete(json);
// Note: complete_message points to reassembly_buffer, which is managed separately
@@ -1084,7 +1084,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Validate subscription ID
if (!validate_subscription_id(subscription_id)) {
send_notice_message(wsi, "error: invalid subscription ID in CLOSE");
send_notice_message(wsi, pss, "error: invalid subscription ID in CLOSE");
DEBUG_WARN("CLOSE rejected: invalid subscription ID");
cJSON_Delete(json);
// Note: complete_message points to reassembly_buffer, which is managed separately
@@ -1130,7 +1130,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Subscription closed
} else {
send_notice_message(wsi, "error: missing or invalid subscription ID in CLOSE");
send_notice_message(wsi, pss, "error: missing or invalid subscription ID in CLOSE");
DEBUG_WARN("CLOSE rejected: missing or invalid subscription ID");
}
} else if (strcmp(msg_type, "AUTH") == 0) {
@@ -1145,11 +1145,11 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// AUTH signed event: ["AUTH", <event>] (standard NIP-42)
handle_nip42_auth_signed_event(wsi, pss, auth_payload);
} else {
send_notice_message(wsi, "Invalid AUTH message format");
send_notice_message(wsi, pss, "Invalid AUTH message format");
DEBUG_WARN("Received AUTH message with invalid payload type");
}
} else {
send_notice_message(wsi, "AUTH message requires payload");
send_notice_message(wsi, pss, "AUTH message requires payload");
DEBUG_WARN("Received AUTH message without payload");
}
} else {
@@ -1157,7 +1157,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
char unknown_msg[128];
snprintf(unknown_msg, sizeof(unknown_msg), "Unknown message type: %.32s", msg_type);
DEBUG_WARN(unknown_msg);
send_notice_message(wsi, "Unknown message type");
send_notice_message(wsi, pss, "Unknown message type");
}
}
}
@@ -1254,7 +1254,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
snprintf(auth_msg, sizeof(auth_msg),
"NIP-42 authentication required for event kind %d", event_kind);
}
send_notice_message(wsi, auth_msg);
send_notice_message(wsi, pss, auth_msg);
DEBUG_WARN("Event rejected: NIP-42 authentication required for kind");
}
cJSON_Delete(json);
@@ -1597,7 +1597,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
if (!pss->auth_challenge_sent) {
send_nip42_auth_challenge(wsi, pss);
} else {
send_notice_message(wsi, "NIP-42 authentication required for subscriptions");
send_notice_message(wsi, pss, "NIP-42 authentication required for subscriptions");
DEBUG_WARN("REQ rejected: NIP-42 authentication required");
}
cJSON_Delete(json);
@@ -1618,7 +1618,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Validate subscription ID before processing
if (!subscription_id) {
DEBUG_TRACE("REQ rejected: NULL subscription ID");
send_notice_message(wsi, "error: invalid subscription ID");
send_notice_message(wsi, pss, "error: invalid subscription ID");
DEBUG_WARN("REQ rejected: NULL subscription ID");
record_malformed_request(pss);
cJSON_Delete(json);
@@ -1629,7 +1629,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Validate subscription ID
if (!validate_subscription_id(subscription_id)) {
DEBUG_TRACE("REQ rejected: invalid subscription ID format");
send_notice_message(wsi, "error: invalid subscription ID");
send_notice_message(wsi, pss, "error: invalid subscription ID");
DEBUG_WARN("REQ rejected: invalid subscription ID");
cJSON_Delete(json);
free(message);
@@ -1642,7 +1642,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
cJSON* filters = cJSON_CreateArray();
if (!filters) {
DEBUG_TRACE("REQ failed: could not create filters array");
send_notice_message(wsi, "error: failed to process filters");
send_notice_message(wsi, pss, "error: failed to process filters");
DEBUG_ERROR("REQ failed: could not create filters array");
cJSON_Delete(json);
free(message);
@@ -1665,7 +1665,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
char filter_error[512] = {0};
if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) {
DEBUG_TRACE("REQ rejected: filter validation failed - %s", filter_error);
send_notice_message(wsi, filter_error);
send_notice_message(wsi, pss, filter_error);
DEBUG_WARN("REQ rejected: invalid filters");
record_malformed_request(pss);
cJSON_Delete(filters);
@@ -1711,7 +1711,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
cJSON_Delete(eose_response);
}
} else {
send_notice_message(wsi, "error: missing or invalid subscription ID in REQ");
send_notice_message(wsi, pss, "error: missing or invalid subscription ID in REQ");
DEBUG_WARN("REQ rejected: missing or invalid subscription ID");
}
} else if (strcmp(msg_type, "COUNT") == 0) {
@@ -1720,7 +1720,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
if (!pss->auth_challenge_sent) {
send_nip42_auth_challenge(wsi, pss);
} else {
send_notice_message(wsi, "NIP-42 authentication required for count requests");
send_notice_message(wsi, pss, "NIP-42 authentication required for count requests");
DEBUG_WARN("COUNT rejected: NIP-42 authentication required");
}
cJSON_Delete(json);
@@ -1747,7 +1747,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Validate filters before processing
char filter_error[512] = {0};
if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) {
send_notice_message(wsi, filter_error);
send_notice_message(wsi, pss, filter_error);
DEBUG_WARN("COUNT rejected: invalid filters");
record_malformed_request(pss);
cJSON_Delete(filters);
@@ -1769,7 +1769,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Validate subscription ID before processing
if (!subscription_id) {
send_notice_message(wsi, "error: invalid subscription ID in CLOSE");
send_notice_message(wsi, pss, "error: invalid subscription ID in CLOSE");
DEBUG_WARN("CLOSE rejected: NULL subscription ID");
cJSON_Delete(json);
free(message);
@@ -1778,7 +1778,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Validate subscription ID
if (!validate_subscription_id(subscription_id)) {
send_notice_message(wsi, "error: invalid subscription ID in CLOSE");
send_notice_message(wsi, pss, "error: invalid subscription ID in CLOSE");
DEBUG_WARN("CLOSE rejected: invalid subscription ID");
cJSON_Delete(json);
free(message);
@@ -1823,7 +1823,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Subscription closed
} else {
send_notice_message(wsi, "error: missing or invalid subscription ID in CLOSE");
send_notice_message(wsi, pss, "error: missing or invalid subscription ID in CLOSE");
DEBUG_WARN("CLOSE rejected: missing or invalid subscription ID");
}
} else if (strcmp(msg_type, "AUTH") == 0) {
@@ -1838,11 +1838,11 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// AUTH signed event: ["AUTH", <event>] (standard NIP-42)
handle_nip42_auth_signed_event(wsi, pss, auth_payload);
} else {
send_notice_message(wsi, "Invalid AUTH message format");
send_notice_message(wsi, pss, "Invalid AUTH message format");
DEBUG_WARN("Received AUTH message with invalid payload type");
}
} else {
send_notice_message(wsi, "AUTH message requires payload");
send_notice_message(wsi, pss, "AUTH message requires payload");
DEBUG_WARN("Received AUTH message without payload");
}
} else {
@@ -1850,7 +1850,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
char unknown_msg[128];
snprintf(unknown_msg, sizeof(unknown_msg), "Unknown message type: %.32s", msg_type);
DEBUG_WARN(unknown_msg);
send_notice_message(wsi, "Unknown message type");
send_notice_message(wsi, pss, "Unknown message type");
}
}
}

101
tests/invalid_kind_test.sh Executable file
View File

@@ -0,0 +1,101 @@
#!/bin/bash
# Test for invalid kind filter validation and NOTICE response
# This test verifies that the relay properly responds with a NOTICE message
# when a REQ contains an invalid kind value (> 65535 per NIP-01)
RELAY_URL="ws://localhost:8888"
TEST_NAME="Invalid Kind Filter Test"
echo "=========================================="
echo "$TEST_NAME"
echo "=========================================="
echo ""
# Test 1: Send REQ with invalid kind (99999 > 65535)
echo "Test 1: REQ with invalid kind 99999 (should receive NOTICE)"
echo "---"
RESPONSE=$(timeout 3 websocat "$RELAY_URL" <<EOF
["REQ","test-invalid-kind",{"kinds":[99999],"limit":0}]
EOF
)
echo "Response: $RESPONSE"
if echo "$RESPONSE" | grep -q "NOTICE"; then
echo "✓ PASS: Received NOTICE for invalid kind"
if echo "$RESPONSE" | grep -qi "kind"; then
echo "✓ PASS: NOTICE mentions kind validation"
else
echo "⚠ WARNING: NOTICE doesn't mention kind (but NOTICE was sent)"
fi
else
echo "✗ FAIL: No NOTICE received for invalid kind"
exit 1
fi
echo ""
# Test 2: Send REQ with valid kind (should receive EOSE)
echo "Test 2: REQ with valid kind 1 (should receive EOSE)"
echo "---"
RESPONSE=$(timeout 3 websocat "$RELAY_URL" <<EOF
["REQ","test-valid-kind",{"kinds":[1],"limit":0}]
EOF
)
echo "Response: $RESPONSE"
if echo "$RESPONSE" | grep -q "EOSE"; then
echo "✓ PASS: Received EOSE for valid kind"
else
echo "✗ FAIL: No EOSE received for valid kind"
exit 1
fi
echo ""
# Test 3: Send REQ with kind at boundary (65535 - should be valid)
echo "Test 3: REQ with boundary kind 65535 (should receive EOSE)"
echo "---"
RESPONSE=$(timeout 3 websocat "$RELAY_URL" <<EOF
["REQ","test-boundary-kind",{"kinds":[65535],"limit":0}]
EOF
)
echo "Response: $RESPONSE"
if echo "$RESPONSE" | grep -q "EOSE"; then
echo "✓ PASS: Received EOSE for boundary kind 65535"
else
echo "✗ FAIL: No EOSE received for boundary kind"
exit 1
fi
echo ""
# Test 4: Send REQ with kind just over boundary (65536 - should receive NOTICE)
echo "Test 4: REQ with over-boundary kind 65536 (should receive NOTICE)"
echo "---"
RESPONSE=$(timeout 3 websocat "$RELAY_URL" <<EOF
["REQ","test-over-boundary",{"kinds":[65536],"limit":0}]
EOF
)
echo "Response: $RESPONSE"
if echo "$RESPONSE" | grep -q "NOTICE"; then
echo "✓ PASS: Received NOTICE for over-boundary kind"
else
echo "✗ FAIL: No NOTICE received for over-boundary kind"
exit 1
fi
echo ""
echo "=========================================="
echo "All tests passed!"
echo "=========================================="