v0.3.8 - safety push

This commit is contained in:
Your Name
2025-09-18 10:18:15 -04:00
parent 3210b9e752
commit 9f3b3dd773
7 changed files with 6385 additions and 1318 deletions

View File

@@ -14,8 +14,12 @@
// External database connection (from main.c)
extern sqlite3* g_db;
// Global configuration manager instance
config_manager_t g_config_manager = {0};
// Global unified configuration cache instance
unified_config_cache_t g_unified_cache = {
.cache_lock = PTHREAD_MUTEX_INITIALIZER,
.cache_valid = 0,
.cache_expires = 0
};
char g_database_path[512] = {0};
// ================================
@@ -78,6 +82,126 @@ static cJSON* g_pending_config_event = NULL;
// Temporary storage for relay private key during first-time setup
static char g_temp_relay_privkey[65] = {0};
// ================================
// UNIFIED CACHE MANAGEMENT FUNCTIONS
// ================================
// Get cache timeout from environment variable or default (similar to request_validator)
static int get_cache_timeout(void) {
char *no_cache = getenv("GINX_NO_CACHE");
char *cache_timeout = getenv("GINX_CACHE_TIMEOUT");
if (no_cache && strcmp(no_cache, "1") == 0) {
return 0; // No caching
}
if (cache_timeout) {
int timeout = atoi(cache_timeout);
return (timeout >= 0) ? timeout : 300; // Use provided value or default
}
return 300; // Default 5 minutes
}
// Force cache refresh - invalidates current cache
void force_config_cache_refresh(void) {
pthread_mutex_lock(&g_unified_cache.cache_lock);
g_unified_cache.cache_valid = 0;
g_unified_cache.cache_expires = 0;
pthread_mutex_unlock(&g_unified_cache.cache_lock);
log_info("Configuration cache forcibly invalidated");
}
// Refresh unified cache from database
static int refresh_unified_cache_from_table(void) {
if (!g_db) {
log_error("Database not available for cache refresh");
return -1;
}
// Clear cache
memset(&g_unified_cache, 0, sizeof(g_unified_cache));
g_unified_cache.cache_lock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
// Load critical config values from table
const char* admin_pubkey = get_config_value_from_table("admin_pubkey");
if (admin_pubkey) {
strncpy(g_unified_cache.admin_pubkey, admin_pubkey, sizeof(g_unified_cache.admin_pubkey) - 1);
g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0';
}
const char* relay_pubkey = get_config_value_from_table("relay_pubkey");
if (relay_pubkey) {
strncpy(g_unified_cache.relay_pubkey, relay_pubkey, sizeof(g_unified_cache.relay_pubkey) - 1);
g_unified_cache.relay_pubkey[sizeof(g_unified_cache.relay_pubkey) - 1] = '\0';
}
// Load auth-related config
const char* auth_required = get_config_value_from_table("auth_required");
g_unified_cache.auth_required = (auth_required && strcmp(auth_required, "true") == 0) ? 1 : 0;
const char* admin_enabled = get_config_value_from_table("admin_enabled");
g_unified_cache.admin_enabled = (admin_enabled && strcmp(admin_enabled, "true") == 0) ? 1 : 0;
const char* max_file_size = get_config_value_from_table("max_file_size");
g_unified_cache.max_file_size = max_file_size ? atol(max_file_size) : 104857600; // 100MB default
const char* nip42_mode = get_config_value_from_table("nip42_mode");
if (nip42_mode) {
if (strcmp(nip42_mode, "disabled") == 0) {
g_unified_cache.nip42_mode = 0;
} else if (strcmp(nip42_mode, "required") == 0) {
g_unified_cache.nip42_mode = 2;
} else {
g_unified_cache.nip42_mode = 1; // Optional/enabled
}
} else {
g_unified_cache.nip42_mode = 1; // Default to optional/enabled
}
const char* challenge_timeout = get_config_value_from_table("nip42_challenge_timeout");
g_unified_cache.nip42_challenge_timeout = challenge_timeout ? atoi(challenge_timeout) : 600;
const char* time_tolerance = get_config_value_from_table("nip42_time_tolerance");
g_unified_cache.nip42_time_tolerance = time_tolerance ? atoi(time_tolerance) : 300;
// Set cache expiration
int cache_timeout = get_cache_timeout();
g_unified_cache.cache_expires = time(NULL) + cache_timeout;
g_unified_cache.cache_valid = 1;
log_info("Unified configuration cache refreshed from database");
return 0;
}
// Get admin pubkey from cache (with automatic refresh)
const char* get_admin_pubkey_cached(void) {
pthread_mutex_lock(&g_unified_cache.cache_lock);
// Check cache validity
if (!g_unified_cache.cache_valid || time(NULL) > g_unified_cache.cache_expires) {
refresh_unified_cache_from_table();
}
const char* result = g_unified_cache.admin_pubkey[0] ? g_unified_cache.admin_pubkey : NULL;
pthread_mutex_unlock(&g_unified_cache.cache_lock);
return result;
}
// Get relay pubkey from cache (with automatic refresh)
const char* get_relay_pubkey_cached(void) {
pthread_mutex_lock(&g_unified_cache.cache_lock);
// Check cache validity
if (!g_unified_cache.cache_valid || time(NULL) > g_unified_cache.cache_expires) {
refresh_unified_cache_from_table();
}
const char* result = g_unified_cache.relay_pubkey[0] ? g_unified_cache.relay_pubkey : NULL;
pthread_mutex_unlock(&g_unified_cache.cache_lock);
return result;
}
// ================================
// UTILITY FUNCTIONS
// ================================
@@ -254,15 +378,16 @@ cJSON* load_config_event_from_database(const char* relay_pubkey) {
sqlite3_stmt* stmt;
int rc;
// If we have admin pubkey, query by it; otherwise find the most recent kind 33334 event
if (strlen(g_config_manager.admin_pubkey) > 0) {
// Try to get admin pubkey from cache, otherwise find the most recent kind 33334 event
const char* admin_pubkey = get_admin_pubkey_cached();
if (admin_pubkey && strlen(admin_pubkey) > 0) {
sql = "SELECT id, pubkey, created_at, kind, content, sig, tags FROM events WHERE kind = 33334 AND pubkey = ? ORDER BY created_at DESC LIMIT 1";
rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
log_error("Failed to prepare configuration event query");
return NULL;
}
sqlite3_bind_text(stmt, 1, g_config_manager.admin_pubkey, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 1, admin_pubkey, -1, SQLITE_STATIC);
} else {
// During existing relay startup, we don't know the admin pubkey yet
// Look for any kind 33334 configuration event (should only be one per relay)
@@ -288,11 +413,8 @@ cJSON* load_config_event_from_database(const char* relay_pubkey) {
cJSON_AddStringToObject(event, "content", (const char*)sqlite3_column_text(stmt, 4));
cJSON_AddStringToObject(event, "sig", (const char*)sqlite3_column_text(stmt, 5));
// If we didn't have admin pubkey, store it now
if (strlen(g_config_manager.admin_pubkey) == 0) {
strncpy(g_config_manager.admin_pubkey, event_pubkey, sizeof(g_config_manager.admin_pubkey) - 1);
g_config_manager.admin_pubkey[sizeof(g_config_manager.admin_pubkey) - 1] = '\0';
}
// If we didn't have admin pubkey in cache, we should update the cache
// Note: This will be handled by the cache refresh mechanism automatically
// Parse tags JSON
const char* tags_str = (const char*)sqlite3_column_text(stmt, 6);
@@ -318,9 +440,28 @@ cJSON* load_config_event_from_database(const char* relay_pubkey) {
// ================================
const char* get_config_value(const char* key) {
static char buffer[CONFIG_VALUE_MAX_LENGTH];
if (!key) {
return NULL;
}
if (!key || !g_current_config) {
// Special fast path for frequently accessed keys via unified cache
if (strcmp(key, "admin_pubkey") == 0) {
return get_admin_pubkey_cached();
}
if (strcmp(key, "relay_pubkey") == 0) {
return get_relay_pubkey_cached();
}
// For other keys, try config table first
const char* table_value = get_config_value_from_table(key);
if (table_value) {
return table_value;
}
// Fallback to legacy event-based config for backward compatibility
// Use unified cache buffer instead of static buffer
if (!g_current_config) {
return NULL;
}
@@ -330,24 +471,30 @@ const char* get_config_value(const char* key) {
return NULL;
}
pthread_mutex_lock(&g_unified_cache.cache_lock);
cJSON* tag = NULL;
cJSON_ArrayForEach(tag, tags) {
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
cJSON* tag_key = cJSON_GetArrayItem(tag, 0);
cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
if (tag_key && tag_value &&
if (tag_key && tag_value &&
cJSON_IsString(tag_key) && cJSON_IsString(tag_value)) {
if (strcmp(cJSON_GetStringValue(tag_key), key) == 0) {
strncpy(buffer, cJSON_GetStringValue(tag_value), sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
return buffer;
strncpy(g_unified_cache.temp_buffer, cJSON_GetStringValue(tag_value),
sizeof(g_unified_cache.temp_buffer) - 1);
g_unified_cache.temp_buffer[sizeof(g_unified_cache.temp_buffer) - 1] = '\0';
pthread_mutex_unlock(&g_unified_cache.cache_lock);
return g_unified_cache.temp_buffer;
}
}
}
}
pthread_mutex_unlock(&g_unified_cache.cache_lock);
return NULL;
}
@@ -491,14 +638,44 @@ int init_configuration_system(const char* config_dir_override, const char* confi
log_info("Initializing event-based configuration system...");
// Clear configuration manager state
memset(&g_config_manager, 0, sizeof(config_manager_t));
g_config_manager.db = g_db;
// Initialize unified cache with proper structure initialization
pthread_mutex_lock(&g_unified_cache.cache_lock);
// For now, set empty paths for compatibility
g_config_manager.config_file_path[0] = '\0';
// Clear the entire cache structure
memset(&g_unified_cache, 0, sizeof(g_unified_cache));
log_success("Event-based configuration system initialized");
// Reinitialize the mutex after memset
g_unified_cache.cache_lock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
// Initialize basic cache state
g_unified_cache.cache_valid = 0;
g_unified_cache.cache_expires = 0;
// Initialize relay_info structure with default values
strncpy(g_unified_cache.relay_info.software, "https://git.laantungir.net/laantungir/c-relay.git",
sizeof(g_unified_cache.relay_info.software) - 1);
strncpy(g_unified_cache.relay_info.version, "0.2.0",
sizeof(g_unified_cache.relay_info.version) - 1);
// Initialize pow_config structure with defaults
g_unified_cache.pow_config.enabled = 1;
g_unified_cache.pow_config.min_pow_difficulty = 0;
g_unified_cache.pow_config.validation_flags = 1; // NOSTR_POW_VALIDATE_BASIC
g_unified_cache.pow_config.require_nonce_tag = 0;
g_unified_cache.pow_config.reject_lower_targets = 0;
g_unified_cache.pow_config.strict_format = 0;
g_unified_cache.pow_config.anti_spam_mode = 0;
// Initialize expiration_config structure with defaults
g_unified_cache.expiration_config.enabled = 1;
g_unified_cache.expiration_config.strict_mode = 1;
g_unified_cache.expiration_config.filter_responses = 1;
g_unified_cache.expiration_config.delete_expired = 0;
g_unified_cache.expiration_config.grace_period = 1;
pthread_mutex_unlock(&g_unified_cache.cache_lock);
log_success("Event-based configuration system initialized with unified cache structures");
return 0;
}
@@ -515,8 +692,38 @@ void cleanup_configuration_system(void) {
g_pending_config_event = NULL;
}
memset(&g_config_manager, 0, sizeof(config_manager_t));
log_success("Configuration system cleaned up");
// Clear unified cache with proper cleanup of JSON objects
pthread_mutex_lock(&g_unified_cache.cache_lock);
// Clean up relay_info JSON objects if they exist
if (g_unified_cache.relay_info.supported_nips) {
cJSON_Delete(g_unified_cache.relay_info.supported_nips);
}
if (g_unified_cache.relay_info.limitation) {
cJSON_Delete(g_unified_cache.relay_info.limitation);
}
if (g_unified_cache.relay_info.retention) {
cJSON_Delete(g_unified_cache.relay_info.retention);
}
if (g_unified_cache.relay_info.relay_countries) {
cJSON_Delete(g_unified_cache.relay_info.relay_countries);
}
if (g_unified_cache.relay_info.language_tags) {
cJSON_Delete(g_unified_cache.relay_info.language_tags);
}
if (g_unified_cache.relay_info.tags) {
cJSON_Delete(g_unified_cache.relay_info.tags);
}
if (g_unified_cache.relay_info.fees) {
cJSON_Delete(g_unified_cache.relay_info.fees);
}
// Clear the entire cache structure
memset(&g_unified_cache, 0, sizeof(g_unified_cache));
g_unified_cache.cache_lock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_unlock(&g_unified_cache.cache_lock);
log_success("Configuration system cleaned up with proper JSON cleanup");
}
int set_database_config(const char* key, const char* value, const char* changed_by) {
@@ -832,11 +1039,13 @@ int first_time_startup_sequence(const cli_options_t* cli_options) {
}
nostr_bytes_to_hex(relay_pubkey_bytes, 32, relay_pubkey);
// 3. Store keys in global config manager
strncpy(g_config_manager.admin_pubkey, admin_pubkey, sizeof(g_config_manager.admin_pubkey) - 1);
g_config_manager.admin_pubkey[sizeof(g_config_manager.admin_pubkey) - 1] = '\0';
strncpy(g_config_manager.relay_pubkey, relay_pubkey, sizeof(g_config_manager.relay_pubkey) - 1);
g_config_manager.relay_pubkey[sizeof(g_config_manager.relay_pubkey) - 1] = '\0';
// 3. Store keys in unified cache (will be added to database after init)
pthread_mutex_lock(&g_unified_cache.cache_lock);
strncpy(g_unified_cache.admin_pubkey, admin_pubkey, sizeof(g_unified_cache.admin_pubkey) - 1);
g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0';
strncpy(g_unified_cache.relay_pubkey, relay_pubkey, sizeof(g_unified_cache.relay_pubkey) - 1);
g_unified_cache.relay_pubkey[sizeof(g_unified_cache.relay_pubkey) - 1] = '\0';
pthread_mutex_unlock(&g_unified_cache.cache_lock);
// 4. Create database with relay pubkey name
if (create_database_with_relay_pubkey(relay_pubkey) != 0) {
@@ -904,8 +1113,11 @@ int startup_existing_relay(const char* relay_pubkey) {
log_info("Starting existing relay...");
printf(" Relay pubkey: %s\n", relay_pubkey);
// Store relay pubkey in global config manager
strncpy(g_config_manager.relay_pubkey, relay_pubkey, sizeof(g_config_manager.relay_pubkey) - 1);
// Store relay pubkey in unified cache
pthread_mutex_lock(&g_unified_cache.cache_lock);
strncpy(g_unified_cache.relay_pubkey, relay_pubkey, sizeof(g_unified_cache.relay_pubkey) - 1);
g_unified_cache.relay_pubkey[sizeof(g_unified_cache.relay_pubkey) - 1] = '\0';
pthread_mutex_unlock(&g_unified_cache.cache_lock);
// Set database path
char* db_name = get_database_name_from_relay_pubkey(relay_pubkey);
@@ -1408,8 +1620,9 @@ int process_configuration_event(const cJSON* event) {
// Verify it's from the admin
const char* event_pubkey = cJSON_GetStringValue(pubkey_obj);
if (strlen(g_config_manager.admin_pubkey) > 0) {
if (strcmp(event_pubkey, g_config_manager.admin_pubkey) != 0) {
const char* admin_pubkey = get_admin_pubkey_cached();
if (admin_pubkey && strlen(admin_pubkey) > 0) {
if (strcmp(event_pubkey, admin_pubkey) != 0) {
log_error("Configuration event not from authorized admin");
return -1;
}
@@ -1580,11 +1793,18 @@ int apply_configuration_from_event(const cJSON* event) {
// Update cached configuration
g_current_config = cJSON_Duplicate(event, 1);
// Extract admin pubkey if not already set
// Extract admin pubkey if not already in cache
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
if (pubkey_obj && strlen(g_config_manager.admin_pubkey) == 0) {
strncpy(g_config_manager.admin_pubkey, cJSON_GetStringValue(pubkey_obj),
sizeof(g_config_manager.admin_pubkey) - 1);
if (pubkey_obj) {
const char* event_pubkey = cJSON_GetStringValue(pubkey_obj);
const char* cached_admin_pubkey = get_admin_pubkey_cached();
if (!cached_admin_pubkey || strlen(cached_admin_pubkey) == 0) {
// Update cache with admin pubkey from event
pthread_mutex_lock(&g_unified_cache.cache_lock);
strncpy(g_unified_cache.admin_pubkey, event_pubkey, sizeof(g_unified_cache.admin_pubkey) - 1);
g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0';
pthread_mutex_unlock(&g_unified_cache.cache_lock);
}
}
// Apply runtime configuration changes
@@ -1651,15 +1871,17 @@ const char* get_config_value_from_table(const char* key) {
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
static char config_value_buffer[CONFIG_VALUE_MAX_LENGTH];
const char* result = NULL;
if (sqlite3_step(stmt) == SQLITE_ROW) {
const char* value = (char*)sqlite3_column_text(stmt, 0);
if (value) {
strncpy(config_value_buffer, value, sizeof(config_value_buffer) - 1);
config_value_buffer[sizeof(config_value_buffer) - 1] = '\0';
result = config_value_buffer;
// Use unified cache buffer with thread safety
pthread_mutex_lock(&g_unified_cache.cache_lock);
strncpy(g_unified_cache.temp_buffer, value, sizeof(g_unified_cache.temp_buffer) - 1);
g_unified_cache.temp_buffer[sizeof(g_unified_cache.temp_buffer) - 1] = '\0';
result = g_unified_cache.temp_buffer;
pthread_mutex_unlock(&g_unified_cache.cache_lock);
}
}
@@ -1783,6 +2005,52 @@ int populate_default_config_values(void) {
return 0;
}
// Add dynamically generated pubkeys to config table
int add_pubkeys_to_config_table(void) {
if (!g_db) {
log_error("Database not available for pubkey storage");
return -1;
}
log_info("Adding dynamically generated pubkeys to config table...");
// Get the pubkeys directly from unified cache (not through cached accessors to avoid circular dependency)
pthread_mutex_lock(&g_unified_cache.cache_lock);
const char* admin_pubkey = g_unified_cache.admin_pubkey[0] ? g_unified_cache.admin_pubkey : NULL;
const char* relay_pubkey = g_unified_cache.relay_pubkey[0] ? g_unified_cache.relay_pubkey : NULL;
pthread_mutex_unlock(&g_unified_cache.cache_lock);
if (!admin_pubkey || strlen(admin_pubkey) != 64) {
log_error("Admin pubkey not available or invalid for config table storage");
return -1;
}
if (!relay_pubkey || strlen(relay_pubkey) != 64) {
log_error("Relay pubkey not available or invalid for config table storage");
return -1;
}
// Store admin pubkey in config table
if (set_config_value_in_table("admin_pubkey", admin_pubkey, "string",
"Administrator public key", "authentication", 0) != 0) {
log_error("Failed to store admin_pubkey in config table");
return -1;
}
// Store relay pubkey in config table
if (set_config_value_in_table("relay_pubkey", relay_pubkey, "string",
"Relay public key", "relay", 0) != 0) {
log_error("Failed to store relay_pubkey in config table");
return -1;
}
log_success("Dynamically generated pubkeys added to config table");
printf(" Admin pubkey: %s\n", admin_pubkey);
printf(" Relay pubkey: %s\n", relay_pubkey);
return 0;
}
// ================================
// ADMIN EVENT PROCESSING FUNCTIONS
// ================================
@@ -2028,17 +2296,24 @@ int remove_auth_rule_from_config(const char* rule_type, const char* pattern_type
// Invalidate configuration cache
void invalidate_config_cache(void) {
// For now, just log that cache was invalidated
// In a full implementation, this would clear any cached config values
log_info("Configuration cache invalidated");
pthread_mutex_lock(&g_unified_cache.cache_lock);
g_unified_cache.cache_valid = 0;
g_unified_cache.cache_expires = 0;
pthread_mutex_unlock(&g_unified_cache.cache_lock);
log_info("Unified configuration cache invalidated");
}
// Reload configuration from table
int reload_config_from_table(void) {
// For now, just log that config was reloaded
// In a full implementation, this would reload all cached values from the table
log_info("Configuration reloaded from table");
return 0;
// Trigger a cache refresh by calling the refresh function directly
int result = refresh_unified_cache_from_table();
if (result == 0) {
log_info("Configuration reloaded from table");
} else {
log_error("Failed to reload configuration from table");
}
return result;
}
// ================================
@@ -2101,8 +2376,11 @@ int is_config_table_ready(void) {
int initialize_config_system_with_migration(void) {
log_info("Initializing configuration system with migration support...");
// Initialize config manager
memset(&g_config_manager, 0, sizeof(g_config_manager));
// Initialize unified cache and migration status
pthread_mutex_lock(&g_unified_cache.cache_lock);
g_unified_cache.cache_valid = 0;
g_unified_cache.cache_expires = 0;
pthread_mutex_unlock(&g_unified_cache.cache_lock);
memset(&g_migration_status, 0, sizeof(g_migration_status));
// For new installations, config table should already exist from embedded schema
@@ -2254,7 +2532,8 @@ int migrate_config_from_events_to_table(void) {
log_info("Migrating configuration from events to config table...");
// Load the most recent configuration event from database
cJSON* config_event = load_config_event_from_database(g_config_manager.relay_pubkey);
const char* relay_pubkey = get_relay_pubkey_cached();
cJSON* config_event = load_config_event_from_database(relay_pubkey);
if (!config_event) {
log_info("No existing configuration event found - migration not needed");
return 0;
@@ -2382,4 +2661,191 @@ int process_startup_config_event_with_fallback(const cJSON* event) {
log_error("Startup configuration processing failed even after populating defaults");
return -1;
}
// ================================
// DYNAMIC EVENT GENERATION FROM CONFIG TABLE
// ================================
// Generate synthetic kind 33334 configuration event from current config table data
cJSON* generate_config_event_from_table(void) {
if (!g_db) {
log_error("Database not available for config event generation");
return NULL;
}
log_info("Generating synthetic kind 33334 event from config table...");
// Get relay pubkey for event generation
const char* relay_pubkey = get_config_value("relay_pubkey");
if (!relay_pubkey || strlen(relay_pubkey) != 64) {
// Try to get from unified cache
relay_pubkey = get_relay_pubkey_cached();
if (!relay_pubkey || strlen(relay_pubkey) != 64) {
log_error("Relay pubkey not available for config event generation");
return NULL;
}
}
// Create the event structure
cJSON* event = cJSON_CreateObject();
if (!event) {
log_error("Failed to create config event object");
return NULL;
}
// Set basic event fields - we'll generate a synthetic event
cJSON_AddStringToObject(event, "id", "synthetic_config_event_id");
cJSON_AddStringToObject(event, "pubkey", relay_pubkey); // Use relay pubkey as event author
cJSON_AddNumberToObject(event, "created_at", (double)time(NULL));
cJSON_AddNumberToObject(event, "kind", 33334);
cJSON_AddStringToObject(event, "content", "C Nostr Relay Configuration");
cJSON_AddStringToObject(event, "sig", "synthetic_signature");
// Create tags array from config table
cJSON* tags = cJSON_CreateArray();
if (!tags) {
log_error("Failed to create tags array for config event");
cJSON_Delete(event);
return NULL;
}
// Add d tag with relay pubkey (addressable event identifier)
cJSON* d_tag = cJSON_CreateArray();
cJSON_AddItemToArray(d_tag, cJSON_CreateString("d"));
cJSON_AddItemToArray(d_tag, cJSON_CreateString(relay_pubkey));
cJSON_AddItemToArray(tags, d_tag);
// Query all configuration values from the config table
const char* sql = "SELECT key, value FROM config ORDER BY key";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
log_error("Failed to prepare config query for event generation");
cJSON_Delete(tags);
cJSON_Delete(event);
return NULL;
}
int config_items_added = 0;
// Add each config item as a tag
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char* key = (const char*)sqlite3_column_text(stmt, 0);
const char* value = (const char*)sqlite3_column_text(stmt, 1);
if (key && value) {
cJSON* config_tag = cJSON_CreateArray();
cJSON_AddItemToArray(config_tag, cJSON_CreateString(key));
cJSON_AddItemToArray(config_tag, cJSON_CreateString(value));
cJSON_AddItemToArray(tags, config_tag);
config_items_added++;
}
}
sqlite3_finalize(stmt);
if (config_items_added == 0) {
log_warning("No configuration items found in config table for event generation");
cJSON_Delete(tags);
cJSON_Delete(event);
return NULL;
}
// Add tags to event
cJSON_AddItemToObject(event, "tags", tags);
char success_msg[256];
snprintf(success_msg, sizeof(success_msg),
"Generated synthetic kind 33334 event with %d configuration items", config_items_added);
log_success(success_msg);
return event;
}
// Check if a REQ filter requests kind 33334 events
int req_filter_requests_config_events(const cJSON* filter) {
if (!filter || !cJSON_IsObject(filter)) {
return 0;
}
cJSON* kinds = cJSON_GetObjectItem(filter, "kinds");
if (!kinds || !cJSON_IsArray(kinds)) {
return 0;
}
// Check if kinds array contains 33334
cJSON* kind_item = NULL;
cJSON_ArrayForEach(kind_item, kinds) {
if (cJSON_IsNumber(kind_item) && (int)cJSON_GetNumberValue(kind_item) == 33334) {
return 1;
}
}
return 0;
}
// Generate synthetic config event data for subscription (callback approach)
cJSON* generate_synthetic_config_event_for_subscription(const char* sub_id, const cJSON* filters) {
if (!sub_id || !filters) {
return NULL;
}
// Check if any filter requests kind 33334
int requests_config = 0;
if (cJSON_IsArray(filters)) {
cJSON* filter = NULL;
cJSON_ArrayForEach(filter, filters) {
if (req_filter_requests_config_events(filter)) {
requests_config = 1;
break;
}
}
} else if (cJSON_IsObject(filters)) {
requests_config = req_filter_requests_config_events(filters);
}
if (!requests_config) {
// No config events requested
return NULL;
}
log_info("Generating synthetic kind 33334 event for subscription");
// Generate synthetic config event from table
cJSON* config_event = generate_config_event_from_table();
if (!config_event) {
log_error("Failed to generate synthetic config event");
return NULL;
}
// Create EVENT message for the subscription
cJSON* event_msg = cJSON_CreateArray();
cJSON_AddItemToArray(event_msg, cJSON_CreateString("EVENT"));
cJSON_AddItemToArray(event_msg, cJSON_CreateString(sub_id));
cJSON_AddItemToArray(event_msg, config_event);
log_success("Generated synthetic kind 33334 configuration event message");
return event_msg;
}
/**
* Generate a synthetic kind 33334 configuration event from config table data
* This allows WebSocket clients to fetch configuration via REQ messages
* Returns JSON string that must be freed by caller
*/
char* generate_config_event_json(void) {
// Use the existing cJSON function and convert to string
cJSON* event = generate_config_event_from_table();
if (!event) {
return NULL;
}
// Convert to JSON string
char* json_string = cJSON_Print(event);
cJSON_Delete(event);
return json_string;
}