nip 17, and 59

This commit is contained in:
2025-10-03 04:25:10 -04:00
parent 54a6044083
commit 6b95ad37c5
32 changed files with 2605 additions and 24009 deletions

BIN
tests/nip17_test Executable file

Binary file not shown.

341
tests/nip17_test.c Normal file
View File

@@ -0,0 +1,341 @@
/*
* NIP-17 Private Direct Messages Test Program
*
* Tests the complete NIP-17 DM flow using synchronous relay operations:
* 1. Generate sender and recipient keypairs
* 2. Create a DM from sender to recipient
* 3. Publish the gift-wrapped DM to relay
* 4. Subscribe to receive the DM back
* 5. Decrypt and verify the received DM
*/
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
// Enable debug output to see all relay messages
#define NOSTR_DEBUG_ENABLED
#include "../nostr_core/nostr_core.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
// Forward declarations for crypto functions
int nostr_secp256k1_get_random_bytes(unsigned char* buf, size_t len);
// Test configuration
#define RELAY_URL "wss://relay.laantungir.net"
#define TEST_TIMEOUT_MS 5000
// Progress callback for publishing
void publish_progress_callback(const char* relay_url, const char* status,
const char* message, int success_count,
int total_relays, int completed_relays, void* user_data) {
(void)user_data;
if (relay_url) {
printf("📡 PUBLISH [%s]: %s", relay_url, status);
if (message) {
printf(" - %s", message);
}
printf(" (%d/%d completed, %d successful)\n", completed_relays, total_relays, success_count);
} else {
printf("📡 PUBLISH COMPLETE: %d/%d successful\n", success_count, total_relays);
}
}
// Progress callback for querying/subscribing
void query_progress_callback(const char* relay_url, const char* status,
const char* event_id, int event_count,
int total_relays, int completed_relays, void* user_data) {
(void)user_data;
if (relay_url) {
printf("🔍 QUERY [%s]: %s", relay_url, status);
if (event_id) {
printf(" - Event: %.12s...", event_id);
}
if (event_count > 0) {
printf(" (%d events)", event_count);
}
printf(" (%d/%d completed)\n", completed_relays, total_relays);
} else {
printf("🔍 QUERY COMPLETE: %d events found\n", event_count);
}
}
/**
* Generate a random keypair for testing
*/
void generate_test_keypair(unsigned char* private_key, char* pubkey_hex) {
// Generate random private key
if (nostr_secp256k1_get_random_bytes(private_key, 32) != 1) {
fprintf(stderr, "Failed to generate random private key\n");
exit(1);
}
// Derive public key
unsigned char public_key[32];
if (nostr_ec_public_key_from_private_key(private_key, public_key) != 0) {
fprintf(stderr, "Failed to derive public key\n");
exit(1);
}
// Convert to hex
nostr_bytes_to_hex(public_key, 32, pubkey_hex);
}
/**
* Main test function
*/
int main(int argc, char* argv[]) {
(void)argc; // Suppress unused parameter warning
(void)argv; // Suppress unused parameter warning
printf("🧪 NIP-17 Private Direct Messages Test (Synchronous)\n");
printf("=================================================\n\n");
// Initialize crypto
if (nostr_init() != NOSTR_SUCCESS) {
fprintf(stderr, "Failed to initialize crypto\n");
return 1;
}
// Generate keypairs
unsigned char sender_privkey[32];
unsigned char recipient_privkey[32];
char sender_pubkey_hex[65];
char recipient_pubkey_hex[65];
printf("🔑 Generating keypairs...\n");
generate_test_keypair(sender_privkey, sender_pubkey_hex);
generate_test_keypair(recipient_privkey, recipient_pubkey_hex);
printf("📤 Sender pubkey: %s\n", sender_pubkey_hex);
printf("📥 Recipient pubkey: %s\n", recipient_pubkey_hex);
printf("\n");
// Create DM event with timestamp
printf("💬 Creating DM event...\n");
time_t now = time(NULL);
char test_message[256];
snprintf(test_message, sizeof(test_message),
"Hello from NIP-17! This is a private direct message sent at %ld", now);
const char* recipient_pubkeys[] = {recipient_pubkey_hex};
cJSON* dm_event = nostr_nip17_create_chat_event(
test_message,
recipient_pubkeys,
1,
"NIP-17 Test",
NULL, // no reply
RELAY_URL,
sender_pubkey_hex
);
if (!dm_event) {
fprintf(stderr, "Failed to create DM event\n");
return 1;
}
printf("📝 Created DM event (kind 14)\n");
// Send DM (create gift wraps)
printf("🎁 Creating gift wraps...\n");
cJSON* gift_wraps[10]; // Max 10 gift wraps
int gift_wrap_count = nostr_nip17_send_dm(
dm_event,
recipient_pubkeys,
1,
sender_privkey,
gift_wraps,
10
);
cJSON_Delete(dm_event); // Original DM event no longer needed
if (gift_wrap_count <= 0) {
fprintf(stderr, "Failed to create gift wraps\n");
return 1;
}
printf("✅ Created %d gift wrap(s)\n", gift_wrap_count);
// Print the gift wrap JSON
printf("\n📄 Gift wrap event JSON:\n");
printf("========================\n");
char* gift_wrap_json = cJSON_Print(gift_wraps[0]);
printf("%s\n", gift_wrap_json);
free(gift_wrap_json);
// PHASE 1: Publish the gift wrap to relay
printf("\n📤 PHASE 1: Publishing gift wrap to relay\n");
printf("==========================================\n");
const char* relay_urls[] = {RELAY_URL};
int success_count = 0;
publish_result_t* publish_results = synchronous_publish_event_with_progress(
relay_urls,
1, // single relay
gift_wraps[0], // Send the first gift wrap
&success_count,
10, // 10 second timeout
publish_progress_callback,
NULL, // no user data
0, // NIP-42 disabled
NULL // no private key for auth
);
if (!publish_results || success_count != 1) {
fprintf(stderr, "❌ Failed to publish gift wrap (success_count: %d)\n", success_count);
// Clean up gift wraps
for (int i = 0; i < gift_wrap_count; i++) {
cJSON_Delete(gift_wraps[i]);
}
if (publish_results) free(publish_results);
return 1;
}
printf("✅ Successfully published gift wrap!\n");
// Clean up publish results and gift wraps
free(publish_results);
for (int i = 0; i < gift_wrap_count; i++) {
cJSON_Delete(gift_wraps[i]);
}
// Small delay to let the relay process the event
printf("⏳ Waiting 2 seconds for relay to process...\n");
sleep(2);
// PHASE 2: Subscribe to receive the DM back
printf("\n📥 PHASE 2: Subscribing to receive DM back\n");
printf("===========================================\n");
// Create filter for gift wraps addressed to recipient
cJSON* filter = cJSON_CreateObject();
cJSON* kinds = cJSON_CreateArray();
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1059)); // Gift wrap kind
cJSON_AddItemToObject(filter, "kinds", kinds);
// Filter for gift wraps with p tag matching recipient
cJSON* p_tags = cJSON_CreateArray();
cJSON_AddItemToArray(p_tags, cJSON_CreateString(recipient_pubkey_hex));
cJSON_AddItemToObject(filter, "#p", p_tags);
// Print the subscription filter JSON
printf("📄 Subscription filter JSON:\n");
printf("============================\n");
char* filter_json = cJSON_Print(filter);
printf("%s\n", filter_json);
free(filter_json);
int query_result_count = 0;
cJSON** query_results = synchronous_query_relays_with_progress(
relay_urls,
1, // single relay
filter,
RELAY_QUERY_ALL_RESULTS, // Get all matching events
&query_result_count,
10, // 10 second timeout per relay
query_progress_callback,
NULL, // no user data
0, // NIP-42 disabled
NULL // no private key for auth
);
cJSON_Delete(filter); // Clean up filter
if (!query_results || query_result_count == 0) {
fprintf(stderr, "❌ No DM events received back from relay\n");
if (query_results) free(query_results);
return 1;
}
printf("✅ Received %d event(s) from relay!\n", query_result_count);
// Process the received events
printf("\n🔍 PHASE 3: Processing received events\n");
printf("=====================================\n");
int dm_found = 0;
cJSON* received_dm = NULL;
for (int i = 0; i < query_result_count; i++) {
cJSON* event = query_results[i];
cJSON* kind_item = cJSON_GetObjectItem(event, "kind");
cJSON* id_item = cJSON_GetObjectItem(event, "id");
if (kind_item && cJSON_IsNumber(kind_item) && cJSON_GetNumberValue(kind_item) == 1059) {
printf("🎁 Found gift wrap event: %.12s...\n",
id_item && cJSON_IsString(id_item) ? cJSON_GetStringValue(id_item) : "unknown");
// Try to decrypt this gift wrap
cJSON* decrypted_dm = nostr_nip17_receive_dm(event, recipient_privkey);
if (decrypted_dm) {
printf("✅ Successfully decrypted gift wrap!\n");
// Verify the decrypted DM
cJSON* content_item = cJSON_GetObjectItem(decrypted_dm, "content");
cJSON* dm_kind_item = cJSON_GetObjectItem(decrypted_dm, "kind");
cJSON* pubkey_item = cJSON_GetObjectItem(decrypted_dm, "pubkey");
if (content_item && dm_kind_item && pubkey_item) {
const char* decrypted_content = cJSON_GetStringValue(content_item);
int decrypted_kind = (int)cJSON_GetNumberValue(dm_kind_item);
const char* decrypted_pubkey = cJSON_GetStringValue(pubkey_item);
printf("📧 Decrypted DM:\n");
printf(" Kind: %d\n", decrypted_kind);
printf(" From: %s\n", decrypted_pubkey);
printf(" Content: %s\n", decrypted_content);
// Verify the DM content (check that it contains our message with timestamp)
int content_ok = strstr(decrypted_content, "Hello from NIP-17! This is a private direct message sent at ") != NULL;
if (decrypted_kind == 14 &&
content_ok &&
strlen(decrypted_content) >= 64 && // Should be at least as long as the prefix
strcmp(decrypted_pubkey, sender_pubkey_hex) == 0) {
printf("✅ DM verification successful!\n");
dm_found = 1;
received_dm = decrypted_dm; // Keep reference for cleanup
break; // Found our DM, no need to check more
} else {
printf("❌ DM verification failed\n");
cJSON_Delete(decrypted_dm);
}
} else {
printf("❌ Invalid decrypted DM structure\n");
cJSON_Delete(decrypted_dm);
}
} else {
printf("❌ Failed to decrypt gift wrap\n");
}
}
}
// Clean up query results
for (int i = 0; i < query_result_count; i++) {
cJSON_Delete(query_results[i]);
}
free(query_results);
if (!dm_found) {
fprintf(stderr, "❌ Could not find or decrypt the expected DM\n");
return 1;
}
printf("\n🎉 NIP-17 synchronous test completed successfully!\n");
// Cleanup
if (received_dm) {
cJSON_Delete(received_dm);
}
nostr_cleanup();
return 0;
}

Binary file not shown.

View File

@@ -1,692 +0,0 @@
/*
* Interactive Relay Pool Test Program
*
* Interactive command-line interface for testing nostr_relay_pool functionality.
* All output is logged to pool.log while the menu runs in the terminal.
*
* Usage: ./pool_test
*/
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "../nostr_core/nostr_core.h"
#include "../cjson/cJSON.h"
// Global variables
volatile sig_atomic_t running = 1;
nostr_relay_pool_t* pool = NULL;
nostr_pool_subscription_t** subscriptions = NULL;
int subscription_count = 0;
int subscription_capacity = 0;
pthread_t poll_thread;
int log_fd = -1;
// Signal handler for clean shutdown
void signal_handler(int signum) {
(void)signum;
running = 0;
}
// Event callback - called when an event is received
void on_event(cJSON* event, const char* relay_url, void* user_data) {
(void)user_data;
// Extract basic event information
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* content = cJSON_GetObjectItem(event, "content");
time_t now = time(NULL);
char timestamp[26];
ctime_r(&now, timestamp);
timestamp[24] = '\0'; // Remove newline
dprintf(log_fd, "[%s] 📨 EVENT from %s\n", timestamp, relay_url);
dprintf(log_fd, "├── ID: %.12s...\n", id && cJSON_IsString(id) ? cJSON_GetStringValue(id) : "unknown");
dprintf(log_fd, "├── Pubkey: %.12s...\n", pubkey && cJSON_IsString(pubkey) ? cJSON_GetStringValue(pubkey) : "unknown");
dprintf(log_fd, "├── Kind: %d\n", kind && cJSON_IsNumber(kind) ? (int)cJSON_GetNumberValue(kind) : -1);
dprintf(log_fd, "├── Created: %lld\n", created_at && cJSON_IsNumber(created_at) ? (long long)cJSON_GetNumberValue(created_at) : 0);
// Truncate content if too long
if (content && cJSON_IsString(content)) {
const char* content_str = cJSON_GetStringValue(content);
size_t content_len = strlen(content_str);
if (content_len > 100) {
dprintf(log_fd, "└── Content: %.97s...\n", content_str);
} else {
dprintf(log_fd, "└── Content: %s\n", content_str);
}
} else {
dprintf(log_fd, "└── Content: (empty)\n");
}
dprintf(log_fd, "\n");
}
// EOSE callback - called when End of Stored Events is received
void on_eose(cJSON** events, int event_count, void* user_data) {
(void)user_data;
time_t now = time(NULL);
char timestamp[26];
ctime_r(&now, timestamp);
timestamp[24] = '\0';
dprintf(log_fd, "[%s] 📋 EOSE received - %d events collected\n", timestamp, event_count);
// Log collected events if any
for (int i = 0; i < event_count; i++) {
cJSON* id = cJSON_GetObjectItem(events[i], "id");
if (id && cJSON_IsString(id)) {
dprintf(log_fd, " Event %d: %.12s...\n", i + 1, cJSON_GetStringValue(id));
}
}
dprintf(log_fd, "\n");
}
// Background polling thread
void* poll_thread_func(void* arg) {
(void)arg;
while (running) {
if (pool) {
nostr_relay_pool_poll(pool, 100);
}
struct timespec ts = {0, 10000000}; // 10ms
nanosleep(&ts, NULL);
}
return NULL;
}
// Print menu
void print_menu() {
printf("\n=== NOSTR Relay Pool Test Menu ===\n");
printf("1. Start Pool (wss://relay.laantungir.net)\n");
printf("2. Stop Pool\n");
printf("3. Add relay to pool\n");
printf("4. Remove relay from pool\n");
printf("5. Add subscription\n");
printf("6. Remove subscription\n");
printf("7. Show pool status\n");
printf("8. Test reconnection (simulate disconnect)\n");
printf("9. Exit\n");
printf("Choice: ");
}
// Get user input with default
char* get_input(const char* prompt, const char* default_value) {
static char buffer[1024];
printf("%s", prompt);
if (default_value) {
printf(" [%s]", default_value);
}
printf(": ");
if (!fgets(buffer, sizeof(buffer), stdin)) {
return NULL;
}
// Remove newline
size_t len = strlen(buffer);
if (len > 0 && buffer[len-1] == '\n') {
buffer[len-1] = '\0';
}
// Return default if empty
if (strlen(buffer) == 0 && default_value) {
return strdup(default_value);
}
return strdup(buffer);
}
// Parse comma-separated list into cJSON array
cJSON* parse_comma_list(const char* input, int is_number) {
if (!input || strlen(input) == 0) {
return NULL;
}
cJSON* array = cJSON_CreateArray();
if (!array) return NULL;
char* input_copy = strdup(input);
char* token = strtok(input_copy, ",");
while (token) {
// Trim whitespace
while (*token == ' ') token++;
char* end = token + strlen(token) - 1;
while (end > token && *end == ' ') *end-- = '\0';
if (is_number) {
int num = atoi(token);
cJSON_AddItemToArray(array, cJSON_CreateNumber(num));
} else {
cJSON_AddItemToArray(array, cJSON_CreateString(token));
}
token = strtok(NULL, ",");
}
free(input_copy);
return array;
}
// Add subscription interactively
void add_subscription() {
if (!pool) {
printf("❌ Pool not started\n");
return;
}
printf("\n--- Add Subscription ---\n");
printf("Enter filter values (press Enter for no value):\n");
cJSON* filter = cJSON_CreateObject();
// ids
char* ids_input = get_input("ids (comma-separated event ids)", NULL);
if (ids_input && strlen(ids_input) > 0) {
cJSON* ids = parse_comma_list(ids_input, 0);
if (ids) cJSON_AddItemToObject(filter, "ids", ids);
}
free(ids_input);
// authors
char* authors_input = get_input("authors (comma-separated pubkeys)", NULL);
if (authors_input && strlen(authors_input) > 0) {
cJSON* authors = parse_comma_list(authors_input, 0);
if (authors) cJSON_AddItemToObject(filter, "authors", authors);
}
free(authors_input);
// kinds
char* kinds_input = get_input("kinds (comma-separated numbers)", NULL);
if (kinds_input && strlen(kinds_input) > 0) {
cJSON* kinds = parse_comma_list(kinds_input, 1);
if (kinds) cJSON_AddItemToObject(filter, "kinds", kinds);
}
free(kinds_input);
// #e tag
char* e_input = get_input("#e (comma-separated event ids)", NULL);
if (e_input && strlen(e_input) > 0) {
cJSON* e_array = parse_comma_list(e_input, 0);
if (e_array) cJSON_AddItemToObject(filter, "#e", e_array);
}
free(e_input);
// #p tag
char* p_input = get_input("#p (comma-separated pubkeys)", NULL);
if (p_input && strlen(p_input) > 0) {
cJSON* p_array = parse_comma_list(p_input, 0);
if (p_array) cJSON_AddItemToObject(filter, "#p", p_array);
}
free(p_input);
// since
char* since_input = get_input("since (unix timestamp or 'n' for now)", NULL);
if (since_input && strlen(since_input) > 0) {
if (strcmp(since_input, "n") == 0) {
// Use current timestamp
time_t now = time(NULL);
cJSON_AddItemToObject(filter, "since", cJSON_CreateNumber((int)now));
printf("Using current timestamp: %ld\n", now);
} else {
int since = atoi(since_input);
if (since > 0) cJSON_AddItemToObject(filter, "since", cJSON_CreateNumber(since));
}
}
free(since_input);
// until
char* until_input = get_input("until (unix timestamp)", NULL);
if (until_input && strlen(until_input) > 0) {
int until = atoi(until_input);
if (until > 0) cJSON_AddItemToObject(filter, "until", cJSON_CreateNumber(until));
}
free(until_input);
// limit
char* limit_input = get_input("limit (max events)", "10");
if (limit_input && strlen(limit_input) > 0) {
int limit = atoi(limit_input);
if (limit > 0) cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber(limit));
}
free(limit_input);
// Get relay URLs from pool
char** relay_urls = NULL;
nostr_pool_relay_status_t* statuses = NULL;
int relay_count = nostr_relay_pool_list_relays(pool, &relay_urls, &statuses);
if (relay_count <= 0) {
printf("❌ No relays in pool\n");
cJSON_Delete(filter);
return;
}
// Ask about close_on_eose behavior
char* close_input = get_input("Close subscription on EOSE? (y/n)", "n");
int close_on_eose = (close_input && strcmp(close_input, "y") == 0) ? 1 : 0;
free(close_input);
// Create subscription with new parameters
nostr_pool_subscription_t* sub = nostr_relay_pool_subscribe(
pool,
(const char**)relay_urls,
relay_count,
filter,
on_event,
on_eose,
NULL,
close_on_eose,
1, // enable_deduplication
NOSTR_POOL_EOSE_FULL_SET, // result_mode
30, // relay_timeout_seconds
60 // eose_timeout_seconds
);
// Free relay URLs
for (int i = 0; i < relay_count; i++) {
free(relay_urls[i]);
}
free(relay_urls);
free(statuses);
if (!sub) {
printf("❌ Failed to create subscription\n");
cJSON_Delete(filter);
return;
}
// Store subscription
if (subscription_count >= subscription_capacity) {
subscription_capacity = subscription_capacity == 0 ? 10 : subscription_capacity * 2;
subscriptions = realloc(subscriptions, subscription_capacity * sizeof(nostr_pool_subscription_t*));
}
subscriptions[subscription_count++] = sub;
printf("✅ Subscription created (ID: %d)\n", subscription_count);
// Log the filter
char* filter_json = cJSON_Print(filter);
time_t now = time(NULL);
char timestamp[26];
ctime_r(&now, timestamp);
timestamp[24] = '\0';
dprintf(log_fd, "[%s] 🔍 New subscription created (ID: %d)\n", timestamp, subscription_count);
dprintf(log_fd, "Filter: %s\n\n", filter_json);
free(filter_json);
}
// Remove subscription
void remove_subscription() {
if (subscription_count == 0) {
printf("❌ No subscriptions to remove\n");
return;
}
printf("\n--- Remove Subscription ---\n");
printf("Available subscriptions:\n");
for (int i = 0; i < subscription_count; i++) {
printf("%d. Subscription %d\n", i + 1, i + 1);
}
char* choice_input = get_input("Enter subscription number to remove", NULL);
if (!choice_input || strlen(choice_input) == 0) {
free(choice_input);
return;
}
int choice = atoi(choice_input) - 1;
free(choice_input);
if (choice < 0 || choice >= subscription_count) {
printf("❌ Invalid subscription number\n");
return;
}
nostr_pool_subscription_close(subscriptions[choice]);
// Shift remaining subscriptions
for (int i = choice; i < subscription_count - 1; i++) {
subscriptions[i] = subscriptions[i + 1];
}
subscription_count--;
printf("✅ Subscription removed\n");
time_t now = time(NULL);
char timestamp[26];
ctime_r(&now, timestamp);
timestamp[24] = '\0';
dprintf(log_fd, "[%s] 🗑️ Subscription removed (was ID: %d)\n\n", timestamp, choice + 1);
}
// Show pool status
void show_pool_status() {
if (!pool) {
printf("❌ Pool not started\n");
return;
}
// Give polling thread time to establish connections
printf("⏳ Waiting for connections to establish...\n");
sleep(3);
char** relay_urls = NULL;
nostr_pool_relay_status_t* statuses = NULL;
int relay_count = nostr_relay_pool_list_relays(pool, &relay_urls, &statuses);
printf("\n📊 POOL STATUS\n");
printf("Relays: %d\n", relay_count);
printf("Subscriptions: %d\n", subscription_count);
if (relay_count > 0) {
printf("\nRelay Details:\n");
for (int i = 0; i < relay_count; i++) {
const char* status_str;
switch (statuses[i]) {
case NOSTR_POOL_RELAY_CONNECTED: status_str = "🟢 CONNECTED"; break;
case NOSTR_POOL_RELAY_CONNECTING: status_str = "🟡 CONNECTING"; break;
case NOSTR_POOL_RELAY_DISCONNECTED: status_str = "⚪ DISCONNECTED"; break;
case NOSTR_POOL_RELAY_ERROR: status_str = "🔴 ERROR"; break;
default: status_str = "❓ UNKNOWN"; break;
}
printf("├── %s: %s\n", relay_urls[i], status_str);
const nostr_relay_stats_t* stats = nostr_relay_pool_get_relay_stats(pool, relay_urls[i]);
if (stats) {
printf("│ ├── Events received: %d\n", stats->events_received);
printf("│ ├── Connection attempts: %d\n", stats->connection_attempts);
printf("│ ├── Connection failures: %d\n", stats->connection_failures);
printf("│ ├── Ping latency: %.2f ms\n", stats->ping_latency_current);
printf("│ └── Query latency: %.2f ms\n", stats->query_latency_avg);
}
free(relay_urls[i]);
}
free(relay_urls);
free(statuses);
}
printf("\n");
}
int main() {
// Setup logging to file
log_fd = open("pool.log", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (log_fd == -1) {
fprintf(stderr, "❌ Failed to open pool.log for writing\n");
return 1;
}
// Initialize NOSTR library
if (nostr_init() != NOSTR_SUCCESS) {
fprintf(stderr, "❌ Failed to initialize NOSTR library\n");
close(log_fd);
return 1;
}
// Setup signal handler
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
// Start polling thread
if (pthread_create(&poll_thread, NULL, poll_thread_func, NULL) != 0) {
fprintf(stderr, "❌ Failed to create polling thread\n");
nostr_cleanup();
close(log_fd);
return 1;
}
printf("🔗 NOSTR Relay Pool Interactive Test\n");
printf("=====================================\n");
printf("All event output is logged to pool.log\n");
printf("Press Ctrl+C to exit\n\n");
time_t now = time(NULL);
char timestamp[26];
ctime_r(&now, timestamp);
timestamp[24] = '\0';
dprintf(log_fd, "[%s] 🚀 Pool test started\n\n", timestamp);
// Main menu loop
while (running) {
print_menu();
char choice;
if (scanf("%c", &choice) != 1) {
break;
}
// Consume newline
int c;
while ((c = getchar()) != '\n' && c != EOF);
switch (choice) {
case '1': { // Start Pool
if (pool) {
printf("❌ Pool already started\n");
break;
}
// Create pool with custom reconnection configuration for faster testing
nostr_pool_reconnect_config_t* config = nostr_pool_reconnect_config_default();
config->ping_interval_seconds = 5; // Ping every 5 seconds for testing
pool = nostr_relay_pool_create(config);
if (!pool) {
printf("❌ Failed to create pool\n");
break;
}
if (nostr_relay_pool_add_relay(pool, "wss://relay.laantungir.net") != NOSTR_SUCCESS) {
printf("❌ Failed to add default relay\n");
nostr_relay_pool_destroy(pool);
pool = NULL;
break;
}
printf("✅ Pool started with wss://relay.laantungir.net\n");
now = time(NULL);
ctime_r(&now, timestamp);
timestamp[24] = '\0';
dprintf(log_fd, "[%s] 🏊 Pool started with default relay\n\n", timestamp);
break;
}
case '2': { // Stop Pool
if (!pool) {
printf("❌ Pool not started\n");
break;
}
// Close all subscriptions
for (int i = 0; i < subscription_count; i++) {
if (subscriptions[i]) {
nostr_pool_subscription_close(subscriptions[i]);
}
}
free(subscriptions);
subscriptions = NULL;
subscription_count = 0;
subscription_capacity = 0;
nostr_relay_pool_destroy(pool);
pool = NULL;
printf("✅ Pool stopped\n");
now = time(NULL);
ctime_r(&now, timestamp);
timestamp[24] = '\0';
dprintf(log_fd, "[%s] 🛑 Pool stopped\n\n", timestamp);
break;
}
case '3': { // Add relay
if (!pool) {
printf("❌ Pool not started\n");
break;
}
char* url = get_input("Enter relay URL", "wss://relay.example.com");
if (url && strlen(url) > 0) {
if (nostr_relay_pool_add_relay(pool, url) == NOSTR_SUCCESS) {
printf("✅ Relay added: %s\n", url);
now = time(NULL);
ctime_r(&now, timestamp);
timestamp[24] = '\0';
dprintf(log_fd, "[%s] Relay added: %s\n\n", timestamp, url);
} else {
printf("❌ Failed to add relay\n");
}
}
free(url);
break;
}
case '4': { // Remove relay
if (!pool) {
printf("❌ Pool not started\n");
break;
}
char* url = get_input("Enter relay URL to remove", NULL);
if (url && strlen(url) > 0) {
if (nostr_relay_pool_remove_relay(pool, url) == NOSTR_SUCCESS) {
printf("✅ Relay removed: %s\n", url);
now = time(NULL);
ctime_r(&now, timestamp);
timestamp[24] = '\0';
dprintf(log_fd, "[%s] Relay removed: %s\n\n", timestamp, url);
} else {
printf("❌ Failed to remove relay\n");
}
}
free(url);
break;
}
case '5': // Add subscription
add_subscription();
break;
case '6': // Remove subscription
remove_subscription();
break;
case '7': // Show status
show_pool_status();
break;
case '8': { // Test reconnection
if (!pool) {
printf("❌ Pool not started\n");
break;
}
char** relay_urls = NULL;
nostr_pool_relay_status_t* statuses = NULL;
int relay_count = nostr_relay_pool_list_relays(pool, &relay_urls, &statuses);
if (relay_count <= 0) {
printf("❌ No relays in pool\n");
break;
}
printf("\n--- Test Reconnection ---\n");
printf("Available relays:\n");
for (int i = 0; i < relay_count; i++) {
printf("%d. %s (%s)\n", i + 1, relay_urls[i],
statuses[i] == NOSTR_POOL_RELAY_CONNECTED ? "CONNECTED" : "NOT CONNECTED");
}
char* choice_input = get_input("Enter relay number to test reconnection with", NULL);
if (!choice_input || strlen(choice_input) == 0) {
for (int i = 0; i < relay_count; i++) free(relay_urls[i]);
free(relay_urls);
free(statuses);
free(choice_input);
break;
}
int choice = atoi(choice_input) - 1;
free(choice_input);
if (choice < 0 || choice >= relay_count) {
printf("❌ Invalid relay number\n");
for (int i = 0; i < relay_count; i++) free(relay_urls[i]);
free(relay_urls);
free(statuses);
break;
}
printf("🔄 Testing reconnection with %s...\n", relay_urls[choice]);
printf(" The pool is configured with automatic reconnection enabled.\n");
printf(" If the connection drops, it will automatically attempt to reconnect\n");
printf(" with exponential backoff (1s → 2s → 4s → 8s → 16s → 30s max).\n");
printf(" Connection health is monitored with ping/pong every 30 seconds.\n");
time_t now = time(NULL);
char timestamp[26];
ctime_r(&now, timestamp);
timestamp[24] = '\0';
dprintf(log_fd, "[%s] 🔄 TEST: Testing reconnection behavior with %s\n", timestamp, relay_urls[choice]);
dprintf(log_fd, " Pool configured with: auto-reconnect=ON, max_attempts=10, ping_interval=30s\n\n");
printf("✅ Reconnection test initiated. Monitor the status and logs for reconnection activity.\n");
for (int i = 0; i < relay_count; i++) free(relay_urls[i]);
free(relay_urls);
free(statuses);
break;
}
case '9': // Exit
running = 0;
break;
default:
printf("❌ Invalid choice\n");
break;
}
}
printf("\n🧹 Cleaning up...\n");
// Stop polling thread
running = 0;
pthread_join(poll_thread, NULL);
// Clean up pool and subscriptions
if (pool) {
for (int i = 0; i < subscription_count; i++) {
if (subscriptions[i]) {
nostr_pool_subscription_close(subscriptions[i]);
}
}
free(subscriptions);
nostr_relay_pool_destroy(pool);
printf("✅ Pool destroyed\n");
}
// Cleanup
nostr_cleanup();
close(log_fd);
printf("👋 Test completed\n");
return 0;
}

Binary file not shown.

188
tests/websocket_debug.c Normal file
View File

@@ -0,0 +1,188 @@
/*
* Simple WebSocket Debug Tool for NIP-17 Testing
*
* Connects to a relay and sends a subscription request to see what responses we get.
*/
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#include "../nostr_core/nostr_core.h"
#include "../nostr_websocket/nostr_websocket_tls.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
int main(int argc, char* argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <relay_url> [event_id]\n", argv[0]);
fprintf(stderr, "Example: %s wss://relay.laantungir.net 06cdf2cdd095ddb1ebe15d5b3c736b27a34de2683e847b871fe37d86ac998772\n");
return 1;
}
const char* relay_url = argv[1];
const char* event_id = (argc >= 3) ? argv[2] : NULL;
printf("🔍 WebSocket Debug Tool\n");
printf("=======================\n");
printf("Relay: %s\n", relay_url);
if (event_id) {
printf("Looking for event: %s\n", event_id);
}
printf("\n");
// Initialize crypto
if (nostr_init() != NOSTR_SUCCESS) {
fprintf(stderr, "Failed to initialize crypto\n");
return 1;
}
// Connect to relay
printf("🔌 Connecting to relay...\n");
nostr_ws_client_t* client = nostr_ws_connect(relay_url);
if (!client) {
fprintf(stderr, "Failed to connect to relay - nostr_ws_connect returned NULL\n");
return 1;
}
// Check initial state
nostr_ws_state_t initial_state = nostr_ws_get_state(client);
printf("Initial connection state: %d\n", (int)initial_state);
// Wait for connection
time_t start_time = time(NULL);
while (time(NULL) - start_time < 10) { // 10 second timeout
nostr_ws_state_t state = nostr_ws_get_state(client);
if (state == NOSTR_WS_CONNECTED) {
printf("✅ Connected!\n");
break;
} else if (state == NOSTR_WS_ERROR) {
fprintf(stderr, "❌ Connection failed\n");
nostr_ws_close(client);
return 1;
}
usleep(100000); // 100ms
}
if (nostr_ws_get_state(client) != NOSTR_WS_CONNECTED) {
fprintf(stderr, "❌ Connection timeout\n");
nostr_ws_close(client);
return 1;
}
// Send subscription request
printf("📡 Sending subscription request...\n");
// Create filter for kind 1059 events
cJSON* filter = cJSON_CreateObject();
cJSON* kinds = cJSON_CreateArray();
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1059));
cJSON_AddItemToObject(filter, "kinds", kinds);
// If we have a specific event ID, add it to the filter
if (event_id) {
cJSON* ids = cJSON_CreateArray();
cJSON_AddItemToArray(ids, cJSON_CreateString(event_id));
cJSON_AddItemToObject(filter, "ids", ids);
}
char* filter_json = cJSON_PrintUnformatted(filter);
printf("Filter: %s\n", filter_json);
// Send REQ message
char subscription_id[32];
snprintf(subscription_id, sizeof(subscription_id), "debug_%ld", time(NULL));
if (nostr_relay_send_req(client, subscription_id, filter) < 0) {
fprintf(stderr, "Failed to send subscription request\n");
cJSON_Delete(filter);
free(filter_json);
nostr_ws_close(client);
return 1;
}
cJSON_Delete(filter);
free(filter_json);
printf("✅ Subscription sent (ID: %s)\n", subscription_id);
printf("⏳ Listening for responses (30 seconds)...\n");
printf("Press Ctrl+C to stop\n\n");
// Listen for responses
start_time = time(NULL);
int message_count = 0;
while (time(NULL) - start_time < 30) { // 30 second timeout
char buffer[16384];
int len = nostr_ws_receive(client, buffer, sizeof(buffer) - 1, 1000); // 1 second timeout
if (len > 0) {
buffer[len] = '\0';
message_count++;
printf("📨 Message %d:\n", message_count);
printf("%s\n", buffer);
// Parse the message
char* msg_type = NULL;
cJSON* parsed = NULL;
if (nostr_parse_relay_message(buffer, &msg_type, &parsed) == 0) {
if (msg_type && strcmp(msg_type, "EVENT") == 0) {
printf(" → EVENT received\n");
if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 3) {
cJSON* event = cJSON_GetArrayItem(parsed, 2);
if (event) {
cJSON* kind_item = cJSON_GetObjectItem(event, "kind");
cJSON* id_item = cJSON_GetObjectItem(event, "id");
if (kind_item && cJSON_IsNumber(kind_item)) {
printf(" → Kind: %d\n", (int)cJSON_GetNumberValue(kind_item));
}
if (id_item && cJSON_IsString(id_item)) {
printf(" → ID: %.12s...\n", cJSON_GetStringValue(id_item));
}
}
}
} else if (msg_type && strcmp(msg_type, "EOSE") == 0) {
printf(" → EOSE (End of Stored Events)\n");
} else if (msg_type && strcmp(msg_type, "NOTICE") == 0) {
printf(" → NOTICE from relay\n");
if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 2) {
cJSON* notice_msg = cJSON_GetArrayItem(parsed, 1);
if (notice_msg && cJSON_IsString(notice_msg)) {
printf(" → Message: %s\n", cJSON_GetStringValue(notice_msg));
}
}
} else if (msg_type) {
printf(" → %s\n", msg_type);
}
if (msg_type) free(msg_type);
if (parsed) cJSON_Delete(parsed);
}
printf("\n");
} else if (len < 0) {
printf("❌ Receive error\n");
break;
}
// Small delay to prevent busy waiting
usleep(10000); // 10ms
}
printf("📊 Total messages received: %d\n", message_count);
// Send CLOSE message
printf("🔌 Closing subscription...\n");
nostr_relay_send_close(client, subscription_id);
// Close connection
nostr_ws_close(client);
nostr_cleanup();
printf("✅ Done\n");
return 0;
}