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); int is_authorized_admin_event(cJSON* event, char* error_message, size_t error_size);
// Forward declaration for NOTICE message support // 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 // Forward declarations for NIP-42 authentication functions
void send_nip42_auth_challenge(struct lws* wsi, struct per_session_data* pss); 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) // 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; if (!wsi || !message) return;
cJSON* notice_msg = cJSON_CreateArray(); cJSON* notice_msg = cJSON_CreateArray();
@@ -218,7 +218,7 @@ void send_notice_message(struct lws* wsi, const char* message) {
if (msg_str) { if (msg_str) {
size_t msg_len = strlen(msg_str); size_t msg_len = strlen(msg_str);
// Use proper message queue system instead of direct lws_write // 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"); DEBUG_ERROR("Failed to queue NOTICE message");
} }
free(msg_str); free(msg_str);

View File

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

View File

@@ -16,7 +16,7 @@
// Forward declaration for notice message function // Forward declaration for notice message function
void send_notice_message(struct lws* wsi, const char* message); void send_notice_message(struct lws* wsi, struct per_session_data* pss, const char* message);
// Forward declarations for NIP-42 functions from request_validator.c // Forward declarations for NIP-42 functions from request_validator.c
int nostr_nip42_generate_challenge(char *challenge_buffer, size_t buffer_size); 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]; char challenge[65];
if (nostr_nip42_generate_challenge(challenge, sizeof(challenge)) != 0) { if (nostr_nip42_generate_challenge(challenge, sizeof(challenge)) != 0) {
DEBUG_ERROR("Failed to generate NIP-42 challenge"); DEBUG_ERROR("Failed to generate NIP-42 challenge");
send_notice_message(wsi, "Authentication temporarily unavailable"); send_notice_message(wsi, pss, "Authentication temporarily unavailable");
return; return;
} }
@@ -71,7 +71,7 @@ void handle_nip42_auth_signed_event(struct lws* wsi, struct per_session_data* ps
// Serialize event for validation // Serialize event for validation
char* event_json = cJSON_Print(auth_event); char* event_json = cJSON_Print(auth_event);
if (!event_json) { if (!event_json) {
send_notice_message(wsi, "Invalid authentication event format"); send_notice_message(wsi, pss, "Invalid authentication event format");
return; 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); time_t current_time = time(NULL);
if (current_time > challenge_expires) { if (current_time > challenge_expires) {
free(event_json); free(event_json);
send_notice_message(wsi, "Authentication challenge expired, please retry"); send_notice_message(wsi, pss, "Authentication challenge expired, please retry");
DEBUG_WARN("NIP-42 authentication failed: challenge expired"); DEBUG_WARN("NIP-42 authentication failed: challenge expired");
return; return;
} }
@@ -127,7 +127,7 @@ void handle_nip42_auth_signed_event(struct lws* wsi, struct per_session_data* ps
pss->auth_challenge_sent = 0; pss->auth_challenge_sent = 0;
pthread_mutex_unlock(&pss->session_lock); pthread_mutex_unlock(&pss->session_lock);
send_notice_message(wsi, "NIP-42 authentication successful"); send_notice_message(wsi, pss, "NIP-42 authentication successful");
} else { } else {
// Authentication failed // Authentication failed
char error_msg[256]; 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); "NIP-42 authentication failed (error code: %d)", result);
DEBUG_WARN(error_msg); 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 // NIP-42 doesn't typically use challenge responses from client to server
// This is reserved for potential future use or protocol extensions // This is reserved for potential future use or protocol extensions
DEBUG_WARN("Received unexpected challenge response from client (not part of standard NIP-42 flow)"); DEBUG_WARN("Received unexpected challenge response from client (not part of standard NIP-42 flow)");
send_notice_message(wsi, "Challenge responses are not supported - please send signed authentication event"); send_notice_message(wsi, 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); int validate_filter_array(cJSON* filters, char* error_message, size_t error_size);
// Forward declarations for NOTICE message support // 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 // Configuration functions from config.c
extern int get_config_bool(const char* key, int default_value); 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 // Check if client is rate limited for malformed requests
if (is_client_rate_limited_for_malformed_requests(pss)) { 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; 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_size = 0;
pss->reassembly_capacity = 0; pss->reassembly_capacity = 0;
pss->reassembly_active = 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; return 0;
} }
pss->reassembly_buffer = new_buffer; 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) { if (!pss->auth_challenge_sent) {
send_nip42_auth_challenge(wsi, pss); send_nip42_auth_challenge(wsi, pss);
} else { } else {
send_notice_message(wsi, "NIP-42 authentication required for subscriptions"); send_notice_message(wsi, pss, "NIP-42 authentication required for subscriptions");
DEBUG_WARN("REQ rejected: NIP-42 authentication required"); DEBUG_WARN("REQ rejected: NIP-42 authentication required");
} }
cJSON_Delete(json); 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 // Validate subscription ID before processing
if (!subscription_id) { if (!subscription_id) {
DEBUG_TRACE("REQ rejected: NULL 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"); DEBUG_WARN("REQ rejected: NULL subscription ID");
record_malformed_request(pss); record_malformed_request(pss);
cJSON_Delete(json); cJSON_Delete(json);
@@ -929,7 +929,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Validate subscription ID // Validate subscription ID
if (!validate_subscription_id(subscription_id)) { if (!validate_subscription_id(subscription_id)) {
DEBUG_TRACE("REQ rejected: invalid subscription ID format"); 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"); DEBUG_WARN("REQ rejected: invalid subscription ID");
cJSON_Delete(json); cJSON_Delete(json);
// Note: complete_message points to reassembly_buffer, which is managed separately // 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(); cJSON* filters = cJSON_CreateArray();
if (!filters) { if (!filters) {
DEBUG_TRACE("REQ failed: could not create filters array"); 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"); DEBUG_ERROR("REQ failed: could not create filters array");
cJSON_Delete(json); cJSON_Delete(json);
// Note: complete_message points to reassembly_buffer, which is managed separately // 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}; char filter_error[512] = {0};
if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) { if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) {
DEBUG_TRACE("REQ rejected: filter validation failed - %s", 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"); DEBUG_WARN("REQ rejected: invalid filters");
record_malformed_request(pss); record_malformed_request(pss);
cJSON_Delete(filters); cJSON_Delete(filters);
@@ -1014,7 +1014,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
cJSON_Delete(eose_response); cJSON_Delete(eose_response);
} }
} else { } 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"); DEBUG_WARN("REQ rejected: missing or invalid subscription ID");
} }
} else if (strcmp(msg_type, "COUNT") == 0) { } 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) { if (!pss->auth_challenge_sent) {
send_nip42_auth_challenge(wsi, pss); send_nip42_auth_challenge(wsi, pss);
} else { } else {
send_notice_message(wsi, "NIP-42 authentication required for count requests"); send_notice_message(wsi, pss, "NIP-42 authentication required for count requests");
DEBUG_WARN("COUNT rejected: NIP-42 authentication required"); DEBUG_WARN("COUNT rejected: NIP-42 authentication required");
} }
cJSON_Delete(json); cJSON_Delete(json);
@@ -1051,7 +1051,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Validate filters before processing // Validate filters before processing
char filter_error[512] = {0}; char filter_error[512] = {0};
if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) { if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) {
send_notice_message(wsi, filter_error); send_notice_message(wsi, pss, filter_error);
DEBUG_WARN("COUNT rejected: invalid filters"); DEBUG_WARN("COUNT rejected: invalid filters");
record_malformed_request(pss); record_malformed_request(pss);
cJSON_Delete(filters); 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 // Validate subscription ID before processing
if (!subscription_id) { if (!subscription_id) {
send_notice_message(wsi, "error: invalid subscription ID in CLOSE"); send_notice_message(wsi, pss, "error: invalid subscription ID in CLOSE");
DEBUG_WARN("CLOSE rejected: NULL subscription ID"); DEBUG_WARN("CLOSE rejected: NULL subscription ID");
cJSON_Delete(json); cJSON_Delete(json);
// Note: complete_message points to reassembly_buffer, which is managed separately // 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 // Validate subscription ID
if (!validate_subscription_id(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"); DEBUG_WARN("CLOSE rejected: invalid subscription ID");
cJSON_Delete(json); cJSON_Delete(json);
// Note: complete_message points to reassembly_buffer, which is managed separately // 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 // Subscription closed
} else { } 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"); DEBUG_WARN("CLOSE rejected: missing or invalid subscription ID");
} }
} else if (strcmp(msg_type, "AUTH") == 0) { } 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) // AUTH signed event: ["AUTH", <event>] (standard NIP-42)
handle_nip42_auth_signed_event(wsi, pss, auth_payload); handle_nip42_auth_signed_event(wsi, pss, auth_payload);
} else { } else {
send_notice_message(wsi, "Invalid AUTH message format"); send_notice_message(wsi, pss, "Invalid AUTH message format");
DEBUG_WARN("Received AUTH message with invalid payload type"); DEBUG_WARN("Received AUTH message with invalid payload type");
} }
} else { } else {
send_notice_message(wsi, "AUTH message requires payload"); send_notice_message(wsi, pss, "AUTH message requires payload");
DEBUG_WARN("Received AUTH message without payload"); DEBUG_WARN("Received AUTH message without payload");
} }
} else { } else {
@@ -1157,7 +1157,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
char unknown_msg[128]; char unknown_msg[128];
snprintf(unknown_msg, sizeof(unknown_msg), "Unknown message type: %.32s", msg_type); snprintf(unknown_msg, sizeof(unknown_msg), "Unknown message type: %.32s", msg_type);
DEBUG_WARN(unknown_msg); 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), snprintf(auth_msg, sizeof(auth_msg),
"NIP-42 authentication required for event kind %d", event_kind); "NIP-42 authentication required for event kind %d", event_kind);
} }
send_notice_message(wsi, auth_msg); send_notice_message(wsi, pss, auth_msg);
DEBUG_WARN("Event rejected: NIP-42 authentication required for kind"); DEBUG_WARN("Event rejected: NIP-42 authentication required for kind");
} }
cJSON_Delete(json); 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) { if (!pss->auth_challenge_sent) {
send_nip42_auth_challenge(wsi, pss); send_nip42_auth_challenge(wsi, pss);
} else { } else {
send_notice_message(wsi, "NIP-42 authentication required for subscriptions"); send_notice_message(wsi, pss, "NIP-42 authentication required for subscriptions");
DEBUG_WARN("REQ rejected: NIP-42 authentication required"); DEBUG_WARN("REQ rejected: NIP-42 authentication required");
} }
cJSON_Delete(json); 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 // Validate subscription ID before processing
if (!subscription_id) { if (!subscription_id) {
DEBUG_TRACE("REQ rejected: NULL 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"); DEBUG_WARN("REQ rejected: NULL subscription ID");
record_malformed_request(pss); record_malformed_request(pss);
cJSON_Delete(json); cJSON_Delete(json);
@@ -1629,7 +1629,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Validate subscription ID // Validate subscription ID
if (!validate_subscription_id(subscription_id)) { if (!validate_subscription_id(subscription_id)) {
DEBUG_TRACE("REQ rejected: invalid subscription ID format"); 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"); DEBUG_WARN("REQ rejected: invalid subscription ID");
cJSON_Delete(json); cJSON_Delete(json);
free(message); free(message);
@@ -1642,7 +1642,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
cJSON* filters = cJSON_CreateArray(); cJSON* filters = cJSON_CreateArray();
if (!filters) { if (!filters) {
DEBUG_TRACE("REQ failed: could not create filters array"); 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"); DEBUG_ERROR("REQ failed: could not create filters array");
cJSON_Delete(json); cJSON_Delete(json);
free(message); free(message);
@@ -1665,7 +1665,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
char filter_error[512] = {0}; char filter_error[512] = {0};
if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) { if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) {
DEBUG_TRACE("REQ rejected: filter validation failed - %s", 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"); DEBUG_WARN("REQ rejected: invalid filters");
record_malformed_request(pss); record_malformed_request(pss);
cJSON_Delete(filters); cJSON_Delete(filters);
@@ -1711,7 +1711,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
cJSON_Delete(eose_response); cJSON_Delete(eose_response);
} }
} else { } 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"); DEBUG_WARN("REQ rejected: missing or invalid subscription ID");
} }
} else if (strcmp(msg_type, "COUNT") == 0) { } 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) { if (!pss->auth_challenge_sent) {
send_nip42_auth_challenge(wsi, pss); send_nip42_auth_challenge(wsi, pss);
} else { } else {
send_notice_message(wsi, "NIP-42 authentication required for count requests"); send_notice_message(wsi, pss, "NIP-42 authentication required for count requests");
DEBUG_WARN("COUNT rejected: NIP-42 authentication required"); DEBUG_WARN("COUNT rejected: NIP-42 authentication required");
} }
cJSON_Delete(json); cJSON_Delete(json);
@@ -1747,7 +1747,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Validate filters before processing // Validate filters before processing
char filter_error[512] = {0}; char filter_error[512] = {0};
if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) { if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) {
send_notice_message(wsi, filter_error); send_notice_message(wsi, pss, filter_error);
DEBUG_WARN("COUNT rejected: invalid filters"); DEBUG_WARN("COUNT rejected: invalid filters");
record_malformed_request(pss); record_malformed_request(pss);
cJSON_Delete(filters); 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 // Validate subscription ID before processing
if (!subscription_id) { if (!subscription_id) {
send_notice_message(wsi, "error: invalid subscription ID in CLOSE"); send_notice_message(wsi, pss, "error: invalid subscription ID in CLOSE");
DEBUG_WARN("CLOSE rejected: NULL subscription ID"); DEBUG_WARN("CLOSE rejected: NULL subscription ID");
cJSON_Delete(json); cJSON_Delete(json);
free(message); free(message);
@@ -1778,7 +1778,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Validate subscription ID // Validate subscription ID
if (!validate_subscription_id(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"); DEBUG_WARN("CLOSE rejected: invalid subscription ID");
cJSON_Delete(json); cJSON_Delete(json);
free(message); free(message);
@@ -1823,7 +1823,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Subscription closed // Subscription closed
} else { } else {
send_notice_message(wsi, "error: missing or invalid subscription ID in CLOSE"); send_notice_message(wsi, pss, "error: missing or invalid subscription ID in CLOSE");
DEBUG_WARN("CLOSE rejected: missing or invalid subscription ID"); DEBUG_WARN("CLOSE rejected: missing or invalid subscription ID");
} }
} else if (strcmp(msg_type, "AUTH") == 0) { } else if (strcmp(msg_type, "AUTH") == 0) {
@@ -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) // AUTH signed event: ["AUTH", <event>] (standard NIP-42)
handle_nip42_auth_signed_event(wsi, pss, auth_payload); handle_nip42_auth_signed_event(wsi, pss, auth_payload);
} else { } else {
send_notice_message(wsi, "Invalid AUTH message format"); send_notice_message(wsi, pss, "Invalid AUTH message format");
DEBUG_WARN("Received AUTH message with invalid payload type"); DEBUG_WARN("Received AUTH message with invalid payload type");
} }
} else { } else {
send_notice_message(wsi, "AUTH message requires payload"); send_notice_message(wsi, pss, "AUTH message requires payload");
DEBUG_WARN("Received AUTH message without payload"); DEBUG_WARN("Received AUTH message without payload");
} }
} else { } else {
@@ -1850,7 +1850,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
char unknown_msg[128]; char unknown_msg[128];
snprintf(unknown_msg, sizeof(unknown_msg), "Unknown message type: %.32s", msg_type); snprintf(unknown_msg, sizeof(unknown_msg), "Unknown message type: %.32s", msg_type);
DEBUG_WARN(unknown_msg); 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 "=========================================="