268 lines
8.2 KiB
C
268 lines
8.2 KiB
C
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include "./nostr_core_lib/cjson/cJSON.h"
|
|
#include "./nostr_core_lib/nostr_core/nostr_core.h"
|
|
|
|
static const char* k_default_relays[] = {
|
|
"wss://relay.damus.io",
|
|
"wss://relay.primal.net",
|
|
"wss://relay.laantungir.net"
|
|
};
|
|
|
|
typedef struct {
|
|
int eose_received;
|
|
int events_received;
|
|
int kind3_events;
|
|
int max_p_tags;
|
|
} test_ctx_t;
|
|
|
|
static volatile int g_ws_eose_seen = 0;
|
|
|
|
static long long now_ms(void) {
|
|
struct timespec ts;
|
|
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
|
|
return 0;
|
|
}
|
|
return ((long long)ts.tv_sec * 1000LL) + (ts.tv_nsec / 1000000LL);
|
|
}
|
|
|
|
static int count_tag(cJSON* tags, const char* key) {
|
|
if (!tags || !cJSON_IsArray(tags) || !key) {
|
|
return 0;
|
|
}
|
|
|
|
int n = cJSON_GetArraySize(tags);
|
|
int count = 0;
|
|
for (int i = 0; i < n; i++) {
|
|
cJSON* tag = cJSON_GetArrayItem(tags, i);
|
|
if (!tag || !cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 1) {
|
|
continue;
|
|
}
|
|
|
|
cJSON* k = cJSON_GetArrayItem(tag, 0);
|
|
if (k && cJSON_IsString(k) && k->valuestring && strcmp(k->valuestring, key) == 0) {
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static void on_event(cJSON* event, const char* relay_url, void* user_data) {
|
|
test_ctx_t* ctx = (test_ctx_t*)user_data;
|
|
if (!ctx || !event || !cJSON_IsObject(event)) {
|
|
return;
|
|
}
|
|
|
|
ctx->events_received++;
|
|
|
|
cJSON* kind = cJSON_GetObjectItemCaseSensitive(event, "kind");
|
|
cJSON* id = cJSON_GetObjectItemCaseSensitive(event, "id");
|
|
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(event, "pubkey");
|
|
cJSON* created_at = cJSON_GetObjectItemCaseSensitive(event, "created_at");
|
|
cJSON* content = cJSON_GetObjectItemCaseSensitive(event, "content");
|
|
cJSON* tags = cJSON_GetObjectItemCaseSensitive(event, "tags");
|
|
|
|
int kind_val = (kind && cJSON_IsNumber(kind)) ? (int)kind->valuedouble : -1;
|
|
int p_tags = count_tag(tags, "p");
|
|
int r_tags = count_tag(tags, "r");
|
|
int tag_count = (tags && cJSON_IsArray(tags)) ? cJSON_GetArraySize(tags) : 0;
|
|
int content_len = (content && cJSON_IsString(content) && content->valuestring) ? (int)strlen(content->valuestring) : 0;
|
|
|
|
if (p_tags > ctx->max_p_tags) {
|
|
ctx->max_p_tags = p_tags;
|
|
}
|
|
if (kind_val == 3) {
|
|
ctx->kind3_events++;
|
|
}
|
|
|
|
printf("[EVENT] relay=%s kind=%d created_at=%lld tags=%d p_tags=%d r_tags=%d content_len=%d id=%.16s pubkey=%.16s\n",
|
|
relay_url ? relay_url : "<unknown>",
|
|
kind_val,
|
|
(created_at && cJSON_IsNumber(created_at)) ? (long long)created_at->valuedouble : -1LL,
|
|
tag_count,
|
|
p_tags,
|
|
r_tags,
|
|
content_len,
|
|
(id && cJSON_IsString(id) && id->valuestring) ? id->valuestring : "",
|
|
(pubkey && cJSON_IsString(pubkey) && pubkey->valuestring) ? pubkey->valuestring : "");
|
|
}
|
|
|
|
static void on_eose(cJSON** events, int event_count, void* user_data) {
|
|
(void)events;
|
|
test_ctx_t* ctx = (test_ctx_t*)user_data;
|
|
if (!ctx) {
|
|
return;
|
|
}
|
|
|
|
ctx->eose_received = 1;
|
|
printf("[EOSE] received for subscription (event_count=%d)\n", event_count);
|
|
}
|
|
|
|
static void log_callback(int level, const char* component, const char* message, void* user_data) {
|
|
(void)user_data;
|
|
const char* lvl = "?";
|
|
switch (level) {
|
|
case NOSTR_LOG_LEVEL_ERROR: lvl = "ERROR"; break;
|
|
case NOSTR_LOG_LEVEL_WARN: lvl = "WARN"; break;
|
|
case NOSTR_LOG_LEVEL_INFO: lvl = "INFO"; break;
|
|
case NOSTR_LOG_LEVEL_DEBUG: lvl = "DEBUG"; break;
|
|
case NOSTR_LOG_LEVEL_TRACE: lvl = "TRACE"; break;
|
|
default: break;
|
|
}
|
|
|
|
if (!component) component = "core";
|
|
if (!message) message = "";
|
|
|
|
if (strcmp(component, "websocket") == 0) {
|
|
printf("[NOSTR %s] [%s] %s\n", lvl, component, message);
|
|
if (strstr(message, "[\"EOSE\"") != NULL) {
|
|
g_ws_eose_seen = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
const char* npub = "npub13lm5wf8dvsdnc2894pkhch9uf8phvw9varrv8zf4sc885hhdmc8q6lx7ks";
|
|
int timeout_ms = 15000;
|
|
|
|
if (argc >= 2 && argv[1] && argv[1][0] != '\0') {
|
|
npub = argv[1];
|
|
}
|
|
if (argc >= 3) {
|
|
timeout_ms = atoi(argv[2]);
|
|
if (timeout_ms <= 0) {
|
|
timeout_ms = 15000;
|
|
}
|
|
}
|
|
|
|
unsigned char pubkey[32] = {0};
|
|
char pubkey_hex[65] = {0};
|
|
|
|
if (nostr_decode_npub(npub, pubkey) != 0) {
|
|
fprintf(stderr, "Failed to decode npub: %s\n", npub);
|
|
return 1;
|
|
}
|
|
nostr_bytes_to_hex(pubkey, 32, pubkey_hex);
|
|
|
|
if (nostr_crypto_init() != 0) {
|
|
fprintf(stderr, "nostr_crypto_init failed\n");
|
|
return 1;
|
|
}
|
|
|
|
nostr_set_log_callback(log_callback, NULL);
|
|
nostr_set_log_level(NOSTR_LOG_LEVEL_TRACE);
|
|
|
|
nostr_pool_reconnect_config_t* reconnect = nostr_pool_reconnect_config_default();
|
|
if (!reconnect) {
|
|
fprintf(stderr, "nostr_pool_reconnect_config_default failed\n");
|
|
nostr_crypto_cleanup();
|
|
return 1;
|
|
}
|
|
|
|
reconnect->enable_auto_reconnect = 1;
|
|
reconnect->ping_interval_seconds = 20;
|
|
reconnect->pong_timeout_seconds = 10;
|
|
|
|
nostr_relay_pool_t* pool = nostr_relay_pool_create(reconnect);
|
|
if (!pool) {
|
|
fprintf(stderr, "nostr_relay_pool_create failed\n");
|
|
nostr_crypto_cleanup();
|
|
return 1;
|
|
}
|
|
|
|
const int relay_count = (int)(sizeof(k_default_relays) / sizeof(k_default_relays[0]));
|
|
for (int i = 0; i < relay_count; i++) {
|
|
if (nostr_relay_pool_add_relay(pool, k_default_relays[i]) != 0) {
|
|
fprintf(stderr, "Failed to add relay: %s\n", k_default_relays[i]);
|
|
}
|
|
}
|
|
|
|
cJSON* filter = cJSON_CreateObject();
|
|
cJSON* kinds = cJSON_CreateArray();
|
|
cJSON* authors = cJSON_CreateArray();
|
|
if (!filter || !kinds || !authors) {
|
|
fprintf(stderr, "Failed to build filter JSON\n");
|
|
cJSON_Delete(filter);
|
|
cJSON_Delete(kinds);
|
|
cJSON_Delete(authors);
|
|
nostr_relay_pool_destroy(pool);
|
|
nostr_crypto_cleanup();
|
|
return 1;
|
|
}
|
|
|
|
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(3));
|
|
cJSON_AddItemToObject(filter, "kinds", kinds);
|
|
cJSON_AddItemToArray(authors, cJSON_CreateString(pubkey_hex));
|
|
cJSON_AddItemToObject(filter, "authors", authors);
|
|
cJSON_AddNumberToObject(filter, "limit", 16);
|
|
|
|
test_ctx_t ctx;
|
|
memset(&ctx, 0, sizeof(ctx));
|
|
|
|
printf("Target npub: %s\n", npub);
|
|
printf("Target hex : %s\n", pubkey_hex);
|
|
printf("Relays (%d):\n", relay_count);
|
|
for (int i = 0; i < relay_count; i++) {
|
|
printf(" - %s\n", k_default_relays[i]);
|
|
}
|
|
printf("Subscribing to kind=3 with timeout=%d ms\n", timeout_ms);
|
|
|
|
nostr_pool_subscription_t* sub = nostr_relay_pool_subscribe(
|
|
pool,
|
|
k_default_relays,
|
|
relay_count,
|
|
filter,
|
|
on_event,
|
|
on_eose,
|
|
&ctx,
|
|
0,
|
|
0,
|
|
NOSTR_POOL_EOSE_FIRST,
|
|
12,
|
|
20
|
|
);
|
|
|
|
if (!sub) {
|
|
fprintf(stderr, "nostr_relay_pool_subscribe failed\n");
|
|
cJSON_Delete(filter);
|
|
nostr_relay_pool_destroy(pool);
|
|
nostr_crypto_cleanup();
|
|
return 1;
|
|
}
|
|
|
|
long long started = now_ms();
|
|
while (!ctx.eose_received && !g_ws_eose_seen) {
|
|
long long elapsed = now_ms() - started;
|
|
if (elapsed >= timeout_ms) {
|
|
break;
|
|
}
|
|
(void)nostr_relay_pool_poll(pool, 100);
|
|
}
|
|
|
|
printf("\nSummary:\n");
|
|
printf(" EOSE callback : %s\n", ctx.eose_received ? "yes" : "no");
|
|
printf(" Websocket EOSE: %s\n", g_ws_eose_seen ? "yes" : "no");
|
|
printf(" Events seen : %d\n", ctx.events_received);
|
|
printf(" Kind-3 events : %d\n", ctx.kind3_events);
|
|
printf(" Max p-tags : %d\n", ctx.max_p_tags);
|
|
|
|
if (!ctx.eose_received && g_ws_eose_seen) {
|
|
fprintf(stderr, "NOTE: Relay sent EOSE on websocket but subscription callback did not fire (pool EOSE behavior mismatch).\n");
|
|
}
|
|
if (!ctx.eose_received && !g_ws_eose_seen) {
|
|
fprintf(stderr, "Timed out waiting for EOSE after %d ms\n", timeout_ms);
|
|
}
|
|
|
|
(void)nostr_pool_subscription_close(sub);
|
|
cJSON_Delete(filter);
|
|
nostr_relay_pool_destroy(pool);
|
|
nostr_crypto_cleanup();
|
|
|
|
return (ctx.eose_received || g_ws_eose_seen) ? 0 : 2;
|
|
}
|