182 lines
6.9 KiB
C
182 lines
6.9 KiB
C
/*
|
|
* test_ws_client.c — Standalone test for nostr_core_lib's WebSocket client.
|
|
*
|
|
* Connects to a relay, sends a REQ with a filter, and counts how many
|
|
* EVENT messages and EOSE it receives. This isolates the WebSocket client
|
|
* from the caching backfill logic to diagnose why the C client only gets
|
|
* 1 event from laantungir.net/relay while node.js gets 500 + EOSE.
|
|
*
|
|
* Build: see test_ws_client_makefile or use the caching/ Makefile pattern.
|
|
* cd tests && gcc -o test_ws_client test_ws_client.c \
|
|
* -I../nostr_core_lib -I../nostr_core_lib/nostr_core \
|
|
* -I../nostr_core_lib/cjson -I../nostr_core_lib/nostr_websocket \
|
|
* ../nostr_core_lib/libnostr_core_x64.a \
|
|
* -lssl -lcrypto -lwebsockets -lsecp256k1 -lz -ldl -lpthread -lm
|
|
*
|
|
* Usage: ./test_ws_client <relay_url> <pubkey_hex> [limit] [kinds]
|
|
* limit defaults to 500, kinds defaults to "0,1,3,6,10000,10002,30023"
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "nostr_core.h"
|
|
#include "cjson/cJSON.h"
|
|
|
|
typedef struct {
|
|
int event_count;
|
|
int got_eose;
|
|
int got_timeout;
|
|
int got_error;
|
|
char last_status[256];
|
|
time_t start;
|
|
} test_ctx_t;
|
|
|
|
static void test_callback(const char* relay_url, const char* status,
|
|
const char* event_id, int events_received,
|
|
int total_relays, int completed_relays,
|
|
void* user_data) {
|
|
(void)relay_url; (void)event_id; (void)total_relays; (void)completed_relays;
|
|
test_ctx_t* ctx = (test_ctx_t*)user_data;
|
|
if (!ctx || !status) return;
|
|
|
|
double elapsed = difftime(time(NULL), ctx->start);
|
|
|
|
/* Track all status types */
|
|
if (strcmp(status, "event_found") == 0) {
|
|
ctx->event_count = events_received;
|
|
if (ctx->event_count % 50 == 0) {
|
|
printf(" [%5.0fs] %d events received\n", elapsed, ctx->event_count);
|
|
}
|
|
} else if (strcmp(status, "eose") == 0) {
|
|
ctx->got_eose = 1;
|
|
ctx->event_count = events_received;
|
|
printf(" [%5.0fs] EOSE received! events=%d\n", elapsed, ctx->event_count);
|
|
} else if (strcmp(status, "timeout") == 0) {
|
|
ctx->got_timeout = 1;
|
|
ctx->event_count = events_received;
|
|
printf(" [%5.0fs] TIMEOUT (relay timed out) events=%d\n", elapsed, ctx->event_count);
|
|
} else if (strcmp(status, "error") == 0) {
|
|
ctx->got_error = 1;
|
|
printf(" [%5.0fs] ERROR from relay\n", elapsed);
|
|
} else if (strcmp(status, "all_complete") == 0) {
|
|
printf(" [%5.0fs] all_complete (synthetic) events=%d\n", elapsed, events_received);
|
|
} else if (strcmp(status, "connecting") == 0) {
|
|
printf(" [%5.0fs] connecting...\n", elapsed);
|
|
} else if (strcmp(status, "subscribed") == 0) {
|
|
printf(" [%5.0fs] subscribed, REQ sent\n", elapsed);
|
|
} else {
|
|
printf(" [%5.0fs] status='%s' events=%d\n", elapsed, status, events_received);
|
|
}
|
|
|
|
snprintf(ctx->last_status, sizeof(ctx->last_status), "%s", status);
|
|
}
|
|
|
|
int main(int argc, char* argv[]) {
|
|
if (argc < 3) {
|
|
fprintf(stderr, "Usage: %s <relay_url> <pubkey_hex> [limit] [kinds_csv]\n", argv[0]);
|
|
fprintf(stderr, "Example: %s wss://laantungir.net/relay 1ec454734dcbf6fe54901ce25c0c7c6bca5edd89443416761fadc321d38df139 500\n", argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
const char* relay_url = argv[1];
|
|
const char* pubkey = argv[2];
|
|
int limit = (argc >= 4) ? atoi(argv[3]) : 500;
|
|
const char* kinds_csv = (argc >= 5) ? argv[4] : "0,1,3,6,10000,10002,30023";
|
|
|
|
printf("=== nostr_core_lib WebSocket Client Test ===\n");
|
|
printf("Relay: %s\n", relay_url);
|
|
printf("Pubkey: %s\n", pubkey);
|
|
printf("Limit: %d\n", limit);
|
|
printf("Kinds: %s\n", kinds_csv);
|
|
printf("Timeout: 30s\n\n");
|
|
|
|
/* Build the filter JSON */
|
|
cJSON* filter = cJSON_CreateObject();
|
|
cJSON* authors = cJSON_CreateArray();
|
|
cJSON_AddItemToArray(authors, cJSON_CreateString(pubkey));
|
|
cJSON_AddItemToObject(filter, "authors", authors);
|
|
|
|
/* Parse kinds CSV */
|
|
cJSON* kinds = cJSON_CreateArray();
|
|
char kinds_buf[256];
|
|
strncpy(kinds_buf, kinds_csv, sizeof(kinds_buf) - 1);
|
|
kinds_buf[sizeof(kinds_buf) - 1] = '\0';
|
|
char* tok = strtok(kinds_buf, ",");
|
|
while (tok) {
|
|
cJSON_AddItemToArray(kinds, cJSON_CreateNumber((double)atoi(tok)));
|
|
tok = strtok(NULL, ",");
|
|
}
|
|
cJSON_AddItemToObject(filter, "kinds", kinds);
|
|
cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber((double)limit));
|
|
|
|
/* Use until=now to mimic backfill first query */
|
|
long now = (long)time(NULL);
|
|
cJSON_AddItemToObject(filter, "until", cJSON_CreateNumber((double)now));
|
|
printf("Until: %ld (now)\n\n", now);
|
|
|
|
const char* one_relay = relay_url;
|
|
int result_count = 0;
|
|
test_ctx_t ctx = {0};
|
|
ctx.start = time(NULL);
|
|
|
|
printf("Calling synchronous_query_relays_with_progress...\n\n");
|
|
|
|
cJSON** events = synchronous_query_relays_with_progress(
|
|
&one_relay, 1, filter, RELAY_QUERY_ALL_RESULTS,
|
|
&result_count, 30, /* 30s timeout */
|
|
test_callback, &ctx,
|
|
0, NULL /* no NIP-42 */);
|
|
|
|
double total_elapsed = difftime(time(NULL), ctx.start);
|
|
|
|
printf("\n=== RESULTS ===\n");
|
|
printf("Total elapsed: %.0fs\n", total_elapsed);
|
|
printf("Events received: %d\n", ctx.event_count);
|
|
printf("Result count: %d\n", result_count);
|
|
printf("Got EOSE: %s\n", ctx.got_eose ? "YES" : "NO");
|
|
printf("Got timeout: %s\n", ctx.got_timeout ? "YES" : "NO");
|
|
printf("Got error: %s\n", ctx.got_error ? "YES" : "NO");
|
|
printf("Last status: %s\n", ctx.last_status);
|
|
|
|
if (events) {
|
|
printf("Returned event array: %d events\n", result_count);
|
|
for (int i = 0; i < result_count && i < 3; i++) {
|
|
if (events[i]) {
|
|
cJSON* id = cJSON_GetObjectItem(events[i], "id");
|
|
cJSON* kind = cJSON_GetObjectItem(events[i], "kind");
|
|
cJSON* created = cJSON_GetObjectItem(events[i], "created_at");
|
|
printf(" event[%d]: id=%s kind=%d created_at=%lld\n",
|
|
i,
|
|
(id && cJSON_IsString(id)) ? id->valuestring : "?",
|
|
(kind && cJSON_IsNumber(kind)) ? (int)kind->valuedouble : -1,
|
|
(created && cJSON_IsNumber(created)) ? (long long)created->valuedouble : 0);
|
|
cJSON_Delete(events[i]);
|
|
}
|
|
}
|
|
/* Free remaining */
|
|
for (int i = 3; i < result_count; i++) {
|
|
if (events[i]) cJSON_Delete(events[i]);
|
|
}
|
|
free(events);
|
|
}
|
|
|
|
cJSON_Delete(filter);
|
|
|
|
printf("\n");
|
|
if (ctx.got_eose) {
|
|
printf("✓ SUCCESS: EOSE received, WebSocket client works correctly\n");
|
|
return 0;
|
|
} else if (ctx.got_timeout) {
|
|
printf("✗ FAILURE: Timed out without EOSE — WebSocket client bug confirmed\n");
|
|
return 1;
|
|
} else {
|
|
printf("✗ FAILURE: No EOSE and no timeout — unexpected state\n");
|
|
return 2;
|
|
}
|
|
}
|