diff --git a/.gitignore b/.gitignore index 5257b230..a4a57a64 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ node_modules/ nostr-tools/ tiny-AES-c/ blossom/ - +ndk/ Trash/debug_tests/ node_modules/ diff --git a/POOL_API.md b/POOL_API.md new file mode 100644 index 00000000..153dd068 --- /dev/null +++ b/POOL_API.md @@ -0,0 +1,755 @@ +# Relay Pool API Reference + +This document describes the public API for the Nostr Relay Pool implementation in [`core_relay_pool.c`](nostr_core/core_relay_pool.c). + +## Function Summary + +| Function | Description | +|----------|-------------| +| [`nostr_relay_pool_create()`](nostr_core/core_relay_pool.c:219) | Create and initialize a new relay pool | +| [`nostr_relay_pool_destroy()`](nostr_core/core_relay_pool.c:304) | Destroy pool and cleanup all resources | +| [`nostr_relay_pool_add_relay()`](nostr_core/core_relay_pool.c:229) | Add a relay URL to the pool | +| [`nostr_relay_pool_remove_relay()`](nostr_core/core_relay_pool.c:273) | Remove a relay URL from the pool | +| [`nostr_relay_pool_subscribe()`](nostr_core/core_relay_pool.c:399) | Create async subscription with callbacks | +| [`nostr_pool_subscription_close()`](nostr_core/core_relay_pool.c:491) | Close subscription and free resources | +| [`nostr_relay_pool_run()`](nostr_core/core_relay_pool.c:1192) | Run event loop for specified timeout | +| [`nostr_relay_pool_poll()`](nostr_core/core_relay_pool.c:1232) | Single iteration poll and dispatch | +| [`nostr_relay_pool_query_sync()`](nostr_core/core_relay_pool.c:695) | Synchronous query returning event array | +| [`nostr_relay_pool_get_event()`](nostr_core/core_relay_pool.c:825) | Get single most recent event | +| [`nostr_relay_pool_publish()`](nostr_core/core_relay_pool.c:866) | Publish event and wait for OK responses | +| [`nostr_relay_pool_get_relay_status()`](nostr_core/core_relay_pool.c:944) | Get connection status for a relay | +| [`nostr_relay_pool_list_relays()`](nostr_core/core_relay_pool.c:960) | List all relays and their statuses | +| [`nostr_relay_pool_get_relay_stats()`](nostr_core/core_relay_pool.c:992) | Get detailed statistics for a relay | +| [`nostr_relay_pool_reset_relay_stats()`](nostr_core/core_relay_pool.c:1008) | Reset statistics for a relay | +| [`nostr_relay_pool_get_relay_query_latency()`](nostr_core/core_relay_pool.c:1045) | Get average query latency for a relay | + +## Pool Lifecycle + +### Create Pool +**Function:** [`nostr_relay_pool_create()`](nostr_core/core_relay_pool.c:219) +```c +nostr_relay_pool_t* nostr_relay_pool_create(void); +``` + +**Example:** +```c +#include "nostr_core.h" + +int main() { + // Create a new relay pool + nostr_relay_pool_t* pool = nostr_relay_pool_create(); + if (!pool) { + fprintf(stderr, "Failed to create relay pool\n"); + return -1; + } + + // Use the pool... + + // Clean up + nostr_relay_pool_destroy(pool); + return 0; +} +``` + +### Destroy Pool +**Function:** [`nostr_relay_pool_destroy()`](nostr_core/core_relay_pool.c:304) +```c +void nostr_relay_pool_destroy(nostr_relay_pool_t* pool); +``` + +**Example:** +```c +// Properly cleanup a relay pool +void cleanup_pool(nostr_relay_pool_t* pool) { + if (pool) { + // This will close all active subscriptions and relay connections + nostr_relay_pool_destroy(pool); + pool = NULL; + } +} +``` + +## Relay Management + +### Add Relay +**Function:** [`nostr_relay_pool_add_relay()`](nostr_core/core_relay_pool.c:229) +```c +int nostr_relay_pool_add_relay(nostr_relay_pool_t* pool, const char* relay_url); +``` + +**Example:** +```c +int setup_relays(nostr_relay_pool_t* pool) { + const char* relays[] = { + "wss://relay.damus.io", + "wss://nos.lol", + "wss://relay.nostr.band" + }; + + for (int i = 0; i < 3; i++) { + int result = nostr_relay_pool_add_relay(pool, relays[i]); + if (result != NOSTR_SUCCESS) { + fprintf(stderr, "Failed to add relay %s: %d\n", relays[i], result); + return -1; + } + printf("Added relay: %s\n", relays[i]); + } + + return 0; +} +``` + +### Remove Relay +**Function:** [`nostr_relay_pool_remove_relay()`](nostr_core/core_relay_pool.c:273) +```c +int nostr_relay_pool_remove_relay(nostr_relay_pool_t* pool, const char* relay_url); +``` + +**Example:** +```c +int remove_slow_relay(nostr_relay_pool_t* pool) { + const char* slow_relay = "wss://slow-relay.example.com"; + + int result = nostr_relay_pool_remove_relay(pool, slow_relay); + if (result == NOSTR_SUCCESS) { + printf("Successfully removed relay: %s\n", slow_relay); + } else { + printf("Failed to remove relay %s (may not exist)\n", slow_relay); + } + + return result; +} +``` + +## Subscriptions (Asynchronous) + +### Subscribe to Events +**Function:** [`nostr_relay_pool_subscribe()`](nostr_core/core_relay_pool.c:399) +```c +nostr_pool_subscription_t* nostr_relay_pool_subscribe( + nostr_relay_pool_t* pool, + const char** relay_urls, + int relay_count, + cJSON* filter, + void (*on_event)(cJSON* event, const char* relay_url, void* user_data), + void (*on_eose)(void* user_data), + void* user_data); +``` + +**Example:** +```c +#include "cjson/cJSON.h" + +// Event callback - called for each received event +void handle_event(cJSON* event, const char* relay_url, void* user_data) { + cJSON* content = cJSON_GetObjectItem(event, "content"); + cJSON* pubkey = cJSON_GetObjectItem(event, "pubkey"); + + if (content && pubkey) { + printf("Event from %s: %s (by %s)\n", + relay_url, + cJSON_GetStringValue(content), + cJSON_GetStringValue(pubkey)); + } +} + +// EOSE callback - called when all relays finish sending stored events +void handle_eose(void* user_data) { + printf("All relays finished sending stored events\n"); +} + +int subscribe_to_notes(nostr_relay_pool_t* pool) { + // Create filter for kind 1 (text notes) from last hour + cJSON* filter = cJSON_CreateObject(); + cJSON* kinds = cJSON_CreateArray(); + cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1)); + cJSON_AddItemToObject(filter, "kinds", kinds); + + time_t since = time(NULL) - 3600; // Last hour + cJSON_AddItemToObject(filter, "since", cJSON_CreateNumber(since)); + cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber(50)); + + // Subscribe to specific relays + const char* relay_urls[] = { + "wss://relay.damus.io", + "wss://nos.lol" + }; + + nostr_pool_subscription_t* sub = nostr_relay_pool_subscribe( + pool, + relay_urls, + 2, + filter, + handle_event, + handle_eose, + NULL // user_data + ); + + cJSON_Delete(filter); // Pool makes its own copy + + if (!sub) { + fprintf(stderr, "Failed to create subscription\n"); + return -1; + } + + // Drive the event loop to receive events + printf("Listening for events for 30 seconds...\n"); + nostr_relay_pool_run(pool, 30000); // 30 seconds + + // Close subscription + nostr_pool_subscription_close(sub); + return 0; +} +``` + +### Close Subscription +**Function:** [`nostr_pool_subscription_close()`](nostr_core/core_relay_pool.c:491) +```c +int nostr_pool_subscription_close(nostr_pool_subscription_t* subscription); +``` + +**Example:** +```c +// Subscription management with cleanup +typedef struct { + nostr_pool_subscription_t* subscription; + int event_count; + int should_stop; +} subscription_context_t; + +void event_counter(cJSON* event, const char* relay_url, void* user_data) { + subscription_context_t* ctx = (subscription_context_t*)user_data; + ctx->event_count++; + + printf("Received event #%d from %s\n", ctx->event_count, relay_url); + + // Stop after 10 events + if (ctx->event_count >= 10) { + ctx->should_stop = 1; + } +} + +int limited_subscription(nostr_relay_pool_t* pool) { + subscription_context_t ctx = {0}; + + // Create filter + cJSON* filter = cJSON_CreateObject(); + cJSON* kinds = cJSON_CreateArray(); + cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1)); + cJSON_AddItemToObject(filter, "kinds", kinds); + + const char* relay_urls[] = {"wss://relay.damus.io"}; + + ctx.subscription = nostr_relay_pool_subscribe( + pool, relay_urls, 1, filter, event_counter, NULL, &ctx); + + cJSON_Delete(filter); + + if (!ctx.subscription) { + return -1; + } + + // Poll until we should stop + while (!ctx.should_stop) { + int events = nostr_relay_pool_poll(pool, 100); + if (events < 0) break; + } + + // Clean up + int result = nostr_pool_subscription_close(ctx.subscription); + printf("Subscription closed with result: %d\n", result); + + return 0; +} +``` + +## Event Loop + +### Run Timed Loop +**Function:** [`nostr_relay_pool_run()`](nostr_core/core_relay_pool.c:1192) +```c +int nostr_relay_pool_run(nostr_relay_pool_t* pool, int timeout_ms); +``` + +**Example:** +```c +int run_event_loop(nostr_relay_pool_t* pool) { + printf("Starting event loop for 60 seconds...\n"); + + // Run for 60 seconds, processing all incoming events + int total_events = nostr_relay_pool_run(pool, 60000); + + if (total_events < 0) { + fprintf(stderr, "Event loop error\n"); + return -1; + } + + printf("Processed %d events total\n", total_events); + return 0; +} +``` + +### Single Poll Iteration +**Function:** [`nostr_relay_pool_poll()`](nostr_core/core_relay_pool.c:1232) +```c +int nostr_relay_pool_poll(nostr_relay_pool_t* pool, int timeout_ms); +``` + +**Example:** +```c +// Integration with custom main loop +int custom_main_loop(nostr_relay_pool_t* pool) { + int running = 1; + int total_events = 0; + + while (running) { + // Poll for Nostr events (non-blocking with 50ms timeout) + int events = nostr_relay_pool_poll(pool, 50); + if (events > 0) { + total_events += events; + printf("Processed %d events this iteration\n", events); + } + + // Do other work in your application + // handle_ui_events(); + // process_background_tasks(); + + // Check exit condition + // running = !should_exit(); + + // Simple exit after 100 events for demo + if (total_events >= 100) { + running = 0; + } + } + + printf("Main loop finished, processed %d total events\n", total_events); + return 0; +} +``` + +## Synchronous Operations + +### Query Multiple Events +**Function:** [`nostr_relay_pool_query_sync()`](nostr_core/core_relay_pool.c:695) +```c +cJSON** nostr_relay_pool_query_sync( + nostr_relay_pool_t* pool, + const char** relay_urls, + int relay_count, + cJSON* filter, + int* event_count, + int timeout_ms); +``` + +**Example:** +```c +int query_recent_notes(nostr_relay_pool_t* pool) { + // Create filter for recent text notes + cJSON* filter = cJSON_CreateObject(); + cJSON* kinds = cJSON_CreateArray(); + cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1)); + cJSON_AddItemToObject(filter, "kinds", kinds); + cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber(20)); + + const char* relay_urls[] = { + "wss://relay.damus.io", + "wss://nos.lol" + }; + + int event_count = 0; + cJSON** events = nostr_relay_pool_query_sync( + pool, relay_urls, 2, filter, &event_count, 10000); // 10 second timeout + + cJSON_Delete(filter); + + if (!events) { + printf("No events received or query failed\n"); + return -1; + } + + printf("Received %d events:\n", event_count); + for (int i = 0; i < event_count; i++) { + cJSON* content = cJSON_GetObjectItem(events[i], "content"); + if (content) { + printf(" %d: %s\n", i + 1, cJSON_GetStringValue(content)); + } + + // Free each event + cJSON_Delete(events[i]); + } + + // Free the events array + free(events); + return event_count; +} +``` + +### Get Single Most Recent Event +**Function:** [`nostr_relay_pool_get_event()`](nostr_core/core_relay_pool.c:825) +```c +cJSON* nostr_relay_pool_get_event( + nostr_relay_pool_t* pool, + const char** relay_urls, + int relay_count, + cJSON* filter, + int timeout_ms); +``` + +**Example:** +```c +int get_latest_note_from_pubkey(nostr_relay_pool_t* pool, const char* pubkey_hex) { + // Create filter for specific author's notes + cJSON* filter = cJSON_CreateObject(); + cJSON* kinds = cJSON_CreateArray(); + cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1)); + cJSON_AddItemToObject(filter, "kinds", kinds); + + cJSON* authors = cJSON_CreateArray(); + cJSON_AddItemToArray(authors, cJSON_CreateString(pubkey_hex)); + cJSON_AddItemToObject(filter, "authors", authors); + + cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber(1)); + + const char* relay_urls[] = {"wss://relay.damus.io"}; + + cJSON* event = nostr_relay_pool_get_event( + pool, relay_urls, 1, filter, 5000); // 5 second timeout + + cJSON_Delete(filter); + + if (!event) { + printf("No recent event found for pubkey %s\n", pubkey_hex); + return -1; + } + + cJSON* content = cJSON_GetObjectItem(event, "content"); + cJSON* created_at = cJSON_GetObjectItem(event, "created_at"); + + if (content && created_at) { + printf("Latest note: %s (created at %ld)\n", + cJSON_GetStringValue(content), + (long)cJSON_GetNumberValue(created_at)); + } + + cJSON_Delete(event); + return 0; +} +``` + +### Publish Event +**Function:** [`nostr_relay_pool_publish()`](nostr_core/core_relay_pool.c:866) +```c +int nostr_relay_pool_publish( + nostr_relay_pool_t* pool, + const char** relay_urls, + int relay_count, + cJSON* event); +``` + +**Example:** +```c +int publish_text_note(nostr_relay_pool_t* pool, const char* content) { + // Create a basic text note event (this is simplified - real implementation + // would need proper signing with private key) + cJSON* event = cJSON_CreateObject(); + cJSON_AddItemToObject(event, "kind", cJSON_CreateNumber(1)); + cJSON_AddItemToObject(event, "content", cJSON_CreateString(content)); + cJSON_AddItemToObject(event, "created_at", cJSON_CreateNumber(time(NULL))); + + // In real usage, you'd add pubkey, id, sig fields here + cJSON_AddItemToObject(event, "pubkey", cJSON_CreateString("your_pubkey_hex")); + cJSON_AddItemToObject(event, "id", cJSON_CreateString("event_id_hash")); + cJSON_AddItemToObject(event, "sig", cJSON_CreateString("event_signature")); + cJSON_AddItemToObject(event, "tags", cJSON_CreateArray()); + + const char* relay_urls[] = { + "wss://relay.damus.io", + "wss://nos.lol", + "wss://relay.nostr.band" + }; + + printf("Publishing note: %s\n", content); + + int success_count = nostr_relay_pool_publish( + pool, relay_urls, 3, event); + + cJSON_Delete(event); + + printf("Successfully published to %d out of 3 relays\n", success_count); + + if (success_count == 0) { + fprintf(stderr, "Failed to publish to any relay\n"); + return -1; + } + + return success_count; +} +``` + +## Status and Statistics + +### Get Relay Status +**Function:** [`nostr_relay_pool_get_relay_status()`](nostr_core/core_relay_pool.c:944) +```c +nostr_pool_relay_status_t nostr_relay_pool_get_relay_status( + nostr_relay_pool_t* pool, + const char* relay_url); +``` + +**Example:** +```c +void check_relay_status(nostr_relay_pool_t* pool, const char* relay_url) { + nostr_pool_relay_status_t status = nostr_relay_pool_get_relay_status(pool, relay_url); + + const char* status_str; + switch (status) { + case NOSTR_POOL_RELAY_DISCONNECTED: + status_str = "DISCONNECTED"; + break; + case NOSTR_POOL_RELAY_CONNECTING: + status_str = "CONNECTING"; + break; + case NOSTR_POOL_RELAY_CONNECTED: + status_str = "CONNECTED"; + break; + case NOSTR_POOL_RELAY_ERROR: + status_str = "ERROR"; + break; + default: + status_str = "UNKNOWN"; + break; + } + + printf("Relay %s status: %s\n", relay_url, status_str); +} +``` + +### List All Relays +**Function:** [`nostr_relay_pool_list_relays()`](nostr_core/core_relay_pool.c:960) +```c +int nostr_relay_pool_list_relays( + nostr_relay_pool_t* pool, + char*** relay_urls, + nostr_pool_relay_status_t** statuses); +``` + +**Example:** +```c +void print_all_relays(nostr_relay_pool_t* pool) { + char** relay_urls = NULL; + nostr_pool_relay_status_t* statuses = NULL; + + int count = nostr_relay_pool_list_relays(pool, &relay_urls, &statuses); + + if (count < 0) { + printf("Failed to list relays\n"); + return; + } + + if (count == 0) { + printf("No relays configured\n"); + return; + } + + printf("Configured relays (%d):\n", count); + for (int i = 0; i < count; i++) { + const char* status_str = (statuses[i] == NOSTR_POOL_RELAY_CONNECTED) ? + "CONNECTED" : "DISCONNECTED"; + printf(" %s - %s\n", relay_urls[i], status_str); + + // Free the duplicated URL string + free(relay_urls[i]); + } + + // Free the arrays + free(relay_urls); + free(statuses); +} +``` + +### Get Relay Statistics +**Function:** [`nostr_relay_pool_get_relay_stats()`](nostr_core/core_relay_pool.c:992) +```c +const nostr_relay_stats_t* nostr_relay_pool_get_relay_stats( + nostr_relay_pool_t* pool, + const char* relay_url); +``` + +**Example:** +```c +void print_relay_stats(nostr_relay_pool_t* pool, const char* relay_url) { + const nostr_relay_stats_t* stats = nostr_relay_pool_get_relay_stats(pool, relay_url); + + if (!stats) { + printf("No stats available for relay %s\n", relay_url); + return; + } + + printf("Statistics for %s:\n", relay_url); + printf(" Connection attempts: %d\n", stats->connection_attempts); + printf(" Connection failures: %d\n", stats->connection_failures); + printf(" Events received: %d\n", stats->events_received); + printf(" Events published: %d\n", stats->events_published); + printf(" Events published OK: %d\n", stats->events_published_ok); + printf(" Events published failed: %d\n", stats->events_published_failed); + printf(" Query latency avg: %.2f ms\n", stats->query_latency_avg); + printf(" Query samples: %d\n", stats->query_samples); + printf(" Publish latency avg: %.2f ms\n", stats->publish_latency_avg); + printf(" Publish samples: %d\n", stats->publish_samples); + + if (stats->last_event_time > 0) { + printf(" Last event: %ld seconds ago\n", + time(NULL) - stats->last_event_time); + } +} +``` + +### Reset Relay Statistics +**Function:** [`nostr_relay_pool_reset_relay_stats()`](nostr_core/core_relay_pool.c:1008) +```c +int nostr_relay_pool_reset_relay_stats( + nostr_relay_pool_t* pool, + const char* relay_url); +``` + +**Example:** +```c +void reset_stats_for_relay(nostr_relay_pool_t* pool, const char* relay_url) { + int result = nostr_relay_pool_reset_relay_stats(pool, relay_url); + + if (result == NOSTR_SUCCESS) { + printf("Successfully reset statistics for %s\n", relay_url); + } else { + printf("Failed to reset statistics for %s\n", relay_url); + } +} +``` + +### Get Query Latency +**Function:** [`nostr_relay_pool_get_relay_query_latency()`](nostr_core/core_relay_pool.c:1045) +```c +double nostr_relay_pool_get_relay_query_latency( + nostr_relay_pool_t* pool, + const char* relay_url); +``` + +**Example:** +```c +void check_relay_performance(nostr_relay_pool_t* pool) { + const char* relays[] = { + "wss://relay.damus.io", + "wss://nos.lol", + "wss://relay.nostr.band" + }; + + printf("Relay performance comparison:\n"); + for (int i = 0; i < 3; i++) { + double latency = nostr_relay_pool_get_relay_query_latency(pool, relays[i]); + + if (latency >= 0) { + printf(" %s: %.2f ms average query latency\n", relays[i], latency); + } else { + printf(" %s: No latency data available\n", relays[i]); + } + } +} +``` + +## Complete Example Application + +```c +#include "nostr_core.h" +#include "cjson/cJSON.h" +#include +#include +#include + +// Global context for the example +typedef struct { + int event_count; + int max_events; +} app_context_t; + +void on_text_note(cJSON* event, const char* relay_url, void* user_data) { + app_context_t* ctx = (app_context_t*)user_data; + + cJSON* content = cJSON_GetObjectItem(event, "content"); + if (content && cJSON_IsString(content)) { + printf("[%s] Note #%d: %s\n", + relay_url, ++ctx->event_count, cJSON_GetStringValue(content)); + } +} + +void on_subscription_complete(void* user_data) { + printf("All relays finished sending stored events\n"); +} + +int main() { + // Initialize pool + nostr_relay_pool_t* pool = nostr_relay_pool_create(); + if (!pool) { + fprintf(stderr, "Failed to create relay pool\n"); + return 1; + } + + // Add relays + const char* relays[] = { + "wss://relay.damus.io", + "wss://nos.lol" + }; + + for (int i = 0; i < 2; i++) { + if (nostr_relay_pool_add_relay(pool, relays[i]) != NOSTR_SUCCESS) { + fprintf(stderr, "Failed to add relay: %s\n", relays[i]); + } + } + + // Create filter for recent text notes + cJSON* filter = cJSON_CreateObject(); + cJSON* kinds = cJSON_CreateArray(); + cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1)); + cJSON_AddItemToObject(filter, "kinds", kinds); + cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber(10)); + + // Set up context + app_context_t ctx = {0, 10}; + + // Subscribe + nostr_pool_subscription_t* sub = nostr_relay_pool_subscribe( + pool, relays, 2, filter, on_text_note, on_subscription_complete, &ctx); + + cJSON_Delete(filter); + + if (!sub) { + fprintf(stderr, "Failed to create subscription\n"); + nostr_relay_pool_destroy(pool); + return 1; + } + + // Run event loop for 30 seconds + printf("Listening for events...\n"); + nostr_relay_pool_run(pool, 30000); + + // Print final stats + for (int i = 0; i < 2; i++) { + print_relay_stats(pool, relays[i]); + } + + // Cleanup + nostr_pool_subscription_close(sub); + nostr_relay_pool_destroy(pool); + + printf("Application finished. Received %d events total.\n", ctx.event_count); + return 0; +} +``` + +## Notes + +- All functions are **not thread-safe**. Use from a single thread or add external synchronization. +- **Memory ownership**: The pool duplicates filters and URLs internally. Caller owns returned events and must free them. +- **Event deduplication** is applied pool-wide using a circular buffer of 1000 event IDs. +- **Ping functionality** is currently disabled in this build. +- **Reconnection** happens on-demand when sending, but active subscriptions are not automatically re-sent after reconnect. +- **Polling model**: You must drive the event loop via [`nostr_relay_pool_run()`](nostr_core/core_relay_pool.c:1192) or [`nostr_relay_pool_poll()`](nostr_core/core_relay_pool.c:1232) to receive events. \ No newline at end of file diff --git a/debug.log b/debug.log index f6bea001..9d52076f 100644 --- a/debug.log +++ b/debug.log @@ -51,3 +51,19 @@ Final event: { "id": "0034dad20205876be164b768ba5efad8aaa23bdf16b273544785cae9f13a76f7", "sig": "e1ef97bf916f9e1fd94e231e62833e6ff000be29a92e16fc3656142e51abbf4ee8795ab4100f448e6712c1a76bb128016c24306b165077188fa8e5b82c72f23f" } + +=== NOSTR WebSocket Debug Log Started === +[11:57:56.083] SEND relay.laantungir.net:443: ["REQ", "sync_1_1759247875", { + "kinds": [1], + "limit": 1 + }] +[11:57:56.742] SEND nostr.mom:443: ["REQ", "sync_2_1759247875", { + "kinds": [1], + "limit": 1 + }] +[11:57:56.742] RECV relay.laantungir.net:443: ["EVENT","sync_1_1759247875",{"content":"🟠 New Bitcoin Block Mined!\n\nBlock Height: 917,088\nBlock Hash: 000000000000000000011050a3967e7d3936867a5afcae9babbdc5925c5b66d5\nTimestamp: 2025-09-30T15:46:37.000Z\nTransactions: 3,161\nBlock Size: 1.52 MB\nBlock Weight: 3,998,317 WU\nDifficulty: 1.42e+14\n\n#Bitcoin #Blockchain #Block917088","created_at":1759247235,"id":"20e0e3acf17ecde6e9ed49bd5b1c739eff1095d6e6c3c2775556b5c978359721","kind":1,"pubkey":"e568a76a4f8836be296d405eb41034260d55e2361e4b2ef88350a4003bbd5f9b","sig":"10817ffa6dafc128c8d2c16e9127d101e3a8730ae205be933d747294afd3766150e54073a6817fae3bdc5cff57436c6ab58ccb8c327e7013220b27ab2680d722","tags":[["t","bitcoin"],["t","blockchain"],["t","block"],["alt","New Bitcoin block mined - Block 917088"],["published_at","1759247197"],["client","info_bot","ws://127.0.0.1:7777"],["r","https://blockstream.info/block/000000000000000000011050a3967e7d3936867a5afcae9babbdc5925c5b66d5"],["new_block","true"],["block_height","917088"],["block_hash","000000000000000000011050a3967e7d3936867a5afcae9babbdc5925c5b66d5"],["block_time","1759247197"],["tx_count","3161"],["block_size","1590325"],["block_weight","3998317"],["difficulty","142342602928674.9"],["previous_block","00000000000000000000c50b4fcf7b8fac91e60893037a99c3dbab84f3aee313"],["mempool_tx_count","3087"],["mempool_size","1195411"],["memory_usage_pct","2.4"]]}] +[11:57:56.802] RECV relay.laantungir.net:443: ["EOSE","sync_1_1759247875"] +[11:57:56.802] SEND relay.laantungir.net:443: ["CLOSE", "sync_1_1759247875"] +[11:57:56.895] RECV nostr.mom:443: ["EVENT","sync_2_1759247875",{"content":"\"No matter how economically or politically complex our human societies appear, they nevertheless remain accountable to the regularities and vagaries of the natural world.\"\n\n-Roy Sebag\n\n#TheQuoteShelf \n\n\n\nhttps://blossom.primal.net/6968f243f29a1f125f049ea798274b13efe44144724bf507cd39246fdd5e4ff6.webp","created_at":1759247875,"id":"0f241242cc645089e4c50a3cec0cf82ec11d7867cde7e257ad7651f836088bf6","kind":1,"pubkey":"356875ffd729b06eeb4c1d7a70a1f750045d067774d21c0faffe4af2bf96a2e8","sig":"d1665367d1ae571b81a624e2ca101f3267af66923389d6ef4f8f4adb452e7632a87f104f9f328363459aaf7537cae583952f169ce5d80a73b8371fe0d1d9ea60","tags":[["t","TheQuoteShelf"],["imeta","url https://blossom.primal.net/6968f243f29a1f125f049ea798274b13efe44144724bf507cd39246fdd5e4ff6.webp","m image/webp","ox 6968f243f29a1f125f049ea798274b13efe44144724bf507cd39246fdd5e4ff6","dim 979x551"]]}] +[11:57:56.905] RECV nostr.mom:443: ["EOSE","sync_2_1759247875"] +[11:57:56.905] SEND nostr.mom:443: ["CLOSE", "sync_2_1759247875"] diff --git a/nostr_core/core_relays.c b/nostr_core/core_relays.c index 07e955f6..dd3dec83 100644 --- a/nostr_core/core_relays.c +++ b/nostr_core/core_relays.c @@ -13,7 +13,7 @@ #define _GNU_SOURCE #define _POSIX_C_SOURCE 200809L -#include "nostr_common.h" +#include "nostr_core.h" #include #include #include @@ -26,6 +26,9 @@ // cJSON for JSON handling #include "../cjson/cJSON.h" +// NIP-42 Authentication +#include "nip042.h" + // ============================================================================= // TYPE DEFINITIONS FOR SYNCHRONOUS RELAY QUERIES // ============================================================================= @@ -51,6 +54,12 @@ typedef struct { cJSON** events; // Array of events from this relay int events_capacity; // Allocated capacity char subscription_id[32]; // Unique subscription ID + + // NIP-42 Authentication fields + nostr_auth_state_t auth_state; // Current authentication state + char auth_challenge[NOSTR_NIP42_MAX_CHALLENGE_LENGTH]; // Stored challenge + time_t auth_challenge_time; // When challenge was received + int nip42_enabled; // Whether NIP-42 is enabled for this relay } relay_connection_t; @@ -65,7 +74,9 @@ cJSON** synchronous_query_relays_with_progress( int* result_count, int relay_timeout_seconds, relay_progress_callback_t callback, - void* user_data) { + void* user_data, + int nip42_enabled, + const unsigned char* private_key) { if (!relay_urls || relay_count <= 0 || !filter || !result_count) { if (result_count) *result_count = 0; @@ -95,11 +106,17 @@ cJSON** synchronous_query_relays_with_progress( relays[i].last_activity = start_time; relays[i].events_capacity = 10; relays[i].events = malloc(relays[i].events_capacity * sizeof(cJSON*)); - + + // Initialize NIP-42 authentication fields + relays[i].auth_state = NOSTR_AUTH_STATE_NONE; + memset(relays[i].auth_challenge, 0, sizeof(relays[i].auth_challenge)); + relays[i].auth_challenge_time = 0; + relays[i].nip42_enabled = nip42_enabled; + // Generate unique subscription ID - snprintf(relays[i].subscription_id, sizeof(relays[i].subscription_id), + snprintf(relays[i].subscription_id, sizeof(relays[i].subscription_id), "sync_%d_%ld", i, start_time); - + if (callback) { callback(relays[i].url, "connecting", NULL, 0, relay_count, 0, user_data); } @@ -191,19 +208,50 @@ cJSON** synchronous_query_relays_with_progress( cJSON* parsed = NULL; if (nostr_parse_relay_message(buffer, &msg_type, &parsed) == 0) { - if (msg_type && strcmp(msg_type, "EVENT") == 0) { + if (msg_type && strcmp(msg_type, "AUTH") == 0) { + // Handle AUTH challenge message: ["AUTH", ] + if (relay->nip42_enabled && private_key && cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 2) { + cJSON* challenge_json = cJSON_GetArrayItem(parsed, 1); + if (cJSON_IsString(challenge_json)) { + const char* challenge = cJSON_GetStringValue(challenge_json); + + // Store challenge and attempt authentication + strncpy(relay->auth_challenge, challenge, sizeof(relay->auth_challenge) - 1); + relay->auth_challenge[sizeof(relay->auth_challenge) - 1] = '\0'; + relay->auth_challenge_time = time(NULL); + relay->auth_state = NOSTR_AUTH_STATE_CHALLENGE_RECEIVED; + + // Create and send authentication event + cJSON* auth_event = nostr_nip42_create_auth_event(challenge, relay->url, private_key, 0); + if (auth_event) { + char* auth_message = nostr_nip42_create_auth_message(auth_event); + if (auth_message) { + if (nostr_ws_send_text(relay->client, auth_message) >= 0) { + relay->auth_state = NOSTR_AUTH_STATE_AUTHENTICATING; + if (callback) { + callback(relay->url, "authenticating", NULL, 0, relay_count, completed_relays, user_data); + } + } + free(auth_message); + } + cJSON_Delete(auth_event); + } + } + } + + } else if (msg_type && strcmp(msg_type, "EVENT") == 0) { // Handle EVENT message if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 3) { cJSON* sub_id_json = cJSON_GetArrayItem(parsed, 1); cJSON* event = cJSON_GetArrayItem(parsed, 2); - + if (cJSON_IsString(sub_id_json) && event && strcmp(cJSON_GetStringValue(sub_id_json), relay->subscription_id) == 0) { - + cJSON* event_id_json = cJSON_GetObjectItem(event, "id"); if (event_id_json && cJSON_IsString(event_id_json)) { const char* event_id = cJSON_GetStringValue(event_id_json); - + // Check for duplicate int is_duplicate = 0; for (int j = 0; j < seen_count; j++) { @@ -212,31 +260,31 @@ cJSON** synchronous_query_relays_with_progress( break; } } - + if (!is_duplicate && seen_count < 1000) { // New event - add to seen list strncpy(seen_event_ids[seen_count], event_id, 64); seen_event_ids[seen_count][64] = '\0'; seen_count++; total_unique_events++; - + // Store event in relay's array if (relay->events_received >= relay->events_capacity) { relay->events_capacity *= 2; - relay->events = realloc(relay->events, + relay->events = realloc(relay->events, relay->events_capacity * sizeof(cJSON*)); } - + relay->events[relay->events_received] = cJSON_Duplicate(event, 1); relay->events_received++; relay->state = RELAY_STATE_ACTIVE; - + if (callback) { - callback(relay->url, "event_found", event_id, - relay->events_received, relay_count, + callback(relay->url, "event_found", event_id, + relay->events_received, relay_count, completed_relays, user_data); } - + // For FIRST_RESULT mode, return immediately if (mode == RELAY_QUERY_FIRST_RESULT) { result_array = malloc(sizeof(cJSON*)); @@ -244,7 +292,7 @@ cJSON** synchronous_query_relays_with_progress( result_array[0] = cJSON_Duplicate(event, 1); *result_count = 1; if (callback) { - callback(NULL, "first_result", event_id, + callback(NULL, "first_result", event_id, 1, relay_count, completed_relays, user_data); } } @@ -254,7 +302,7 @@ cJSON** synchronous_query_relays_with_progress( } } } - + } else if (msg_type && strcmp(msg_type, "EOSE") == 0) { // Handle End of Stored Events cJSON* sub_id_json = cJSON_GetArrayItem(parsed, 1); @@ -400,7 +448,9 @@ publish_result_t* synchronous_publish_event_with_progress( int* success_count, int relay_timeout_seconds, publish_progress_callback_t callback, - void* user_data) { + void* user_data, + int nip42_enabled, + const unsigned char* private_key) { if (!relay_urls || relay_count <= 0 || !event || !success_count) { if (success_count) *success_count = 0; @@ -443,7 +493,13 @@ publish_result_t* synchronous_publish_event_with_progress( relays[i].state = RELAY_STATE_CONNECTING; relays[i].last_activity = start_time; results[i] = PUBLISH_ERROR; // Default to error - + + // Initialize NIP-42 authentication fields + relays[i].auth_state = NOSTR_AUTH_STATE_NONE; + memset(relays[i].auth_challenge, 0, sizeof(relays[i].auth_challenge)); + relays[i].auth_challenge_time = 0; + relays[i].nip42_enabled = nip42_enabled; + if (callback) { callback(relays[i].url, "connecting", NULL, 0, relay_count, 0, user_data); } @@ -535,34 +591,65 @@ publish_result_t* synchronous_publish_event_with_progress( char* msg_type = NULL; cJSON* parsed = NULL; if (nostr_parse_relay_message(buffer, &msg_type, &parsed) == 0) { - - if (msg_type && strcmp(msg_type, "OK") == 0) { + + if (msg_type && strcmp(msg_type, "AUTH") == 0) { + // Handle AUTH challenge message: ["AUTH", ] + if (relay->nip42_enabled && private_key && cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 2) { + cJSON* challenge_json = cJSON_GetArrayItem(parsed, 1); + if (cJSON_IsString(challenge_json)) { + const char* challenge = cJSON_GetStringValue(challenge_json); + + // Store challenge and attempt authentication + strncpy(relay->auth_challenge, challenge, sizeof(relay->auth_challenge) - 1); + relay->auth_challenge[sizeof(relay->auth_challenge) - 1] = '\0'; + relay->auth_challenge_time = time(NULL); + relay->auth_state = NOSTR_AUTH_STATE_CHALLENGE_RECEIVED; + + // Create and send authentication event + cJSON* auth_event = nostr_nip42_create_auth_event(challenge, relay->url, private_key, 0); + if (auth_event) { + char* auth_message = nostr_nip42_create_auth_message(auth_event); + if (auth_message) { + if (nostr_ws_send_text(relay->client, auth_message) >= 0) { + relay->auth_state = NOSTR_AUTH_STATE_AUTHENTICATING; + if (callback) { + callback(relay->url, "authenticating", NULL, 0, relay_count, completed_relays, user_data); + } + } + free(auth_message); + } + cJSON_Delete(auth_event); + } + } + } + + } else if (msg_type && strcmp(msg_type, "OK") == 0) { // Handle OK message: ["OK", , , ] if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 3) { cJSON* ok_event_id = cJSON_GetArrayItem(parsed, 1); cJSON* accepted = cJSON_GetArrayItem(parsed, 2); cJSON* message = cJSON_GetArrayItem(parsed, 3); - + // Verify this OK is for our event if (ok_event_id && cJSON_IsString(ok_event_id) && event_id && strcmp(cJSON_GetStringValue(ok_event_id), event_id) == 0) { - + relay->state = RELAY_STATE_EOSE_RECEIVED; // Reuse for "completed" active_relays--; completed_relays++; - + const char* ok_message = ""; if (message && cJSON_IsString(message)) { ok_message = cJSON_GetStringValue(message); } - + if (accepted && cJSON_IsBool(accepted) && cJSON_IsTrue(accepted)) { // Event accepted results[i] = PUBLISH_SUCCESS; (*success_count)++; if (callback) { - callback(relay->url, "accepted", ok_message, + callback(relay->url, "accepted", ok_message, *success_count, relay_count, completed_relays, user_data); } } else { @@ -570,11 +657,11 @@ publish_result_t* synchronous_publish_event_with_progress( results[i] = PUBLISH_REJECTED; if (callback) { - callback(relay->url, "rejected", ok_message, + callback(relay->url, "rejected", ok_message, *success_count, relay_count, completed_relays, user_data); } } - + // Close connection nostr_ws_close(relay->client); relay->client = NULL; diff --git a/nostr_core/nostr_common.h b/nostr_core/nostr_common.h index f9937e67..9d52aca3 100644 --- a/nostr_core/nostr_common.h +++ b/nostr_core/nostr_common.h @@ -81,66 +81,11 @@ // Forward declaration for cJSON (to avoid requiring cJSON.h in header) struct cJSON; -// Relay query modes -typedef enum { - RELAY_QUERY_FIRST_RESULT, // Return as soon as first event is received - RELAY_QUERY_MOST_RECENT, // Return the most recent event from all relays - RELAY_QUERY_ALL_RESULTS // Return all unique events from all relays -} relay_query_mode_t; - -// Publish result types -typedef enum { - PUBLISH_SUCCESS, // Event was accepted by relay - PUBLISH_REJECTED, // Event was rejected by relay - PUBLISH_TIMEOUT, // No response within timeout - PUBLISH_ERROR // Connection or other error -} publish_result_t; - -// Progress callback function types -typedef void (*relay_progress_callback_t)( - const char* relay_url, - const char* status, - const char* event_id, - int events_received, - int total_relays, - int completed_relays, - void* user_data); - -typedef void (*publish_progress_callback_t)( - const char* relay_url, - const char* status, - const char* message, - int success_count, - int total_relays, - int completed_relays, - void* user_data); - // Function declarations const char* nostr_strerror(int error_code); -// Library initialization functions +// Library initialization functions int nostr_init(void); void nostr_cleanup(void); -// Relay query functions -struct cJSON** synchronous_query_relays_with_progress( - const char** relay_urls, - int relay_count, - struct cJSON* filter, - relay_query_mode_t mode, - int* result_count, - int relay_timeout_seconds, - relay_progress_callback_t callback, - void* user_data); - -// Relay publish functions -publish_result_t* synchronous_publish_event_with_progress( - const char** relay_urls, - int relay_count, - struct cJSON* event, - int* success_count, - int relay_timeout_seconds, - publish_progress_callback_t callback, - void* user_data); - #endif // NOSTR_COMMON_H diff --git a/nostr_core/nostr_core.h b/nostr_core/nostr_core.h index 4416ca79..5963ee90 100644 --- a/nostr_core/nostr_core.h +++ b/nostr_core/nostr_core.h @@ -82,6 +82,15 @@ * - nostr_auth_rule_add() -> Add authentication rule * - nostr_auth_rule_remove() -> Remove authentication rule * + * RELAY OPERATIONS: + * - synchronous_query_relays_with_progress() -> One-off query from multiple relays + * - synchronous_publish_event_with_progress() -> One-off publish to multiple relays + * * + * RELAY POOL OPERATIONS: + * - nostr_relay_pool_create() -> Create relay pool for persistent connections + * - nostr_relay_pool_subscribe() -> Subscribe to events with callbacks + * - nostr_relay_pool_run() -> Run event loop for receiving events + * SYSTEM FUNCTIONS: * - nostr_crypto_init() -> Initialize crypto subsystem * - nostr_crypto_cleanup() -> Cleanup crypto subsystem @@ -142,6 +151,155 @@ extern "C" { // Authentication and request validation system #include "request_validator.h" // Request validation and authentication rules +// Relay pool types and functions +typedef enum { + NOSTR_POOL_RELAY_DISCONNECTED = 0, + NOSTR_POOL_RELAY_CONNECTING = 1, + NOSTR_POOL_RELAY_CONNECTED = 2, + NOSTR_POOL_RELAY_ERROR = -1 +} nostr_pool_relay_status_t; + +typedef struct { + int connection_attempts; + int connection_failures; + int events_received; + int events_published; + int events_published_ok; + int events_published_failed; + time_t last_event_time; + time_t connection_uptime_start; + double ping_latency_avg; + double ping_latency_min; + double ping_latency_max; + double ping_latency_current; + int ping_samples; + double query_latency_avg; + double query_latency_min; + double query_latency_max; + int query_samples; + double publish_latency_avg; + int publish_samples; +} nostr_relay_stats_t; + +typedef struct nostr_relay_pool nostr_relay_pool_t; +typedef struct nostr_pool_subscription nostr_pool_subscription_t; + +// Relay pool management functions +nostr_relay_pool_t* nostr_relay_pool_create(void); +int nostr_relay_pool_add_relay(nostr_relay_pool_t* pool, const char* relay_url); +int nostr_relay_pool_remove_relay(nostr_relay_pool_t* pool, const char* relay_url); +void nostr_relay_pool_destroy(nostr_relay_pool_t* pool); + +// Subscription management +nostr_pool_subscription_t* nostr_relay_pool_subscribe( + nostr_relay_pool_t* pool, + const char** relay_urls, + int relay_count, + cJSON* filter, + void (*on_event)(cJSON* event, const char* relay_url, void* user_data), + void (*on_eose)(void* user_data), + void* user_data); +int nostr_pool_subscription_close(nostr_pool_subscription_t* subscription); + +// Event loop functions +int nostr_relay_pool_run(nostr_relay_pool_t* pool, int timeout_ms); +int nostr_relay_pool_poll(nostr_relay_pool_t* pool, int timeout_ms); + +// Synchronous query/publish functions +cJSON** nostr_relay_pool_query_sync( + nostr_relay_pool_t* pool, + const char** relay_urls, + int relay_count, + cJSON* filter, + int* event_count, + int timeout_ms); +cJSON* nostr_relay_pool_get_event( + nostr_relay_pool_t* pool, + const char** relay_urls, + int relay_count, + cJSON* filter, + int timeout_ms); +int nostr_relay_pool_publish( + nostr_relay_pool_t* pool, + const char** relay_urls, + int relay_count, + cJSON* event); + +// Status and statistics functions +nostr_pool_relay_status_t nostr_relay_pool_get_relay_status( + nostr_relay_pool_t* pool, + const char* relay_url); +int nostr_relay_pool_list_relays( + nostr_relay_pool_t* pool, + char*** relay_urls, + nostr_pool_relay_status_t** statuses); +const nostr_relay_stats_t* nostr_relay_pool_get_relay_stats( + nostr_relay_pool_t* pool, + const char* relay_url); +int nostr_relay_pool_reset_relay_stats( + nostr_relay_pool_t* pool, + const char* relay_url); +double nostr_relay_pool_get_relay_query_latency( + nostr_relay_pool_t* pool, + const char* relay_url); + +// Synchronous relay operations (one-off queries/publishes) +typedef enum { + RELAY_QUERY_FIRST_RESULT, // Return as soon as first event is received + RELAY_QUERY_MOST_RECENT, // Return the most recent event from all relays + RELAY_QUERY_ALL_RESULTS // Return all unique events from all relays +} relay_query_mode_t; + +typedef enum { + PUBLISH_SUCCESS, // Event was accepted by relay + PUBLISH_REJECTED, // Event was rejected by relay + PUBLISH_TIMEOUT, // No response within timeout + PUBLISH_ERROR // Connection or other error +} publish_result_t; + +typedef void (*relay_progress_callback_t)( + const char* relay_url, + const char* status, + const char* event_id, + int events_received, + int total_relays, + int completed_relays, + void* user_data); + +typedef void (*publish_progress_callback_t)( + const char* relay_url, + const char* status, + const char* message, + int success_count, + int total_relays, + int completed_relays, + void* user_data); + +// Synchronous relay query functions +struct cJSON** synchronous_query_relays_with_progress( + const char** relay_urls, + int relay_count, + struct cJSON* filter, + relay_query_mode_t mode, + int* result_count, + int relay_timeout_seconds, + relay_progress_callback_t callback, + void* user_data, + int nip42_enabled, + const unsigned char* private_key); + +// Synchronous relay publish functions +publish_result_t* synchronous_publish_event_with_progress( + const char** relay_urls, + int relay_count, + struct cJSON* event, + int* success_count, + int relay_timeout_seconds, + publish_progress_callback_t callback, + void* user_data, + int nip42_enabled, + const unsigned char* private_key); + // Relay communication functions are defined in nostr_common.h // WebSocket functions are defined in nostr_common.h diff --git a/nostr_core/request_validator.c b/nostr_core/request_validator.c index 8a62ffc0..70c5959f 100644 --- a/nostr_core/request_validator.c +++ b/nostr_core/request_validator.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -241,8 +242,10 @@ void nostr_request_validator_cleanup(void) { // CONVENIENCE FUNCTIONS //============================================================================= -int nostr_auth_check_upload(const char* pubkey, const char* auth_header, +int nostr_auth_check_upload(const char* pubkey, const char* auth_header, const char* hash, const char* mime_type, long file_size) { + (void)pubkey; // Parameter not used in this convenience function + nostr_request_t request = { .operation = "upload", .auth_header = auth_header, @@ -265,6 +268,8 @@ int nostr_auth_check_upload(const char* pubkey, const char* auth_header, } int nostr_auth_check_delete(const char* pubkey, const char* auth_header, const char* hash) { + (void)pubkey; // Parameter not used in this convenience function + nostr_request_t request = { .operation = "delete", .auth_header = auth_header, @@ -287,6 +292,8 @@ int nostr_auth_check_delete(const char* pubkey, const char* auth_header, const c } int nostr_auth_check_publish(const char* pubkey, struct cJSON* event) { + (void)pubkey; // Parameter not used in this convenience function + if (!event) { return NOSTR_ERROR_INVALID_INPUT; } @@ -459,6 +466,8 @@ static int validate_nostr_event(struct cJSON* event, const char* expected_hash, //============================================================================= static int sqlite_auth_init(const char* db_path, const char* app_name) { + (void)app_name; // Parameter not used in this implementation + if (g_auth_db) { return NOSTR_SUCCESS; // Already initialized } @@ -765,13 +774,15 @@ static int sqlite_auth_rule_update(const nostr_auth_rule_t* rule) { } static int sqlite_auth_rule_list(const char* operation, nostr_auth_rule_t** rules, int* count) { + (void)operation; // Parameter not used in this implementation + if (!g_auth_db || !rules || !count) { return NOSTR_ERROR_INVALID_INPUT; } - + *rules = NULL; *count = 0; - + // For now, return empty list - would implement full rule listing return NOSTR_SUCCESS; } @@ -1130,19 +1141,21 @@ static int evaluate_auth_rules(const char* pubkey, const char* operation, const static void generate_auth_cache_key(const char* pubkey, const char* operation, const char* hash, const char* mime_type, long file_size, char* cache_key, size_t key_size) { char temp_buffer[1024]; - snprintf(temp_buffer, sizeof(temp_buffer), "%s|%s|%s|%s|%ld", - pubkey ? pubkey : "", operation ? operation : "", - hash ? hash : "", mime_type ? mime_type : "", file_size); - + int written = snprintf(temp_buffer, sizeof(temp_buffer), "%s|%s|%s|%s|%ld", + pubkey ? pubkey : "", operation ? operation : "", + hash ? hash : "", mime_type ? mime_type : "", file_size); + // Generate SHA-256 hash of the key components for consistent cache keys unsigned char hash_bytes[32]; - if (nostr_sha256((unsigned char*)temp_buffer, strlen(temp_buffer), hash_bytes) == NOSTR_SUCCESS) { + size_t hash_len = (written >= 0 && (size_t)written < sizeof(temp_buffer)) ? (size_t)written : sizeof(temp_buffer) - 1; + if (nostr_sha256((unsigned char*)temp_buffer, hash_len, hash_bytes) == NOSTR_SUCCESS) { nostr_bytes_to_hex(hash_bytes, 32, cache_key); cache_key[64] = '\0'; // Ensure null termination } else { - // Fallback if hashing fails - strncpy(cache_key, temp_buffer, key_size - 1); - cache_key[key_size - 1] = '\0'; + // Fallback if hashing fails - safely copy up to key_size - 1 characters + size_t copy_len = (written >= 0 && (size_t)written < key_size - 1) ? (size_t)written : key_size - 1; + memcpy(cache_key, temp_buffer, copy_len); + cache_key[copy_len] = '\0'; } } @@ -1179,6 +1192,8 @@ int nostr_auth_rule_list(const char* operation, nostr_auth_rule_t** rules, int* } void nostr_auth_rules_free(nostr_auth_rule_t* rules, int count) { + (void)count; // Parameter not used in this implementation + if (rules) { free(rules); } @@ -1200,6 +1215,8 @@ int nostr_auth_cache_stats(int* hit_count, int* miss_count, int* entries) { } int nostr_auth_register_db_backend(const nostr_auth_db_interface_t* backend) { + (void)backend; // Parameter not used in this implementation + // For now, only SQLite backend is supported return NOSTR_ERROR_AUTH_RULES_BACKEND_NOT_FOUND; } diff --git a/nostr_core_lib.code-workspace b/nostr_core_lib.code-workspace new file mode 100644 index 00000000..fd3b45f9 --- /dev/null +++ b/nostr_core_lib.code-workspace @@ -0,0 +1,10 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "git.ignoreLimitWarning": true + } +} \ No newline at end of file