Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cdeb569a16 | |||
| 2224448d12 |
@@ -1,223 +0,0 @@
|
||||
# NOSTR Core Library - Automatic Versioning System
|
||||
|
||||
## Overview
|
||||
|
||||
The NOSTR Core Library now features an automatic version increment system that automatically increments the patch version (e.g., v0.2.0 → v0.2.1) with each build. This ensures consistent versioning and traceability across builds.
|
||||
|
||||
## How It Works
|
||||
|
||||
### Version Format
|
||||
The library uses semantic versioning with the format: `vMAJOR.MINOR.PATCH`
|
||||
|
||||
- **MAJOR**: Incremented for breaking changes (manual)
|
||||
- **MINOR**: Incremented for new features (manual)
|
||||
- **PATCH**: Incremented automatically with each build
|
||||
|
||||
### Automatic Increment Process
|
||||
|
||||
1. **Version Detection**: The build system scans all git tags matching `v*.*.*` pattern
|
||||
2. **Highest Version**: Uses `sort -V` to find the numerically highest version (not chronologically latest)
|
||||
3. **Patch Increment**: Increments the patch number by 1
|
||||
4. **Git Tag Creation**: Creates a new git tag for the incremented version
|
||||
5. **File Generation**: Generates `nostr_core/version.h` and `nostr_core/version.c` with build metadata
|
||||
|
||||
### Generated Files
|
||||
|
||||
The system automatically generates two files during each build:
|
||||
|
||||
#### `nostr_core/version.h`
|
||||
```c
|
||||
#define VERSION_MAJOR 0
|
||||
#define VERSION_MINOR 2
|
||||
#define VERSION_PATCH 1
|
||||
#define VERSION_STRING "0.2.1"
|
||||
#define VERSION_TAG "v0.2.1"
|
||||
|
||||
#define BUILD_DATE "2025-08-09"
|
||||
#define BUILD_TIME "10:42:45"
|
||||
#define BUILD_TIMESTAMP "2025-08-09 10:42:45"
|
||||
|
||||
#define GIT_HASH "ca6b475"
|
||||
#define GIT_BRANCH "master"
|
||||
|
||||
// API functions
|
||||
const char* nostr_core_get_version(void);
|
||||
const char* nostr_core_get_version_full(void);
|
||||
const char* nostr_core_get_build_info(void);
|
||||
```
|
||||
|
||||
#### `nostr_core/version.c`
|
||||
Contains the implementation of the version API functions.
|
||||
|
||||
## Usage
|
||||
|
||||
### Building with Auto-Versioning
|
||||
|
||||
All major build targets automatically increment the version:
|
||||
|
||||
```bash
|
||||
# Build static library (increments version)
|
||||
./build.sh lib
|
||||
|
||||
# Build shared library (increments version)
|
||||
./build.sh shared
|
||||
|
||||
# Build all libraries (increments version)
|
||||
./build.sh all
|
||||
|
||||
# Build examples (increments version)
|
||||
./build.sh examples
|
||||
|
||||
# Install to system (increments version)
|
||||
./build.sh install
|
||||
```
|
||||
|
||||
### Non-Versioned Builds
|
||||
|
||||
Some targets do not increment versions:
|
||||
|
||||
```bash
|
||||
# Clean build artifacts (no version increment)
|
||||
./build.sh clean
|
||||
|
||||
# Run tests (no version increment)
|
||||
./build.sh test
|
||||
```
|
||||
|
||||
### Using Version Information in Code
|
||||
|
||||
```c
|
||||
#include "version.h"
|
||||
|
||||
// Get version string
|
||||
printf("Version: %s\n", nostr_core_get_version());
|
||||
|
||||
// Get full version with timestamp and commit
|
||||
printf("Full: %s\n", nostr_core_get_version_full());
|
||||
|
||||
// Get detailed build information
|
||||
printf("Build: %s\n", nostr_core_get_build_info());
|
||||
|
||||
// Use version macros
|
||||
#if VERSION_MAJOR >= 1
|
||||
// Use new API
|
||||
#else
|
||||
// Use legacy API
|
||||
#endif
|
||||
```
|
||||
|
||||
### Testing Version System
|
||||
|
||||
A version test example is provided:
|
||||
|
||||
```bash
|
||||
# Build and run version test
|
||||
gcc -I. -Inostr_core examples/version_test.c -o examples/version_test ./libnostr_core.a ./secp256k1/.libs/libsecp256k1.a -lm
|
||||
./examples/version_test
|
||||
```
|
||||
|
||||
## Version History Tracking
|
||||
|
||||
### View All Versions
|
||||
```bash
|
||||
# List all version tags
|
||||
git tag --list
|
||||
|
||||
# View version history
|
||||
git log --oneline --decorate --graph
|
||||
```
|
||||
|
||||
### Current Version
|
||||
```bash
|
||||
# Check current version
|
||||
cat VERSION
|
||||
|
||||
# Or check the latest git tag
|
||||
git describe --tags --abbrev=0
|
||||
```
|
||||
|
||||
## Manual Version Management
|
||||
|
||||
### Major/Minor Version Bumps
|
||||
|
||||
For major or minor version changes, manually create the appropriate tag:
|
||||
|
||||
```bash
|
||||
# For a minor version bump (new features)
|
||||
git tag v0.3.0
|
||||
|
||||
# For a major version bump (breaking changes)
|
||||
git tag v1.0.0
|
||||
```
|
||||
|
||||
The next build will automatically increment from the new base version.
|
||||
|
||||
### Resetting Version
|
||||
|
||||
To reset or fix version numbering:
|
||||
|
||||
```bash
|
||||
# Delete incorrect tags (if needed)
|
||||
git tag -d v0.2.1
|
||||
git push origin --delete v0.2.1
|
||||
|
||||
# Create correct base version
|
||||
git tag v0.2.0
|
||||
|
||||
# Next build will create v0.2.1
|
||||
```
|
||||
|
||||
## Integration Notes
|
||||
|
||||
### Makefile Integration
|
||||
- The `version.c` file is automatically included in `LIB_SOURCES`
|
||||
- Version files are compiled and linked with the library
|
||||
- Clean targets remove generated version object files
|
||||
|
||||
### Git Integration
|
||||
- Version files (`version.h`, `version.c`) are excluded from git via `.gitignore`
|
||||
- Only version tags and the `VERSION` file are tracked
|
||||
- Build system works in any git repository with version tags
|
||||
|
||||
### Build System Integration
|
||||
- Version increment is integrated directly into `build.sh`
|
||||
- No separate scripts or external dependencies required
|
||||
- Self-contained and portable across systems
|
||||
|
||||
## Example Output
|
||||
|
||||
When building, you'll see output like:
|
||||
|
||||
```
|
||||
[INFO] Incrementing version...
|
||||
[INFO] Current version: v0.2.0
|
||||
[INFO] New version: v0.2.1
|
||||
[SUCCESS] Created new version tag: v0.2.1
|
||||
[SUCCESS] Generated version.h and version.c
|
||||
[SUCCESS] Updated VERSION file to 0.2.1
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Version Not Incrementing
|
||||
- Ensure you're in a git repository
|
||||
- Check that git tags exist with `git tag --list`
|
||||
- Verify tag format matches `v*.*.*` pattern
|
||||
|
||||
### Tag Already Exists
|
||||
If a tag already exists, the build will continue with the existing version:
|
||||
|
||||
```
|
||||
[WARNING] Tag v0.2.1 already exists - using existing version
|
||||
```
|
||||
|
||||
### Missing Git Information
|
||||
If git is not available, version files will show "unknown" for git hash and branch.
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Automatic Traceability**: Every build has a unique version
|
||||
2. **Build Metadata**: Includes timestamp, git commit, and branch information
|
||||
3. **API Integration**: Version information accessible via C API
|
||||
4. **Zero Maintenance**: No manual version file editing required
|
||||
5. **Git Integration**: Automatic git tag creation for version history
|
||||
26
README.md
26
README.md
@@ -302,20 +302,32 @@ publish_result_t* synchronous_publish_event_with_progress(const char** relay_url
|
||||
|
||||
### Relay Pools (Asynchronous)
|
||||
```c
|
||||
// Create and manage relay pool
|
||||
nostr_relay_pool_t* nostr_relay_pool_create(void);
|
||||
// Create and manage relay pool with reconnection
|
||||
nostr_pool_reconnect_config_t* config = nostr_pool_reconnect_config_default();
|
||||
nostr_relay_pool_t* nostr_relay_pool_create(nostr_pool_reconnect_config_t* config);
|
||||
int nostr_relay_pool_add_relay(nostr_relay_pool_t* pool, const char* relay_url);
|
||||
void nostr_relay_pool_destroy(nostr_relay_pool_t* pool);
|
||||
|
||||
// Subscribe to events
|
||||
// Subscribe to events (with auto-reconnection)
|
||||
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);
|
||||
void (*on_eose)(void* user_data), void* user_data, int close_on_eose);
|
||||
|
||||
// Run event loop
|
||||
// Run event loop (handles reconnection automatically)
|
||||
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);
|
||||
|
||||
// Reconnection configuration
|
||||
typedef struct {
|
||||
int enable_auto_reconnect; // Enable automatic reconnection
|
||||
int max_reconnect_attempts; // Maximum retry attempts
|
||||
int initial_reconnect_delay_ms; // Initial delay between attempts
|
||||
int max_reconnect_delay_ms; // Maximum delay cap
|
||||
int reconnect_backoff_multiplier; // Exponential backoff factor
|
||||
int ping_interval_seconds; // Health check ping interval
|
||||
int pong_timeout_seconds; // Pong response timeout
|
||||
} nostr_pool_reconnect_config_t;
|
||||
```
|
||||
|
||||
### NIP-05 Identifier Verification
|
||||
@@ -370,6 +382,9 @@ The library includes extensive tests:
|
||||
|
||||
# Individual test categories
|
||||
cd tests && make test
|
||||
|
||||
# Interactive relay pool testing
|
||||
cd tests && ./pool_test
|
||||
```
|
||||
|
||||
**Test Categories:**
|
||||
@@ -383,6 +398,7 @@ cd tests && make test
|
||||
- **Relay Communication**: `relay_pool_test`, `sync_test`
|
||||
- **HTTP/WebSocket**: `http_test`, `wss_test`
|
||||
- **Proof of Work**: `test_pow_loop`
|
||||
- **Interactive Pool Testing**: `pool_test` (menu-driven interface with reconnection testing)
|
||||
|
||||
## 🏗️ Integration
|
||||
|
||||
|
||||
1
build.sh
1
build.sh
@@ -491,6 +491,7 @@ SOURCES="$SOURCES cjson/cJSON.c"
|
||||
SOURCES="$SOURCES nostr_core/utils.c"
|
||||
SOURCES="$SOURCES nostr_core/nostr_common.c"
|
||||
SOURCES="$SOURCES nostr_core/core_relays.c"
|
||||
SOURCES="$SOURCES nostr_core/core_relay_pool.c"
|
||||
SOURCES="$SOURCES nostr_websocket/nostr_websocket_openssl.c"
|
||||
SOURCES="$SOURCES nostr_core/request_validator.c"
|
||||
|
||||
|
||||
394
dev_build.sh
394
dev_build.sh
@@ -1,394 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# NOSTR Core Library Build Script
|
||||
# Provides convenient build targets for the standalone library
|
||||
# Automatically increments patch version with each build
|
||||
|
||||
set -e # Exit on any error
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print colored output
|
||||
print_status() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Function to automatically increment version
|
||||
increment_version() {
|
||||
print_status "Incrementing version..."
|
||||
|
||||
# Check if we're in a git repository
|
||||
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
||||
print_warning "Not in a git repository - skipping version increment"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Get the highest version tag (not necessarily the most recent chronologically)
|
||||
LATEST_TAG=$(git tag -l 'v*.*.*' | sort -V | tail -n 1 || echo "v0.1.0")
|
||||
if [[ -z "$LATEST_TAG" ]]; then
|
||||
LATEST_TAG="v0.1.0"
|
||||
fi
|
||||
|
||||
# Extract version components (remove 'v' prefix if present)
|
||||
VERSION=${LATEST_TAG#v}
|
||||
|
||||
# Parse major.minor.patch
|
||||
if [[ $VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||
MAJOR=${BASH_REMATCH[1]}
|
||||
MINOR=${BASH_REMATCH[2]}
|
||||
PATCH=${BASH_REMATCH[3]}
|
||||
else
|
||||
print_error "Invalid version format in tag: $LATEST_TAG"
|
||||
print_error "Expected format: v0.1.0"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Increment patch version
|
||||
NEW_PATCH=$((PATCH + 1))
|
||||
NEW_VERSION="v${MAJOR}.${MINOR}.${NEW_PATCH}"
|
||||
|
||||
print_status "Current version: $LATEST_TAG"
|
||||
print_status "New version: $NEW_VERSION"
|
||||
|
||||
# Create new git tag
|
||||
if git tag "$NEW_VERSION" 2>/dev/null; then
|
||||
print_success "Created new version tag: $NEW_VERSION"
|
||||
else
|
||||
print_warning "Tag $NEW_VERSION already exists - using existing version"
|
||||
NEW_VERSION=$LATEST_TAG
|
||||
fi
|
||||
|
||||
# Update VERSION file for compatibility
|
||||
echo "${NEW_VERSION#v}" > VERSION
|
||||
print_success "Updated VERSION file to ${NEW_VERSION#v}"
|
||||
}
|
||||
|
||||
# Function to perform git operations after successful build
|
||||
perform_git_operations() {
|
||||
local commit_message="$1"
|
||||
|
||||
if [[ -z "$commit_message" ]]; then
|
||||
return 0 # No commit message provided, skip git operations
|
||||
fi
|
||||
|
||||
print_status "Performing git operations..."
|
||||
|
||||
# Check if we're in a git repository
|
||||
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
||||
print_warning "Not in a git repository - skipping git operations"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check if there are changes to commit
|
||||
if git diff --quiet && git diff --cached --quiet; then
|
||||
print_warning "No changes to commit"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Add all changes
|
||||
print_status "Adding changes to git..."
|
||||
if ! git add .; then
|
||||
print_error "Failed to add changes to git"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Commit changes
|
||||
print_status "Committing changes with message: '$commit_message'"
|
||||
if ! git commit -m "$commit_message"; then
|
||||
print_error "Failed to commit changes"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Push changes
|
||||
print_status "Pushing changes to remote repository..."
|
||||
if ! git push; then
|
||||
print_error "Failed to push changes to remote repository"
|
||||
print_warning "Changes have been committed locally but not pushed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
print_success "Git operations completed successfully!"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Function to show usage
|
||||
show_usage() {
|
||||
echo "NOSTR Core Library Build Script"
|
||||
echo "==============================="
|
||||
echo ""
|
||||
echo "Usage: $0 [target] [-m \"commit message\"]"
|
||||
echo ""
|
||||
echo "Available targets:"
|
||||
echo " clean - Clean all build artifacts"
|
||||
echo " lib - Build static libraries for both x64 and ARM64 (default)"
|
||||
echo " x64 - Build x64 static library only"
|
||||
echo " arm64 - Build ARM64 static library only"
|
||||
echo " all - Build both architectures and examples"
|
||||
echo " examples - Build example programs"
|
||||
echo " test - Run tests"
|
||||
echo " install - Install library to system"
|
||||
echo " uninstall - Remove library from system"
|
||||
echo " help - Show this help message"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " -m \"message\" - Git commit message (triggers automatic git add, commit, push after successful build)"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 lib -m \"Add new proof-of-work parameters\""
|
||||
echo " $0 x64 -m \"Fix OpenSSL minimal build configuration\""
|
||||
echo " $0 lib # Build without git operations"
|
||||
echo ""
|
||||
echo "Library outputs (both self-contained with secp256k1):"
|
||||
echo " libnostr_core.a - x86_64 static library"
|
||||
echo " libnostr_core_arm64.a - ARM64 static library"
|
||||
echo " examples/* - Example programs"
|
||||
echo ""
|
||||
echo "Both libraries include secp256k1 objects internally."
|
||||
echo "Users only need to link with the library + -lm."
|
||||
}
|
||||
|
||||
# Parse command line arguments
|
||||
TARGET=""
|
||||
COMMIT_MESSAGE=""
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-m)
|
||||
COMMIT_MESSAGE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-*)
|
||||
print_error "Unknown option: $1"
|
||||
show_usage
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$TARGET" ]]; then
|
||||
TARGET="$1"
|
||||
else
|
||||
print_error "Multiple targets specified: $TARGET and $1"
|
||||
show_usage
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Set default target if none specified
|
||||
TARGET=${TARGET:-lib}
|
||||
|
||||
case "$TARGET" in
|
||||
clean)
|
||||
print_status "Cleaning build artifacts..."
|
||||
make clean
|
||||
print_success "Clean completed"
|
||||
;;
|
||||
|
||||
lib|library)
|
||||
increment_version
|
||||
print_status "Building both x64 and ARM64 static libraries..."
|
||||
make clean
|
||||
make
|
||||
|
||||
# Check both libraries were built
|
||||
SUCCESS=0
|
||||
if [ -f "libnostr_core.a" ]; then
|
||||
SIZE_X64=$(stat -c%s "libnostr_core.a")
|
||||
print_success "x64 static library built successfully (${SIZE_X64} bytes)"
|
||||
SUCCESS=$((SUCCESS + 1))
|
||||
else
|
||||
print_error "Failed to build x64 static library"
|
||||
fi
|
||||
|
||||
if [ -f "libnostr_core_arm64.a" ]; then
|
||||
SIZE_ARM64=$(stat -c%s "libnostr_core_arm64.a")
|
||||
print_success "ARM64 static library built successfully (${SIZE_ARM64} bytes)"
|
||||
SUCCESS=$((SUCCESS + 1))
|
||||
else
|
||||
print_error "Failed to build ARM64 static library"
|
||||
fi
|
||||
|
||||
if [ $SUCCESS -eq 2 ]; then
|
||||
print_success "Both architectures built successfully!"
|
||||
ls -la libnostr_core*.a
|
||||
perform_git_operations "$COMMIT_MESSAGE"
|
||||
else
|
||||
print_error "Failed to build all libraries"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
|
||||
x64|x64-only)
|
||||
increment_version
|
||||
print_status "Building x64 static library only..."
|
||||
make clean
|
||||
make x64
|
||||
if [ -f "libnostr_core.a" ]; then
|
||||
SIZE=$(stat -c%s "libnostr_core.a")
|
||||
print_success "x64 static library built successfully (${SIZE} bytes)"
|
||||
ls -la libnostr_core.a
|
||||
perform_git_operations "$COMMIT_MESSAGE"
|
||||
else
|
||||
print_error "Failed to build x64 static library"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
|
||||
arm64|arm64-only)
|
||||
increment_version
|
||||
print_status "Building ARM64 static library only..."
|
||||
make clean
|
||||
make arm64
|
||||
if [ -f "libnostr_core_arm64.a" ]; then
|
||||
SIZE=$(stat -c%s "libnostr_core_arm64.a")
|
||||
print_success "ARM64 static library built successfully (${SIZE} bytes)"
|
||||
ls -la libnostr_core_arm64.a
|
||||
perform_git_operations "$COMMIT_MESSAGE"
|
||||
else
|
||||
print_error "Failed to build ARM64 static library"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
|
||||
shared)
|
||||
increment_version
|
||||
print_status "Building shared library..."
|
||||
make clean
|
||||
make libnostr_core.so
|
||||
if [ -f "libnostr_core.so" ]; then
|
||||
SIZE=$(stat -c%s "libnostr_core.so")
|
||||
print_success "Shared library built successfully (${SIZE} bytes)"
|
||||
ls -la libnostr_core.so
|
||||
perform_git_operations "$COMMIT_MESSAGE"
|
||||
else
|
||||
print_error "Failed to build shared library"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
|
||||
all)
|
||||
increment_version
|
||||
print_status "Building all libraries and examples..."
|
||||
make clean
|
||||
make all
|
||||
|
||||
# Check both libraries and examples were built
|
||||
SUCCESS=0
|
||||
if [ -f "libnostr_core.a" ]; then
|
||||
SIZE_X64=$(stat -c%s "libnostr_core.a")
|
||||
print_success "x64 static library built successfully (${SIZE_X64} bytes)"
|
||||
SUCCESS=$((SUCCESS + 1))
|
||||
else
|
||||
print_error "Failed to build x64 static library"
|
||||
fi
|
||||
|
||||
if [ -f "libnostr_core_arm64.a" ]; then
|
||||
SIZE_ARM64=$(stat -c%s "libnostr_core_arm64.a")
|
||||
print_success "ARM64 static library built successfully (${SIZE_ARM64} bytes)"
|
||||
SUCCESS=$((SUCCESS + 1))
|
||||
else
|
||||
print_error "Failed to build ARM64 static library"
|
||||
fi
|
||||
|
||||
if [ $SUCCESS -eq 2 ]; then
|
||||
print_success "All libraries and examples built successfully!"
|
||||
ls -la libnostr_core*.a
|
||||
ls -la examples/
|
||||
perform_git_operations "$COMMIT_MESSAGE"
|
||||
else
|
||||
print_error "Failed to build all components"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
|
||||
examples)
|
||||
increment_version
|
||||
print_status "Building both libraries and examples..."
|
||||
make clean
|
||||
make
|
||||
make examples
|
||||
|
||||
# Verify libraries were built
|
||||
if [ -f "libnostr_core.a" ] && [ -f "libnostr_core_arm64.a" ]; then
|
||||
print_success "Both libraries and examples built successfully"
|
||||
ls -la libnostr_core*.a
|
||||
ls -la examples/
|
||||
perform_git_operations "$COMMIT_MESSAGE"
|
||||
else
|
||||
print_error "Failed to build libraries for examples"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
|
||||
test)
|
||||
print_status "Running tests..."
|
||||
make clean
|
||||
make
|
||||
if make test-crypto 2>/dev/null; then
|
||||
print_success "All tests passed"
|
||||
else
|
||||
print_warning "Running simple test instead..."
|
||||
make test
|
||||
print_success "Basic test completed"
|
||||
fi
|
||||
;;
|
||||
|
||||
tests)
|
||||
print_status "Running tests..."
|
||||
make clean
|
||||
make
|
||||
if make test-crypto 2>/dev/null; then
|
||||
print_success "All tests passed"
|
||||
else
|
||||
print_warning "Running simple test instead..."
|
||||
make test
|
||||
print_success "Basic test completed"
|
||||
fi
|
||||
;;
|
||||
|
||||
install)
|
||||
increment_version
|
||||
print_status "Installing library to system..."
|
||||
make clean
|
||||
make all
|
||||
sudo make install
|
||||
print_success "Library installed to /usr/local"
|
||||
perform_git_operations "$COMMIT_MESSAGE"
|
||||
;;
|
||||
|
||||
uninstall)
|
||||
print_status "Uninstalling library from system..."
|
||||
sudo make uninstall
|
||||
print_success "Library uninstalled"
|
||||
;;
|
||||
|
||||
help|--help|-h)
|
||||
show_usage
|
||||
;;
|
||||
|
||||
*)
|
||||
print_error "Unknown target: $TARGET"
|
||||
echo ""
|
||||
show_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
150
increment_and_push.sh
Executable file
150
increment_and_push.sh
Executable file
@@ -0,0 +1,150 @@
|
||||
#!/bin/bash
|
||||
|
||||
# increment_and_push.sh - Version increment and git automation script
|
||||
# Usage: ./increment_and_push.sh "meaningful git comment"
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Color constants
|
||||
RED='\033[31m'
|
||||
GREEN='\033[32m'
|
||||
YELLOW='\033[33m'
|
||||
BLUE='\033[34m'
|
||||
BOLD='\033[1m'
|
||||
RESET='\033[0m'
|
||||
|
||||
# Function to print output with colors
|
||||
print_info() {
|
||||
if [ "$USE_COLORS" = true ]; then
|
||||
echo -e "${BLUE}[INFO]${RESET} $1"
|
||||
else
|
||||
echo "[INFO] $1"
|
||||
fi
|
||||
}
|
||||
|
||||
print_success() {
|
||||
if [ "$USE_COLORS" = true ]; then
|
||||
echo -e "${GREEN}${BOLD}[SUCCESS]${RESET} $1"
|
||||
else
|
||||
echo "[SUCCESS] $1"
|
||||
fi
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
if [ "$USE_COLORS" = true ]; then
|
||||
echo -e "${YELLOW}[WARNING]${RESET} $1"
|
||||
else
|
||||
echo "[WARNING] $1"
|
||||
fi
|
||||
}
|
||||
|
||||
print_error() {
|
||||
if [ "$USE_COLORS" = true ]; then
|
||||
echo -e "${RED}${BOLD}[ERROR]${RESET} $1"
|
||||
else
|
||||
echo "[ERROR] $1"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if we're in the correct directory
|
||||
CURRENT_DIR=$(basename "$(pwd)")
|
||||
if [ "$CURRENT_DIR" != "nostr_core_lib" ]; then
|
||||
print_error "Script must be run from the nostr_core_lib directory"
|
||||
echo ""
|
||||
echo "Current directory: $CURRENT_DIR"
|
||||
echo "Expected directory: nostr_core_lib"
|
||||
echo ""
|
||||
echo "Please change to the nostr_core_lib directory first."
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if git repository exists
|
||||
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
||||
print_error "Not a git repository. Please initialize git first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if we have a commit message
|
||||
if [ $# -eq 0 ]; then
|
||||
print_error "Usage: $0 \"meaningful git comment\""
|
||||
echo ""
|
||||
echo "Example: $0 \"Add enhanced subscription functionality\""
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
COMMIT_MESSAGE="$1"
|
||||
|
||||
# Check if nostr_core.h exists
|
||||
if [ ! -f "nostr_core/nostr_core.h" ]; then
|
||||
print_error "nostr_core/nostr_core.h not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_info "Starting version increment and push process..."
|
||||
|
||||
# Extract current version from nostr_core.h
|
||||
CURRENT_VERSION=$(grep '#define VERSION ' nostr_core/nostr_core.h | cut -d'"' -f2)
|
||||
if [ -z "$CURRENT_VERSION" ]; then
|
||||
print_error "Could not find VERSION define in nostr_core.h"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract version components
|
||||
VERSION_MAJOR=$(grep '#define VERSION_MAJOR ' nostr_core/nostr_core.h | awk '{print $3}')
|
||||
VERSION_MINOR=$(grep '#define VERSION_MINOR ' nostr_core/nostr_core.h | awk '{print $3}')
|
||||
VERSION_PATCH=$(grep '#define VERSION_PATCH ' nostr_core/nostr_core.h | awk '{print $3}')
|
||||
|
||||
if [ -z "$VERSION_MAJOR" ] || [ -z "$VERSION_MINOR" ] || [ -z "$VERSION_PATCH" ]; then
|
||||
print_error "Could not extract version components from nostr_core.h"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_info "Current version: $CURRENT_VERSION (Major: $VERSION_MAJOR, Minor: $VERSION_MINOR, Patch: $VERSION_PATCH)"
|
||||
|
||||
# Increment patch version
|
||||
NEW_PATCH=$((VERSION_PATCH + 1))
|
||||
NEW_VERSION="v$VERSION_MAJOR.$VERSION_MINOR.$NEW_PATCH"
|
||||
|
||||
print_info "New version will be: $NEW_VERSION"
|
||||
|
||||
# Update version in nostr_core.h
|
||||
sed -i "s/#define VERSION .*/#define VERSION \"$NEW_VERSION\"/" nostr_core/nostr_core.h
|
||||
sed -i "s/#define VERSION_PATCH .*/#define VERSION_PATCH $NEW_PATCH/" nostr_core/nostr_core.h
|
||||
|
||||
print_success "Updated version in nostr_core.h"
|
||||
|
||||
# Check if VERSION file exists and update it
|
||||
if [ -f "VERSION" ]; then
|
||||
echo "$VERSION_MAJOR.$VERSION_MINOR.$NEW_PATCH" > VERSION
|
||||
print_success "Updated VERSION file"
|
||||
fi
|
||||
|
||||
# Check git status
|
||||
if ! git diff --quiet; then
|
||||
print_info "Adding changes to git..."
|
||||
git add .
|
||||
|
||||
print_info "Committing changes..."
|
||||
git commit -m "$COMMIT_MESSAGE"
|
||||
|
||||
print_success "Changes committed"
|
||||
else
|
||||
print_warning "No changes to commit"
|
||||
fi
|
||||
|
||||
# Create and push git tag
|
||||
print_info "Creating git tag: $NEW_VERSION"
|
||||
git tag "$NEW_VERSION"
|
||||
|
||||
print_info "Pushing commits and tags..."
|
||||
git push origin main
|
||||
git push origin "$NEW_VERSION"
|
||||
|
||||
print_success "Version $NEW_VERSION successfully released!"
|
||||
print_info "Git commit: $COMMIT_MESSAGE"
|
||||
print_info "Tag: $NEW_VERSION"
|
||||
|
||||
echo ""
|
||||
echo "🎉 Release complete! Version $NEW_VERSION is now live."
|
||||
@@ -65,22 +65,26 @@ typedef struct relay_connection {
|
||||
char* url;
|
||||
nostr_ws_client_t* ws_client;
|
||||
nostr_pool_relay_status_t status;
|
||||
|
||||
|
||||
// Connection management
|
||||
time_t last_ping;
|
||||
time_t connect_time;
|
||||
nostr_relay_pool_t* pool; // Back reference to pool for config access
|
||||
|
||||
// Reconnection management
|
||||
int reconnect_attempts;
|
||||
|
||||
// Ping management for latency measurement
|
||||
time_t last_reconnect_attempt;
|
||||
time_t next_reconnect_time;
|
||||
|
||||
// Connection health monitoring (ping/pong)
|
||||
time_t last_ping_sent;
|
||||
time_t next_ping_time; // last_ping_sent + NOSTR_POOL_PING_INTERVAL
|
||||
time_t last_pong_received;
|
||||
int ping_pending;
|
||||
double pending_ping_start_ms; // High-resolution timestamp for ping measurement
|
||||
int ping_pending; // Flag to track if ping response is expected
|
||||
|
||||
|
||||
// Multi-subscription latency tracking (REQ->first EVENT/EOSE)
|
||||
subscription_timing_t pending_subscriptions[NOSTR_POOL_MAX_PENDING_SUBSCRIPTIONS];
|
||||
int pending_subscription_count;
|
||||
|
||||
|
||||
// Statistics
|
||||
nostr_relay_stats_t stats;
|
||||
} relay_connection_t;
|
||||
@@ -88,34 +92,53 @@ typedef struct relay_connection {
|
||||
struct nostr_pool_subscription {
|
||||
char subscription_id[NOSTR_POOL_SUBSCRIPTION_ID_SIZE];
|
||||
cJSON* filter;
|
||||
|
||||
|
||||
// Relay-specific subscription tracking
|
||||
char** relay_urls;
|
||||
int relay_count;
|
||||
int* eose_received; // Track EOSE from each relay
|
||||
|
||||
|
||||
// Callbacks
|
||||
void (*on_event)(cJSON* event, const char* relay_url, void* user_data);
|
||||
void (*on_eose)(void* user_data);
|
||||
void (*on_eose)(cJSON** events, int event_count, void* user_data);
|
||||
void* user_data;
|
||||
|
||||
|
||||
int closed;
|
||||
int close_on_eose; // Auto-close subscription when all relays send EOSE
|
||||
nostr_relay_pool_t* pool; // Back reference to pool
|
||||
|
||||
// New subscription control parameters
|
||||
int enable_deduplication; // Per-subscription deduplication control
|
||||
nostr_pool_eose_result_mode_t result_mode; // EOSE result selection mode
|
||||
int relay_timeout_seconds; // Timeout for individual relay operations
|
||||
int eose_timeout_seconds; // Timeout for waiting for EOSE completion
|
||||
time_t subscription_start_time; // When subscription was created
|
||||
|
||||
// Event collection for EOSE result modes
|
||||
cJSON** collected_events;
|
||||
int collected_event_count;
|
||||
int collected_events_capacity;
|
||||
|
||||
// Per-relay timeout tracking
|
||||
time_t* relay_last_activity; // Last activity time per relay
|
||||
};
|
||||
|
||||
struct nostr_relay_pool {
|
||||
relay_connection_t* relays[NOSTR_POOL_MAX_RELAYS];
|
||||
int relay_count;
|
||||
|
||||
|
||||
// Reconnection configuration
|
||||
nostr_pool_reconnect_config_t reconnect_config;
|
||||
|
||||
// Event deduplication - simple hash table with linear probing
|
||||
char seen_event_ids[NOSTR_POOL_MAX_SEEN_EVENTS][65]; // 64 hex chars + null terminator
|
||||
int seen_count;
|
||||
int seen_next_index;
|
||||
|
||||
|
||||
// Active subscriptions
|
||||
nostr_pool_subscription_t* subscriptions[NOSTR_POOL_MAX_SUBSCRIPTIONS];
|
||||
int subscription_count;
|
||||
|
||||
|
||||
// Pool-wide settings
|
||||
int default_timeout_ms;
|
||||
};
|
||||
@@ -163,33 +186,211 @@ static int add_subscription_timing(relay_connection_t* relay, const char* subscr
|
||||
// Helper function to find and remove subscription timing
|
||||
static double remove_subscription_timing(relay_connection_t* relay, const char* subscription_id) {
|
||||
if (!relay || !subscription_id) return -1.0;
|
||||
|
||||
|
||||
for (int i = 0; i < relay->pending_subscription_count; i++) {
|
||||
if (relay->pending_subscriptions[i].active &&
|
||||
if (relay->pending_subscriptions[i].active &&
|
||||
strcmp(relay->pending_subscriptions[i].subscription_id, subscription_id) == 0) {
|
||||
|
||||
|
||||
// Calculate latency
|
||||
double current_time_ms = get_current_time_ms();
|
||||
double latency_ms = current_time_ms - relay->pending_subscriptions[i].start_time_ms;
|
||||
|
||||
|
||||
// Mark as inactive and remove by shifting remaining entries
|
||||
relay->pending_subscriptions[i].active = 0;
|
||||
for (int j = i; j < relay->pending_subscription_count - 1; j++) {
|
||||
relay->pending_subscriptions[j] = relay->pending_subscriptions[j + 1];
|
||||
}
|
||||
relay->pending_subscription_count--;
|
||||
|
||||
|
||||
return latency_ms;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return -1.0; // Not found
|
||||
}
|
||||
|
||||
// Helper function to check if event ID has been seen
|
||||
// Helper function to ensure relay connection
|
||||
static int ensure_relay_connection(relay_connection_t* relay) {
|
||||
if (!relay) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (relay->ws_client && nostr_ws_get_state(relay->ws_client) == NOSTR_WS_CONNECTED) {
|
||||
relay->status = NOSTR_POOL_RELAY_CONNECTED;
|
||||
return 0; // Already connected
|
||||
}
|
||||
|
||||
// Close existing connection if any
|
||||
if (relay->ws_client) {
|
||||
nostr_ws_close(relay->ws_client);
|
||||
relay->ws_client = NULL;
|
||||
}
|
||||
|
||||
// Attempt connection
|
||||
relay->status = NOSTR_POOL_RELAY_CONNECTING;
|
||||
relay->stats.connection_attempts++;
|
||||
|
||||
relay->ws_client = nostr_ws_connect(relay->url);
|
||||
|
||||
if (!relay->ws_client) {
|
||||
relay->status = NOSTR_POOL_RELAY_ERROR;
|
||||
relay->reconnect_attempts++;
|
||||
relay->stats.connection_failures++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
nostr_ws_state_t state = nostr_ws_get_state(relay->ws_client);
|
||||
|
||||
if (state == NOSTR_WS_CONNECTED) {
|
||||
relay->status = NOSTR_POOL_RELAY_CONNECTED;
|
||||
relay->connect_time = time(NULL);
|
||||
relay->reconnect_attempts = 0;
|
||||
|
||||
// Initialize ping/pong monitoring on new connection
|
||||
relay->last_ping_sent = time(NULL);
|
||||
relay->last_pong_received = time(NULL);
|
||||
relay->ping_pending = 0;
|
||||
|
||||
return 0;
|
||||
} else {
|
||||
relay->status = NOSTR_POOL_RELAY_ERROR;
|
||||
relay->reconnect_attempts++;
|
||||
relay->stats.connection_failures++;
|
||||
|
||||
// Close the failed connection
|
||||
nostr_ws_close(relay->ws_client);
|
||||
relay->ws_client = NULL;
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Reconnection helper functions
|
||||
static int should_attempt_reconnect(relay_connection_t* relay) {
|
||||
if (!relay->pool->reconnect_config.enable_auto_reconnect) return 0;
|
||||
if (relay->status == NOSTR_POOL_RELAY_CONNECTED) return 0;
|
||||
if (relay->reconnect_attempts >= relay->pool->reconnect_config.max_reconnect_attempts) return 0;
|
||||
|
||||
time_t now = time(NULL);
|
||||
return (now >= relay->next_reconnect_time);
|
||||
}
|
||||
|
||||
static int calculate_reconnect_delay(relay_connection_t* relay) {
|
||||
int delay = relay->pool->reconnect_config.initial_reconnect_delay_ms;
|
||||
|
||||
// Apply exponential backoff
|
||||
for (int i = 1; i < relay->reconnect_attempts; i++) {
|
||||
delay *= relay->pool->reconnect_config.reconnect_backoff_multiplier;
|
||||
if (delay > relay->pool->reconnect_config.max_reconnect_delay_ms) {
|
||||
delay = relay->pool->reconnect_config.max_reconnect_delay_ms;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return delay;
|
||||
}
|
||||
|
||||
static void restore_subscriptions_on_reconnect(relay_connection_t* relay) {
|
||||
// Find subscriptions that should be active on this relay
|
||||
for (int i = 0; i < relay->pool->subscription_count; i++) {
|
||||
nostr_pool_subscription_t* sub = relay->pool->subscriptions[i];
|
||||
if (!sub->closed) {
|
||||
// Check if this subscription should be on this relay
|
||||
for (int j = 0; j < sub->relay_count; j++) {
|
||||
if (strcmp(sub->relay_urls[j], relay->url) == 0) {
|
||||
// Re-send the subscription
|
||||
if (nostr_relay_send_req(relay->ws_client, sub->subscription_id, sub->filter) >= 0) {
|
||||
// Add timing for latency measurement
|
||||
add_subscription_timing(relay, sub->subscription_id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void attempt_reconnect(relay_connection_t* relay) {
|
||||
relay->status = NOSTR_POOL_RELAY_CONNECTING;
|
||||
relay->last_reconnect_attempt = time(NULL);
|
||||
relay->reconnect_attempts++;
|
||||
|
||||
if (ensure_relay_connection(relay) == 0) {
|
||||
// Success! Reset reconnection state
|
||||
relay->reconnect_attempts = 0;
|
||||
relay->next_reconnect_time = 0;
|
||||
|
||||
// Restore subscriptions on reconnect
|
||||
restore_subscriptions_on_reconnect(relay);
|
||||
} else {
|
||||
// Failed - schedule next attempt with backoff
|
||||
int delay_ms = calculate_reconnect_delay(relay);
|
||||
relay->next_reconnect_time = time(NULL) + (delay_ms / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Connection health monitoring (ping/pong)
|
||||
static void check_connection_health(relay_connection_t* relay) {
|
||||
time_t now = time(NULL);
|
||||
|
||||
// Send ping if interval elapsed and ping is enabled
|
||||
if (relay->pool->reconnect_config.ping_interval_seconds > 0 &&
|
||||
now - relay->last_ping_sent >= relay->pool->reconnect_config.ping_interval_seconds &&
|
||||
!relay->ping_pending) {
|
||||
|
||||
if (nostr_ws_ping(relay->ws_client) == 0) {
|
||||
relay->last_ping_sent = now;
|
||||
relay->ping_pending = 1;
|
||||
// Store high-resolution start time for latency measurement
|
||||
relay->pending_ping_start_ms = get_current_time_ms();
|
||||
}
|
||||
}
|
||||
|
||||
// Check for pong timeout
|
||||
if (relay->ping_pending &&
|
||||
now - relay->last_ping_sent > relay->pool->reconnect_config.pong_timeout_seconds) {
|
||||
|
||||
// No pong received - connection is dead
|
||||
relay->status = NOSTR_POOL_RELAY_DISCONNECTED;
|
||||
relay->ping_pending = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_pong_response(relay_connection_t* relay) {
|
||||
relay->last_pong_received = time(NULL);
|
||||
|
||||
if (relay->ping_pending) {
|
||||
// Calculate ping latency
|
||||
double current_time_ms = get_current_time_ms();
|
||||
double ping_latency = current_time_ms - relay->pending_ping_start_ms;
|
||||
|
||||
// Update ping statistics
|
||||
if (relay->stats.ping_samples == 0) {
|
||||
relay->stats.ping_latency_avg = ping_latency;
|
||||
relay->stats.ping_latency_min = ping_latency;
|
||||
relay->stats.ping_latency_max = ping_latency;
|
||||
} else {
|
||||
relay->stats.ping_latency_avg =
|
||||
(relay->stats.ping_latency_avg * relay->stats.ping_samples + ping_latency) /
|
||||
(relay->stats.ping_samples + 1);
|
||||
|
||||
if (ping_latency < relay->stats.ping_latency_min) {
|
||||
relay->stats.ping_latency_min = ping_latency;
|
||||
}
|
||||
if (ping_latency > relay->stats.ping_latency_max) {
|
||||
relay->stats.ping_latency_max = ping_latency;
|
||||
}
|
||||
}
|
||||
|
||||
relay->stats.ping_latency_current = ping_latency;
|
||||
relay->stats.ping_samples++;
|
||||
relay->ping_pending = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int is_event_seen(nostr_relay_pool_t* pool, const char* event_id) {
|
||||
if (!pool || !event_id) return 0;
|
||||
|
||||
|
||||
for (int i = 0; i < pool->seen_count; i++) {
|
||||
if (strcmp(pool->seen_event_ids[i], event_id) == 0) {
|
||||
return 1;
|
||||
@@ -201,28 +402,49 @@ static int is_event_seen(nostr_relay_pool_t* pool, const char* event_id) {
|
||||
// Helper function to mark event as seen
|
||||
static void mark_event_seen(nostr_relay_pool_t* pool, const char* event_id) {
|
||||
if (!pool || !event_id) return;
|
||||
|
||||
|
||||
// Don't add duplicates
|
||||
if (is_event_seen(pool, event_id)) return;
|
||||
|
||||
|
||||
// Use circular buffer for seen events
|
||||
strncpy(pool->seen_event_ids[pool->seen_next_index], event_id, 64);
|
||||
pool->seen_event_ids[pool->seen_next_index][64] = '\0';
|
||||
|
||||
|
||||
pool->seen_next_index = (pool->seen_next_index + 1) % NOSTR_POOL_MAX_SEEN_EVENTS;
|
||||
if (pool->seen_count < NOSTR_POOL_MAX_SEEN_EVENTS) {
|
||||
pool->seen_count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Default configuration helper
|
||||
nostr_pool_reconnect_config_t* nostr_pool_reconnect_config_default(void) {
|
||||
static nostr_pool_reconnect_config_t config = {
|
||||
.enable_auto_reconnect = 1,
|
||||
.max_reconnect_attempts = 10,
|
||||
.initial_reconnect_delay_ms = 1000,
|
||||
.max_reconnect_delay_ms = 30000,
|
||||
.reconnect_backoff_multiplier = 2,
|
||||
.ping_interval_seconds = 30,
|
||||
.pong_timeout_seconds = 10
|
||||
};
|
||||
return &config;
|
||||
}
|
||||
|
||||
// Pool management functions
|
||||
nostr_relay_pool_t* nostr_relay_pool_create(void) {
|
||||
nostr_relay_pool_t* nostr_relay_pool_create(nostr_pool_reconnect_config_t* config) {
|
||||
if (!config) {
|
||||
config = nostr_pool_reconnect_config_default();
|
||||
}
|
||||
|
||||
nostr_relay_pool_t* pool = calloc(1, sizeof(nostr_relay_pool_t));
|
||||
if (!pool) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// Copy configuration
|
||||
pool->reconnect_config = *config;
|
||||
pool->default_timeout_ms = NOSTR_POOL_DEFAULT_TIMEOUT;
|
||||
|
||||
return pool;
|
||||
}
|
||||
|
||||
@@ -250,15 +472,19 @@ int nostr_relay_pool_add_relay(nostr_relay_pool_t* pool, const char* relay_url)
|
||||
|
||||
relay->status = NOSTR_POOL_RELAY_DISCONNECTED;
|
||||
relay->ws_client = NULL;
|
||||
relay->last_ping = 0;
|
||||
relay->connect_time = 0;
|
||||
relay->pool = pool; // Set back reference
|
||||
|
||||
// Initialize reconnection state
|
||||
relay->reconnect_attempts = 0;
|
||||
|
||||
// Initialize ping management
|
||||
relay->last_reconnect_attempt = 0;
|
||||
relay->next_reconnect_time = 0;
|
||||
|
||||
// Initialize ping/pong monitoring
|
||||
relay->last_ping_sent = 0;
|
||||
relay->next_ping_time = 0;
|
||||
relay->pending_ping_start_ms = 0.0;
|
||||
relay->last_pong_received = 0;
|
||||
relay->ping_pending = 0;
|
||||
relay->pending_ping_start_ms = 0.0;
|
||||
|
||||
// Initialize statistics
|
||||
memset(&relay->stats, 0, sizeof(relay->stats));
|
||||
@@ -325,85 +551,21 @@ void nostr_relay_pool_destroy(nostr_relay_pool_t* pool) {
|
||||
free(pool);
|
||||
}
|
||||
|
||||
// Helper function to ensure relay connection
|
||||
static int ensure_relay_connection(relay_connection_t* relay) {
|
||||
if (!relay) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
if (relay->ws_client && nostr_ws_get_state(relay->ws_client) == NOSTR_WS_CONNECTED) {
|
||||
relay->status = NOSTR_POOL_RELAY_CONNECTED;
|
||||
return 0; // Already connected
|
||||
}
|
||||
|
||||
// Close existing connection if any
|
||||
if (relay->ws_client) {
|
||||
nostr_ws_close(relay->ws_client);
|
||||
relay->ws_client = NULL;
|
||||
}
|
||||
|
||||
// Attempt connection
|
||||
|
||||
relay->status = NOSTR_POOL_RELAY_CONNECTING;
|
||||
relay->stats.connection_attempts++;
|
||||
|
||||
relay->ws_client = nostr_ws_connect(relay->url);
|
||||
|
||||
if (!relay->ws_client) {
|
||||
relay->status = NOSTR_POOL_RELAY_ERROR;
|
||||
relay->reconnect_attempts++;
|
||||
relay->stats.connection_failures++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
nostr_ws_state_t state = nostr_ws_get_state(relay->ws_client);
|
||||
|
||||
|
||||
if (state == NOSTR_WS_CONNECTED) {
|
||||
|
||||
relay->status = NOSTR_POOL_RELAY_CONNECTED;
|
||||
relay->connect_time = time(NULL);
|
||||
relay->reconnect_attempts = 0;
|
||||
|
||||
// PING FUNCTIONALITY DISABLED - Initial ping on connection establishment
|
||||
/* COMMENTED OUT - PING FUNCTIONALITY DISABLED
|
||||
// Trigger immediate ping on new connection
|
||||
time_t current_time = time(NULL);
|
||||
relay->pending_ping_start_ms = get_current_time_ms();
|
||||
relay->ping_pending = 1;
|
||||
relay->last_ping_sent = current_time;
|
||||
relay->next_ping_time = current_time + NOSTR_POOL_PING_INTERVAL;
|
||||
|
||||
if (nostr_ws_send_ping(relay->ws_client, "ping", 4) < 0) {
|
||||
relay->ping_pending = 0;
|
||||
}
|
||||
*/
|
||||
|
||||
return 0;
|
||||
} else {
|
||||
|
||||
relay->status = NOSTR_POOL_RELAY_ERROR;
|
||||
relay->reconnect_attempts++;
|
||||
relay->stats.connection_failures++;
|
||||
|
||||
// Close the failed connection
|
||||
nostr_ws_close(relay->ws_client);
|
||||
relay->ws_client = NULL;
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Subscription management
|
||||
nostr_pool_subscription_t* nostr_relay_pool_subscribe(
|
||||
nostr_relay_pool_t* pool,
|
||||
const char** relay_urls,
|
||||
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) {
|
||||
void (*on_eose)(cJSON** events, int event_count, void* user_data),
|
||||
void* user_data,
|
||||
int close_on_eose,
|
||||
int enable_deduplication,
|
||||
nostr_pool_eose_result_mode_t result_mode,
|
||||
int relay_timeout_seconds,
|
||||
int eose_timeout_seconds) {
|
||||
|
||||
if (!pool || !relay_urls || relay_count <= 0 || !filter ||
|
||||
pool->subscription_count >= NOSTR_POOL_MAX_SUBSCRIPTIONS) {
|
||||
@@ -458,7 +620,57 @@ nostr_pool_subscription_t* nostr_relay_pool_subscribe(
|
||||
sub->on_eose = on_eose;
|
||||
sub->user_data = user_data;
|
||||
sub->closed = 0;
|
||||
sub->close_on_eose = close_on_eose;
|
||||
sub->pool = pool;
|
||||
|
||||
// Set new subscription control parameters
|
||||
sub->enable_deduplication = enable_deduplication;
|
||||
sub->result_mode = result_mode;
|
||||
sub->relay_timeout_seconds = relay_timeout_seconds;
|
||||
sub->eose_timeout_seconds = eose_timeout_seconds;
|
||||
sub->subscription_start_time = time(NULL);
|
||||
|
||||
// Initialize event collection arrays (only for EOSE result modes)
|
||||
if (result_mode != NOSTR_POOL_EOSE_FIRST) {
|
||||
sub->collected_events_capacity = 10; // Initial capacity
|
||||
sub->collected_events = calloc(sub->collected_events_capacity, sizeof(cJSON*));
|
||||
if (!sub->collected_events) {
|
||||
// Cleanup on failure
|
||||
cJSON_Delete(sub->filter);
|
||||
for (int j = 0; j < relay_count; j++) {
|
||||
free(sub->relay_urls[j]);
|
||||
}
|
||||
free(sub->relay_urls);
|
||||
free(sub->eose_received);
|
||||
free(sub);
|
||||
return NULL;
|
||||
}
|
||||
sub->collected_event_count = 0;
|
||||
} else {
|
||||
sub->collected_events = NULL;
|
||||
sub->collected_event_count = 0;
|
||||
sub->collected_events_capacity = 0;
|
||||
}
|
||||
|
||||
// Initialize per-relay activity tracking
|
||||
sub->relay_last_activity = calloc(relay_count, sizeof(time_t));
|
||||
if (!sub->relay_last_activity) {
|
||||
// Cleanup on failure
|
||||
if (sub->collected_events) free(sub->collected_events);
|
||||
cJSON_Delete(sub->filter);
|
||||
for (int j = 0; j < relay_count; j++) {
|
||||
free(sub->relay_urls[j]);
|
||||
}
|
||||
free(sub->relay_urls);
|
||||
free(sub->eose_received);
|
||||
free(sub);
|
||||
return NULL;
|
||||
}
|
||||
// Initialize all relay activity times to current time
|
||||
time_t now = time(NULL);
|
||||
for (int i = 0; i < relay_count; i++) {
|
||||
sub->relay_last_activity[i] = now;
|
||||
}
|
||||
|
||||
// Add to pool
|
||||
pool->subscriptions[pool->subscription_count++] = sub;
|
||||
@@ -556,43 +768,56 @@ static void process_relay_message(nostr_relay_pool_t* pool, relay_connection_t*
|
||||
if (event_id_json && cJSON_IsString(event_id_json)) {
|
||||
const char* event_id = cJSON_GetStringValue(event_id_json);
|
||||
|
||||
// Check for duplicate
|
||||
if (!is_event_seen(pool, event_id)) {
|
||||
mark_event_seen(pool, event_id);
|
||||
relay->stats.events_received++;
|
||||
|
||||
// Measure query latency (first event response)
|
||||
double latency_ms = remove_subscription_timing(relay, subscription_id);
|
||||
if (latency_ms > 0.0) {
|
||||
// Update query latency statistics
|
||||
if (relay->stats.query_samples == 0) {
|
||||
relay->stats.query_latency_avg = latency_ms;
|
||||
relay->stats.query_latency_min = latency_ms;
|
||||
relay->stats.query_latency_max = latency_ms;
|
||||
} else {
|
||||
relay->stats.query_latency_avg =
|
||||
(relay->stats.query_latency_avg * relay->stats.query_samples + latency_ms) /
|
||||
(relay->stats.query_samples + 1);
|
||||
|
||||
if (latency_ms < relay->stats.query_latency_min) {
|
||||
relay->stats.query_latency_min = latency_ms;
|
||||
}
|
||||
if (latency_ms > relay->stats.query_latency_max) {
|
||||
relay->stats.query_latency_max = latency_ms;
|
||||
}
|
||||
}
|
||||
relay->stats.query_samples++;
|
||||
// Find subscription first
|
||||
nostr_pool_subscription_t* sub = NULL;
|
||||
for (int i = 0; i < pool->subscription_count; i++) {
|
||||
if (pool->subscriptions[i] && !pool->subscriptions[i]->closed &&
|
||||
strcmp(pool->subscriptions[i]->subscription_id, subscription_id) == 0) {
|
||||
sub = pool->subscriptions[i];
|
||||
break;
|
||||
}
|
||||
|
||||
// Find subscription and call callback
|
||||
for (int i = 0; i < pool->subscription_count; i++) {
|
||||
nostr_pool_subscription_t* sub = pool->subscriptions[i];
|
||||
if (sub && !sub->closed &&
|
||||
strcmp(sub->subscription_id, subscription_id) == 0) {
|
||||
if (sub->on_event) {
|
||||
sub->on_event(event, relay->url, sub->user_data);
|
||||
}
|
||||
|
||||
if (sub) {
|
||||
// Check for duplicate (per-subscription deduplication)
|
||||
int is_duplicate = 0;
|
||||
if (sub->enable_deduplication) {
|
||||
if (is_event_seen(pool, event_id)) {
|
||||
is_duplicate = 1;
|
||||
} else {
|
||||
mark_event_seen(pool, event_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_duplicate) {
|
||||
relay->stats.events_received++;
|
||||
|
||||
// Measure query latency (first event response)
|
||||
double latency_ms = remove_subscription_timing(relay, subscription_id);
|
||||
if (latency_ms > 0.0) {
|
||||
// Update query latency statistics
|
||||
if (relay->stats.query_samples == 0) {
|
||||
relay->stats.query_latency_avg = latency_ms;
|
||||
relay->stats.query_latency_min = latency_ms;
|
||||
relay->stats.query_latency_max = latency_ms;
|
||||
} else {
|
||||
relay->stats.query_latency_avg =
|
||||
(relay->stats.query_latency_avg * relay->stats.query_samples + latency_ms) /
|
||||
(relay->stats.query_samples + 1);
|
||||
|
||||
if (latency_ms < relay->stats.query_latency_min) {
|
||||
relay->stats.query_latency_min = latency_ms;
|
||||
}
|
||||
if (latency_ms > relay->stats.query_latency_max) {
|
||||
relay->stats.query_latency_max = latency_ms;
|
||||
}
|
||||
}
|
||||
break;
|
||||
relay->stats.query_samples++;
|
||||
}
|
||||
|
||||
// Call event callback
|
||||
if (sub->on_event) {
|
||||
sub->on_event(event, relay->url, sub->user_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -630,8 +855,22 @@ static void process_relay_message(nostr_relay_pool_t* pool, relay_connection_t*
|
||||
}
|
||||
}
|
||||
|
||||
if (all_eose && sub->on_eose) {
|
||||
sub->on_eose(sub->user_data);
|
||||
if (all_eose) {
|
||||
if (sub->on_eose) {
|
||||
// Pass collected events based on result mode
|
||||
if (sub->result_mode == NOSTR_POOL_EOSE_FIRST) {
|
||||
// FIRST mode: no events collected, pass NULL/0
|
||||
sub->on_eose(NULL, 0, sub->user_data);
|
||||
} else {
|
||||
// FULL_SET or MOST_RECENT: pass collected events
|
||||
sub->on_eose(sub->collected_events, sub->collected_event_count, sub->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-close subscription if close_on_eose is enabled
|
||||
if (sub->close_on_eose && !sub->closed) {
|
||||
nostr_pool_subscription_close(sub);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -652,39 +891,8 @@ static void process_relay_message(nostr_relay_pool_t* pool, relay_connection_t*
|
||||
}
|
||||
}
|
||||
} else if (strcmp(msg_type, "PONG") == 0) {
|
||||
// PING FUNCTIONALITY DISABLED - Handle PONG response
|
||||
/* COMMENTED OUT - PING FUNCTIONALITY DISABLED
|
||||
if (relay->ping_pending) {
|
||||
double current_time_ms = get_current_time_ms();
|
||||
double ping_latency = current_time_ms - relay->pending_ping_start_ms;
|
||||
|
||||
// Update ping statistics
|
||||
if (relay->stats.ping_samples == 0) {
|
||||
relay->stats.ping_latency_avg = ping_latency;
|
||||
relay->stats.ping_latency_min = ping_latency;
|
||||
relay->stats.ping_latency_max = ping_latency;
|
||||
} else {
|
||||
relay->stats.ping_latency_avg =
|
||||
(relay->stats.ping_latency_avg * relay->stats.ping_samples + ping_latency) /
|
||||
(relay->stats.ping_samples + 1);
|
||||
|
||||
if (ping_latency < relay->stats.ping_latency_min) {
|
||||
relay->stats.ping_latency_min = ping_latency;
|
||||
}
|
||||
if (ping_latency > relay->stats.ping_latency_max) {
|
||||
relay->stats.ping_latency_max = ping_latency;
|
||||
}
|
||||
}
|
||||
|
||||
relay->stats.ping_latency_current = ping_latency;
|
||||
relay->stats.ping_samples++;
|
||||
relay->ping_pending = 0;
|
||||
|
||||
#ifdef NOSTR_DEBUG_ENABLED
|
||||
printf("🏓 DEBUG: PONG from %s - latency: %.2f ms\n", relay->url, ping_latency);
|
||||
#endif
|
||||
}
|
||||
*/
|
||||
// Handle PONG response for connection health monitoring
|
||||
handle_pong_response(relay);
|
||||
}
|
||||
|
||||
if (msg_type) free(msg_type);
|
||||
@@ -757,11 +965,17 @@ cJSON** nostr_relay_pool_query_sync(
|
||||
int len = nostr_ws_receive(relay->ws_client, buffer, sizeof(buffer) - 1, 100);
|
||||
if (len > 0) {
|
||||
buffer[len] = '\0';
|
||||
|
||||
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) {
|
||||
|
||||
// Check if this is a pong message (WebSocket library prefixes pong messages)
|
||||
if (strncmp(buffer, "__PONG__", 8) == 0) {
|
||||
// Handle pong response for connection health monitoring
|
||||
handle_pong_response(relay);
|
||||
} else {
|
||||
// Process as regular NOSTR 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) {
|
||||
// Handle EVENT message
|
||||
if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 3) {
|
||||
cJSON* sub_id_json = cJSON_GetArrayItem(parsed, 1);
|
||||
@@ -806,6 +1020,7 @@ cJSON** nostr_relay_pool_query_sync(
|
||||
}
|
||||
if (msg_type) free(msg_type);
|
||||
if (parsed) cJSON_Delete(parsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1059,76 +1274,78 @@ double nostr_relay_pool_get_relay_query_latency(
|
||||
}
|
||||
|
||||
int nostr_relay_pool_ping_relay(
|
||||
nostr_relay_pool_t* pool,
|
||||
nostr_relay_pool_t* pool,
|
||||
const char* relay_url) {
|
||||
|
||||
|
||||
// PING FUNCTIONALITY DISABLED
|
||||
/* COMMENTED OUT - PING FUNCTIONALITY DISABLED
|
||||
if (!pool || !relay_url) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
|
||||
relay_connection_t* relay = find_relay_by_url(pool, relay_url);
|
||||
if (!relay || !relay->ws_client) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
|
||||
if (ensure_relay_connection(relay) != 0) {
|
||||
return NOSTR_ERROR_NETWORK_FAILED;
|
||||
}
|
||||
|
||||
|
||||
time_t current_time = time(NULL);
|
||||
relay->pending_ping_start_ms = get_current_time_ms();
|
||||
relay->ping_pending = 1;
|
||||
relay->last_ping_sent = current_time;
|
||||
relay->next_ping_time = current_time + NOSTR_POOL_PING_INTERVAL;
|
||||
|
||||
|
||||
if (nostr_ws_send_ping(relay->ws_client, "ping", 4) < 0) {
|
||||
relay->ping_pending = 0;
|
||||
return NOSTR_ERROR_NETWORK_FAILED;
|
||||
}
|
||||
|
||||
|
||||
return NOSTR_SUCCESS;
|
||||
*/
|
||||
|
||||
|
||||
(void)pool; // Suppress unused parameter warning
|
||||
(void)relay_url; // Suppress unused parameter warning
|
||||
return NOSTR_ERROR_INVALID_INPUT; // Function disabled
|
||||
}
|
||||
|
||||
int nostr_relay_pool_ping_relay_sync(
|
||||
nostr_relay_pool_t* pool,
|
||||
const char* relay_url,
|
||||
nostr_relay_pool_t* pool,
|
||||
const char* relay_url,
|
||||
int timeout_seconds) {
|
||||
|
||||
|
||||
// PING FUNCTIONALITY DISABLED
|
||||
/* COMMENTED OUT - PING FUNCTIONALITY DISABLED
|
||||
if (!pool || !relay_url) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
|
||||
relay_connection_t* relay = find_relay_by_url(pool, relay_url);
|
||||
if (!relay || !relay->ws_client) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
|
||||
if (ensure_relay_connection(relay) != 0) {
|
||||
return NOSTR_ERROR_NETWORK_FAILED;
|
||||
}
|
||||
|
||||
|
||||
if (timeout_seconds <= 0) {
|
||||
timeout_seconds = 5;
|
||||
}
|
||||
|
||||
|
||||
time_t current_time = time(NULL);
|
||||
relay->pending_ping_start_ms = get_current_time_ms();
|
||||
relay->ping_pending = 1;
|
||||
relay->last_ping_sent = current_time;
|
||||
relay->next_ping_time = current_time + NOSTR_POOL_PING_INTERVAL;
|
||||
|
||||
|
||||
if (nostr_ws_send_ping(relay->ws_client, "ping", 4) < 0) {
|
||||
relay->ping_pending = 0;
|
||||
return NOSTR_ERROR_NETWORK_FAILED;
|
||||
}
|
||||
|
||||
|
||||
// Wait for PONG response
|
||||
time_t wait_start = time(NULL);
|
||||
while (time(NULL) - wait_start < timeout_seconds && relay->ping_pending) {
|
||||
@@ -1136,7 +1353,7 @@ int nostr_relay_pool_ping_relay_sync(
|
||||
int len = nostr_ws_receive(relay->ws_client, buffer, sizeof(buffer) - 1, 1000);
|
||||
if (len > 0) {
|
||||
buffer[len] = '\0';
|
||||
|
||||
|
||||
char* msg_type = NULL;
|
||||
cJSON* parsed = NULL;
|
||||
if (nostr_parse_relay_message(buffer, &msg_type, &parsed) == 0) {
|
||||
@@ -1146,17 +1363,17 @@ int nostr_relay_pool_ping_relay_sync(
|
||||
if (relay->ping_pending) {
|
||||
double current_time_ms = get_current_time_ms();
|
||||
double ping_latency = current_time_ms - relay->pending_ping_start_ms;
|
||||
|
||||
|
||||
// Update ping statistics
|
||||
if (relay->stats.ping_samples == 0) {
|
||||
relay->stats.ping_latency_avg = ping_latency;
|
||||
relay->stats.ping_latency_min = ping_latency;
|
||||
relay->stats.ping_latency_max = ping_latency;
|
||||
} else {
|
||||
relay->stats.ping_latency_avg =
|
||||
(relay->stats.ping_latency_avg * relay->stats.ping_samples + ping_latency) /
|
||||
relay->stats.ping_latency_avg =
|
||||
(relay->stats.ping_latency_avg * relay->stats.ping_samples + ping_latency) /
|
||||
(relay->stats.ping_samples + 1);
|
||||
|
||||
|
||||
if (ping_latency < relay->stats.ping_latency_min) {
|
||||
relay->stats.ping_latency_min = ping_latency;
|
||||
}
|
||||
@@ -1164,11 +1381,11 @@ int nostr_relay_pool_ping_relay_sync(
|
||||
relay->stats.ping_latency_max = ping_latency;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
relay->stats.ping_latency_current = ping_latency;
|
||||
relay->stats.ping_samples++;
|
||||
relay->ping_pending = 0;
|
||||
|
||||
|
||||
if (msg_type) free(msg_type);
|
||||
if (parsed) cJSON_Delete(parsed);
|
||||
return NOSTR_SUCCESS;
|
||||
@@ -1179,12 +1396,15 @@ int nostr_relay_pool_ping_relay_sync(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Timeout
|
||||
relay->ping_pending = 0;
|
||||
return NOSTR_ERROR_NETWORK_FAILED;
|
||||
*/
|
||||
|
||||
|
||||
(void)pool; // Suppress unused parameter warning
|
||||
(void)relay_url; // Suppress unused parameter warning
|
||||
(void)timeout_seconds; // Suppress unused parameter warning
|
||||
return NOSTR_ERROR_INVALID_INPUT; // Function disabled
|
||||
}
|
||||
|
||||
@@ -1233,52 +1453,58 @@ int nostr_relay_pool_poll(nostr_relay_pool_t* pool, int timeout_ms) {
|
||||
if (!pool) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int events_processed = 0;
|
||||
|
||||
|
||||
for (int i = 0; i < pool->relay_count; i++) {
|
||||
relay_connection_t* relay = pool->relays[i];
|
||||
if (!relay || !relay->ws_client) {
|
||||
if (!relay) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Check if reconnection is needed
|
||||
if (should_attempt_reconnect(relay)) {
|
||||
attempt_reconnect(relay);
|
||||
}
|
||||
|
||||
// Skip if no WebSocket client
|
||||
if (!relay->ws_client) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check connection state
|
||||
nostr_ws_state_t state = nostr_ws_get_state(relay->ws_client);
|
||||
|
||||
|
||||
if (state != NOSTR_WS_CONNECTED) {
|
||||
relay->status = (state == NOSTR_WS_ERROR) ? NOSTR_POOL_RELAY_ERROR : NOSTR_POOL_RELAY_DISCONNECTED;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
relay->status = NOSTR_POOL_RELAY_CONNECTED;
|
||||
|
||||
// PING FUNCTIONALITY DISABLED - Automatic ping management
|
||||
/* COMMENTED OUT - PING FUNCTIONALITY DISABLED
|
||||
// Check if we need to send a ping to keep the connection alive
|
||||
if (current_time >= relay->next_ping_time && !relay->ping_pending) {
|
||||
relay->pending_ping_start_ms = get_current_time_ms();
|
||||
relay->ping_pending = 1;
|
||||
relay->last_ping_sent = current_time;
|
||||
relay->next_ping_time = current_time + NOSTR_POOL_PING_INTERVAL;
|
||||
|
||||
if (nostr_ws_send_ping(relay->ws_client, "ping", 4) < 0) {
|
||||
relay->ping_pending = 0;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
// Connection health monitoring (ping/pong)
|
||||
check_connection_health(relay);
|
||||
|
||||
// Process incoming messages
|
||||
char buffer[8192];
|
||||
int timeout_per_relay = timeout_ms / pool->relay_count;
|
||||
|
||||
|
||||
int len = nostr_ws_receive(relay->ws_client, buffer, sizeof(buffer) - 1, timeout_per_relay);
|
||||
|
||||
|
||||
if (len > 0) {
|
||||
buffer[len] = '\0';
|
||||
process_relay_message(pool, relay, buffer);
|
||||
|
||||
// Check if this is a pong message (WebSocket library prefixes pong messages)
|
||||
if (strncmp(buffer, "__PONG__", 8) == 0) {
|
||||
// Handle pong response for connection health monitoring
|
||||
handle_pong_response(relay);
|
||||
} else {
|
||||
// Process as regular NOSTR message
|
||||
process_relay_message(pool, relay, buffer);
|
||||
}
|
||||
events_processed++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return events_processed;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
#ifndef NOSTR_CORE_H
|
||||
#define NOSTR_CORE_H
|
||||
|
||||
// Version information (auto-updated by increment_and_push.sh)
|
||||
#define VERSION "v0.4.3"
|
||||
#define VERSION_MAJOR 0
|
||||
#define VERSION_MINOR 4
|
||||
#define VERSION_PATCH 3
|
||||
|
||||
/*
|
||||
* NOSTR Core Library - Complete API Reference
|
||||
*
|
||||
@@ -159,6 +165,13 @@ typedef enum {
|
||||
NOSTR_POOL_RELAY_ERROR = -1
|
||||
} nostr_pool_relay_status_t;
|
||||
|
||||
// EOSE result mode for subscriptions
|
||||
typedef enum {
|
||||
NOSTR_POOL_EOSE_FULL_SET, // Wait for all relays, return all events
|
||||
NOSTR_POOL_EOSE_MOST_RECENT, // Wait for all relays, return most recent event
|
||||
NOSTR_POOL_EOSE_FIRST // Return results on first EOSE (fastest response)
|
||||
} nostr_pool_eose_result_mode_t;
|
||||
|
||||
typedef struct {
|
||||
int connection_attempts;
|
||||
int connection_failures;
|
||||
@@ -184,8 +197,20 @@ typedef struct {
|
||||
typedef struct nostr_relay_pool nostr_relay_pool_t;
|
||||
typedef struct nostr_pool_subscription nostr_pool_subscription_t;
|
||||
|
||||
// Reconnection configuration
|
||||
typedef struct {
|
||||
int enable_auto_reconnect; // 1 = enable, 0 = disable
|
||||
int max_reconnect_attempts; // Max attempts per relay
|
||||
int initial_reconnect_delay_ms; // Initial delay between attempts
|
||||
int max_reconnect_delay_ms; // Max delay (cap exponential backoff)
|
||||
int reconnect_backoff_multiplier; // Delay multiplier
|
||||
int ping_interval_seconds; // How often to ping (0 = disable)
|
||||
int pong_timeout_seconds; // How long to wait for pong before reconnecting
|
||||
} nostr_pool_reconnect_config_t;
|
||||
|
||||
// Relay pool management functions
|
||||
nostr_relay_pool_t* nostr_relay_pool_create(void);
|
||||
nostr_relay_pool_t* nostr_relay_pool_create(nostr_pool_reconnect_config_t* config);
|
||||
nostr_pool_reconnect_config_t* nostr_pool_reconnect_config_default(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);
|
||||
@@ -197,10 +222,26 @@ nostr_pool_subscription_t* nostr_relay_pool_subscribe(
|
||||
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);
|
||||
void (*on_eose)(cJSON** events, int event_count, void* user_data),
|
||||
void* user_data,
|
||||
int close_on_eose,
|
||||
int enable_deduplication,
|
||||
nostr_pool_eose_result_mode_t result_mode,
|
||||
int relay_timeout_seconds,
|
||||
int eose_timeout_seconds);
|
||||
int nostr_pool_subscription_close(nostr_pool_subscription_t* subscription);
|
||||
|
||||
// Backward compatibility wrapper
|
||||
nostr_pool_subscription_t* nostr_relay_pool_subscribe_compat(
|
||||
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 close_on_eose);
|
||||
|
||||
// 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);
|
||||
@@ -242,6 +283,9 @@ int nostr_relay_pool_reset_relay_stats(
|
||||
double nostr_relay_pool_get_relay_query_latency(
|
||||
nostr_relay_pool_t* pool,
|
||||
const char* relay_url);
|
||||
double nostr_relay_pool_get_relay_ping_latency(
|
||||
nostr_relay_pool_t* pool,
|
||||
const char* relay_url);
|
||||
|
||||
// Synchronous relay operations (one-off queries/publishes)
|
||||
typedef enum {
|
||||
|
||||
692
tests/pool_test.c
Normal file
692
tests/pool_test.c
Normal file
@@ -0,0 +1,692 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
254
tests/relay_synchronous_test.c
Normal file
254
tests/relay_synchronous_test.c
Normal file
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Relay Pool Test Program
|
||||
*
|
||||
* Tests the nostr_relay_pool functionality with persistent connections
|
||||
* and subscriptions. Prints events as they arrive and shows connection status.
|
||||
*
|
||||
* Usage: ./pool_test
|
||||
* Press Ctrl+C to exit
|
||||
*/
|
||||
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include "../nostr_core/nostr_core.h"
|
||||
#include "../cjson/cJSON.h"
|
||||
|
||||
// Global variables for signal handling
|
||||
volatile sig_atomic_t running = 1;
|
||||
time_t last_status_time = 0;
|
||||
|
||||
// Signal handler for clean shutdown
|
||||
void signal_handler(int signum) {
|
||||
(void)signum; // Suppress unused parameter warning
|
||||
printf("\n🛑 Received signal, shutting down...\n");
|
||||
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; // Suppress unused parameter warning
|
||||
|
||||
// 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");
|
||||
|
||||
printf("\n📨 EVENT from %s\n", relay_url);
|
||||
printf("├── ID: %.12s...\n", id && cJSON_IsString(id) ? cJSON_GetStringValue(id) : "unknown");
|
||||
printf("├── Pubkey: %.12s...\n", pubkey && cJSON_IsString(pubkey) ? cJSON_GetStringValue(pubkey) : "unknown");
|
||||
printf("├── Kind: %d\n", kind && cJSON_IsNumber(kind) ? (int)cJSON_GetNumberValue(kind) : -1);
|
||||
printf("├── 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) {
|
||||
printf("└── Content: %.97s...\n", content_str);
|
||||
} else {
|
||||
printf("└── Content: %s\n", content_str);
|
||||
}
|
||||
} else {
|
||||
printf("└── Content: (empty)\n");
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
// EOSE callback - called when End of Stored Events is received
|
||||
void on_eose(cJSON** events, int event_count, void* user_data) {
|
||||
(void)user_data; // Suppress unused parameter warning
|
||||
printf("📋 EOSE received - %d events collected\n", 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)) {
|
||||
printf(" Event %d: %.12s...\n", i + 1, cJSON_GetStringValue(id));
|
||||
}
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
// Print connection status for all relays
|
||||
void print_relay_status(nostr_relay_pool_t* 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");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("\n📊 RELAY STATUS (%d relays):\n", relay_count);
|
||||
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);
|
||||
|
||||
// Show additional stats if available
|
||||
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);
|
||||
}
|
||||
|
||||
free(relay_urls[i]);
|
||||
}
|
||||
printf("\n");
|
||||
free(relay_urls);
|
||||
free(statuses);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
int main() {
|
||||
printf("🔗 NOSTR Relay Pool Test\n");
|
||||
printf("========================\n");
|
||||
printf("Testing persistent relay connections with subscriptions.\n");
|
||||
printf("Press Ctrl+C to exit.\n\n");
|
||||
|
||||
// Initialize NOSTR library
|
||||
if (nostr_init() != NOSTR_SUCCESS) {
|
||||
fprintf(stderr, "❌ Failed to initialize NOSTR library\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Setup signal handler for clean shutdown
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGTERM, signal_handler);
|
||||
|
||||
// Create relay pool with default configuration
|
||||
nostr_pool_reconnect_config_t* config = nostr_pool_reconnect_config_default();
|
||||
nostr_relay_pool_t* pool = nostr_relay_pool_create(config);
|
||||
if (!pool) {
|
||||
fprintf(stderr, "❌ Failed to create relay pool\n");
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Add relays to the pool
|
||||
const char* relay_urls[] = {
|
||||
"wss://nostr.mom",
|
||||
"wss://relay.laantungir.net",
|
||||
"wss://nos.lol"
|
||||
};
|
||||
int relay_count = 3;
|
||||
|
||||
printf("📡 Adding %d relays to pool:\n", relay_count);
|
||||
for (int i = 0; i < relay_count; i++) {
|
||||
printf("├── %s\n", relay_urls[i]);
|
||||
if (nostr_relay_pool_add_relay(pool, relay_urls[i]) != NOSTR_SUCCESS) {
|
||||
printf("│ ❌ Failed to add relay\n");
|
||||
} else {
|
||||
printf("│ ✅ Added successfully\n");
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
// Create filter for subscription (kind 1 events - 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)); // Limit to 10 events per relay
|
||||
|
||||
printf("🔍 Creating subscription with filter:\n");
|
||||
char* filter_json = cJSON_Print(filter);
|
||||
printf("%s\n\n", filter_json);
|
||||
free(filter_json);
|
||||
|
||||
// Create subscription with new parameters
|
||||
nostr_pool_subscription_t* subscription = nostr_relay_pool_subscribe(
|
||||
pool,
|
||||
relay_urls,
|
||||
relay_count,
|
||||
filter,
|
||||
on_event, // Event callback
|
||||
on_eose, // EOSE callback
|
||||
NULL, // User data (not used)
|
||||
0, // close_on_eose (false - keep subscription open)
|
||||
1, // enable_deduplication
|
||||
NOSTR_POOL_EOSE_FULL_SET, // result_mode
|
||||
30, // relay_timeout_seconds
|
||||
60 // eose_timeout_seconds
|
||||
);
|
||||
|
||||
if (!subscription) {
|
||||
fprintf(stderr, "❌ Failed to create subscription\n");
|
||||
cJSON_Delete(filter);
|
||||
nostr_relay_pool_destroy(pool);
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("✅ Subscription created successfully\n");
|
||||
printf("🎯 Listening for events... (Ctrl+C to exit)\n\n");
|
||||
|
||||
// Record start time for status updates
|
||||
last_status_time = time(NULL);
|
||||
|
||||
// Main event loop
|
||||
while (running) {
|
||||
// Poll for events (100ms timeout)
|
||||
int events_processed = nostr_relay_pool_poll(pool, 100);
|
||||
|
||||
// Check if we should print status (every 30 seconds)
|
||||
time_t current_time = time(NULL);
|
||||
if (current_time - last_status_time >= 30) {
|
||||
print_relay_status(pool);
|
||||
last_status_time = current_time;
|
||||
}
|
||||
|
||||
// Small delay to prevent busy waiting
|
||||
if (events_processed == 0) {
|
||||
struct timespec ts = {0, 10000000}; // 10ms
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n🧹 Cleaning up...\n");
|
||||
|
||||
// Close subscription
|
||||
if (subscription) {
|
||||
nostr_pool_subscription_close(subscription);
|
||||
printf("✅ Subscription closed\n");
|
||||
}
|
||||
|
||||
// Destroy pool
|
||||
nostr_relay_pool_destroy(pool);
|
||||
printf("✅ Relay pool destroyed\n");
|
||||
|
||||
// Cleanup JSON
|
||||
cJSON_Delete(filter);
|
||||
|
||||
// Cleanup library
|
||||
nostr_cleanup();
|
||||
|
||||
printf("👋 Test completed successfully\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include "../nostr_core/nostr_common.h"
|
||||
#include "../nostr_core/nostr_core.h"
|
||||
#include "../cjson/cJSON.h"
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ int main() {
|
||||
const char* filter_json =
|
||||
"{"
|
||||
" \"kinds\": [1],"
|
||||
" \"limit\": 1"
|
||||
" \"limit\": 4"
|
||||
"}";
|
||||
|
||||
// Alternative filter examples (comment out the one above, uncomment one below):
|
||||
@@ -133,7 +133,8 @@ int main() {
|
||||
|
||||
cJSON** results = synchronous_query_relays_with_progress(
|
||||
test_relays, relay_count, filter, test_mode,
|
||||
&result_count, 5, progress_callback, NULL
|
||||
&result_count, 5, progress_callback, NULL,
|
||||
1, NULL // nip42_enabled = true, private_key = NULL (no auth)
|
||||
);
|
||||
|
||||
time_t end_time = time(NULL);
|
||||
|
||||
103
tests/wss_test.c
103
tests/wss_test.c
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* WebSocket SSL Test - Test OpenSSL WebSocket implementation
|
||||
* Connect to a NOSTR relay and fetch one type 1 event
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../cjson/cJSON.h"
|
||||
#include "../nostr_core/nostr_common.h"
|
||||
|
||||
// Progress callback to show connection status
|
||||
static void progress_callback(
|
||||
const char* relay_url,
|
||||
const char* status,
|
||||
const char* event_id,
|
||||
int events_received,
|
||||
int total_relays,
|
||||
int completed_relays,
|
||||
void* user_data)
|
||||
{
|
||||
printf("Progress: %s - %s", relay_url ? relay_url : "Summary", status);
|
||||
if (event_id) {
|
||||
printf(" (Event: %.12s...)", event_id);
|
||||
}
|
||||
printf(" [%d/%d events, %d/%d relays]\n",
|
||||
events_received, *(int*)user_data, completed_relays, total_relays);
|
||||
}
|
||||
|
||||
int main() {
|
||||
printf("WebSocket SSL Test - Testing OpenSSL WebSocket with NOSTR relay\n");
|
||||
printf("================================================================\n");
|
||||
|
||||
// Initialize NOSTR library
|
||||
if (nostr_init() != NOSTR_SUCCESS) {
|
||||
printf("❌ Failed to initialize NOSTR library\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("✅ NOSTR library initialized\n");
|
||||
|
||||
// Setup relay and filter
|
||||
const char* relay_urls[] = {"wss://nostr.mom"};
|
||||
int relay_count = 1;
|
||||
|
||||
// Create filter for type 1 events (text notes), limit to 1 event
|
||||
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(1));
|
||||
|
||||
printf("📡 Connecting to %s...\n", relay_urls[0]);
|
||||
printf("🔍 Requesting 1 type 1 event (text note)...\n\n");
|
||||
|
||||
// Query the relay
|
||||
int result_count = 0;
|
||||
int expected_events = 1;
|
||||
cJSON** events = synchronous_query_relays_with_progress(
|
||||
relay_urls,
|
||||
relay_count,
|
||||
filter,
|
||||
RELAY_QUERY_FIRST_RESULT, // Return as soon as we get the first event
|
||||
&result_count,
|
||||
10, // 10 second timeout
|
||||
progress_callback,
|
||||
&expected_events
|
||||
);
|
||||
|
||||
// Process results
|
||||
if (events && result_count > 0) {
|
||||
printf("\n✅ Successfully received %d event(s)!\n", result_count);
|
||||
printf("📄 Raw JSON event data:\n");
|
||||
printf("========================\n");
|
||||
|
||||
for (int i = 0; i < result_count; i++) {
|
||||
char* json_string = cJSON_Print(events[i]);
|
||||
if (json_string) {
|
||||
printf("%s\n\n", json_string);
|
||||
free(json_string);
|
||||
}
|
||||
cJSON_Delete(events[i]);
|
||||
}
|
||||
free(events);
|
||||
|
||||
printf("🎉 WebSocket SSL Test PASSED - OpenSSL WebSocket working correctly!\n");
|
||||
} else {
|
||||
printf("\n❌ No events received or query failed\n");
|
||||
printf("❌ WebSocket SSL Test FAILED\n");
|
||||
|
||||
// Cleanup and return error
|
||||
cJSON_Delete(filter);
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
cJSON_Delete(filter);
|
||||
nostr_cleanup();
|
||||
|
||||
printf("✅ WebSocket connection and TLS working with OpenSSL\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user