From 227c57914751330674557ffeb08a6de498f0a925 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 3 Sep 2025 20:39:06 -0400 Subject: [PATCH] nip01 upload --- .gitignore | 1 + .gitmodules | 3 + Makefile | 72 +++++ README.md | 4 +- db/README.md | 228 ++++++++++++++++ db/c_nostr_relay.db | Bin 0 -> 53248 bytes db/c_nostr_relay.db-shm | Bin 0 -> 32768 bytes db/c_nostr_relay.db-wal | Bin 0 -> 90672 bytes db/init.sh | 234 ++++++++++++++++ db/schema.sql | 66 +++++ make_and_restart_relay.sh | 94 +++++++ nips | 1 + nostr_core_lib | 1 + relay.log | 11 + relay.pid | 1 + src/main | Bin 0 -> 202976 bytes src/main.c | 562 ++++++++++++++++++++++++++++++++++++++ tests/1_nip_test.sh | 112 ++++++++ 18 files changed, 1389 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Makefile create mode 100644 db/README.md create mode 100644 db/c_nostr_relay.db create mode 100644 db/c_nostr_relay.db-shm create mode 100644 db/c_nostr_relay.db-wal create mode 100755 db/init.sh create mode 100644 db/schema.sql create mode 100755 make_and_restart_relay.sh create mode 160000 nips create mode 160000 nostr_core_lib create mode 100644 relay.log create mode 100644 relay.pid create mode 100755 src/main create mode 100644 src/main.c create mode 100755 tests/1_nip_test.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..45376a1 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +nostr_core_lib/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..84dd331 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "nostr_core_lib"] + path = nostr_core_lib + url = https://git.laantungir.net/laantungir/nostr_core_lib.git diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6834d5c --- /dev/null +++ b/Makefile @@ -0,0 +1,72 @@ +# C-Relay Makefile + +CC = gcc +CFLAGS = -Wall -Wextra -std=c99 -g -O2 +INCLUDES = -I. -Inostr_core_lib -Inostr_core_lib/nostr_core -Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket +LIBS = -lsqlite3 -lwebsockets -lz -ldl -lpthread -lm -L/usr/local/lib -lsecp256k1 -lssl -lcrypto -L/usr/local/lib -lcurl + +# Source files +MAIN_SRC = src/main.c +NOSTR_CORE_LIB = nostr_core_lib/libnostr_core_x64.a + +# Target binary +TARGET = src/main + +# Default target +all: $(TARGET) + +# Check if nostr_core_lib is built +$(NOSTR_CORE_LIB): + @echo "Building nostr_core_lib..." + cd nostr_core_lib && ./build.sh + +# Build the relay +$(TARGET): $(MAIN_SRC) $(NOSTR_CORE_LIB) + @echo "Compiling C-Relay..." + $(CC) $(CFLAGS) $(INCLUDES) $(MAIN_SRC) -o $(TARGET) $(NOSTR_CORE_LIB) $(LIBS) + @echo "Build complete: $(TARGET)" + +# Run tests +test: $(TARGET) + @echo "Running tests..." + ./tests/1_nip_test.sh + +# Initialize database +init-db: + @echo "Initializing database..." + ./db/init.sh --force + +# Clean build artifacts +clean: + rm -f $(TARGET) + @echo "Clean complete" + +# Clean everything including nostr_core_lib +clean-all: clean + cd nostr_core_lib && make clean 2>/dev/null || true + +# Install dependencies (Ubuntu/Debian) +install-deps: + @echo "Installing dependencies..." + sudo apt update + sudo apt install -y build-essential libsqlite3-dev libssl-dev libcurl4-openssl-dev libsecp256k1-dev zlib1g-dev jq curl + +# Help +help: + @echo "C-Relay Build System" + @echo "" + @echo "Targets:" + @echo " all Build the relay (default)" + @echo " test Build and run tests" + @echo " init-db Initialize the database" + @echo " clean Clean build artifacts" + @echo " clean-all Clean everything including dependencies" + @echo " install-deps Install system dependencies" + @echo " help Show this help" + @echo "" + @echo "Usage:" + @echo " make # Build the relay" + @echo " make test # Run tests" + @echo " make init-db # Set up database" + +.PHONY: all test init-db clean clean-all install-deps help \ No newline at end of file diff --git a/README.md b/README.md index 40f8490..cf19cbd 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ -A nostr relay in C. +A nostr relay in C with sqlite on the back end. + + diff --git a/db/README.md b/db/README.md new file mode 100644 index 0000000..d4080cb --- /dev/null +++ b/db/README.md @@ -0,0 +1,228 @@ +# C Nostr Relay Database + +This directory contains the SQLite database schema and initialization scripts for the C Nostr Relay implementation. + +## Files + +- **`schema.sql`** - Complete database schema based on nostr-rs-relay v18 +- **`init.sh`** - Database initialization script +- **`c_nostr_relay.db`** - SQLite database file (created after running init.sh) + +## Quick Start + +1. **Initialize the database:** + ```bash + cd db + ./init.sh + ``` + +2. **Force reinitialize (removes existing database):** + ```bash + ./init.sh --force + ``` + +3. **Initialize with optimization and info:** + ```bash + ./init.sh --info --optimize + ``` + +## Database Schema + +The schema is fully compatible with the Nostr protocol and includes: + +### Core Tables + +- **`event`** - Main event storage with all Nostr event data +- **`tag`** - Denormalized tag index for efficient queries +- **`user_verification`** - NIP-05 verification tracking +- **`account`** - User account management (optional) +- **`invoice`** - Lightning payment tracking (optional) + +### Key Features + +- ✅ **NIP-01 compliant** - Full basic protocol support +- ✅ **Replaceable events** - Supports kinds 0, 3, 10000-19999 +- ✅ **Parameterized replaceable** - Supports kinds 30000-39999 with `d` tags +- ✅ **Event deletion** - NIP-09 soft deletion with `hidden` column +- ✅ **Event expiration** - NIP-40 automatic cleanup +- ✅ **Authentication** - NIP-42 client authentication +- ✅ **NIP-05 verification** - Domain-based identity verification +- ✅ **Performance optimized** - Comprehensive indexing strategy + +### Schema Version + +Current version: **v18** (compatible with nostr-rs-relay v18) + +## Database Structure + +### Event Storage +```sql +CREATE TABLE event ( + id INTEGER PRIMARY KEY, + event_hash BLOB NOT NULL, -- 32-byte SHA256 hash + first_seen INTEGER NOT NULL, -- relay receive timestamp + created_at INTEGER NOT NULL, -- event creation timestamp + expires_at INTEGER, -- NIP-40 expiration + author BLOB NOT NULL, -- 32-byte pubkey + delegated_by BLOB, -- NIP-26 delegator + kind INTEGER NOT NULL, -- event kind + hidden INTEGER DEFAULT FALSE, -- soft deletion flag + content TEXT NOT NULL -- complete JSON event +); +``` + +### Tag Indexing +```sql +CREATE TABLE tag ( + id INTEGER PRIMARY KEY, + event_id INTEGER NOT NULL, + name TEXT, -- tag name ("e", "p", etc.) + value TEXT, -- tag value + created_at INTEGER NOT NULL, -- denormalized for performance + kind INTEGER NOT NULL -- denormalized for performance +); +``` + +## Performance Features + +### Optimized Indexes +- **Hash-based lookups** - `event_hash_index` for O(1) event retrieval +- **Author queries** - `author_index`, `author_created_at_index` +- **Kind filtering** - `kind_index`, `kind_created_at_index` +- **Tag searching** - `tag_covering_index` for efficient tag queries +- **Composite queries** - Multi-column indexes for complex filters + +### Query Optimization +- **Denormalized tags** - Includes `kind` and `created_at` in tag table +- **Binary storage** - BLOBs for hex data (pubkeys, hashes) +- **WAL mode** - Write-Ahead Logging for concurrent access +- **Automatic cleanup** - Triggers for data integrity + +## Usage Examples + +### Basic Operations + +1. **Insert an event:** + ```sql + INSERT INTO event (event_hash, first_seen, created_at, author, kind, content) + VALUES (?, ?, ?, ?, ?, ?); + ``` + +2. **Query by author:** + ```sql + SELECT content FROM event + WHERE author = ? AND hidden != TRUE + ORDER BY created_at DESC; + ``` + +3. **Filter by tags:** + ```sql + SELECT e.content FROM event e + JOIN tag t ON e.id = t.event_id + WHERE t.name = 'p' AND t.value = ? AND e.hidden != TRUE; + ``` + +### Advanced Queries + +1. **Get replaceable event (latest only):** + ```sql + SELECT content FROM event + WHERE author = ? AND kind = ? AND hidden != TRUE + ORDER BY created_at DESC LIMIT 1; + ``` + +2. **Tag-based filtering (NIP-01 filters):** + ```sql + SELECT e.content FROM event e + WHERE e.id IN ( + SELECT t.event_id FROM tag t + WHERE t.name = ? AND t.value IN (?, ?, ?) + ) AND e.hidden != TRUE; + ``` + +## Maintenance + +### Regular Operations + +1. **Check database integrity:** + ```bash + sqlite3 c_nostr_relay.db "PRAGMA integrity_check;" + ``` + +2. **Optimize database:** + ```bash + sqlite3 c_nostr_relay.db "PRAGMA optimize; VACUUM; ANALYZE;" + ``` + +3. **Clean expired events:** + ```sql + DELETE FROM event WHERE expires_at <= strftime('%s', 'now'); + ``` + +### Monitoring + +1. **Database size:** + ```bash + ls -lh c_nostr_relay.db + ``` + +2. **Table statistics:** + ```sql + SELECT name, COUNT(*) as count FROM ( + SELECT 'events' as name FROM event UNION ALL + SELECT 'tags' as name FROM tag UNION ALL + SELECT 'verifications' as name FROM user_verification + ) GROUP BY name; + ``` + +## Migration Support + +The schema includes a migration system for future updates: + +```sql +CREATE TABLE schema_info ( + version INTEGER PRIMARY KEY, + applied_at INTEGER NOT NULL, + description TEXT +); +``` + +## Security Considerations + +1. **Input validation** - Always validate event JSON and signatures +2. **Rate limiting** - Implement at application level +3. **Access control** - Use `account` table for permissions +4. **Backup strategy** - Regular database backups recommended + +## Compatibility + +- **SQLite version** - Requires SQLite 3.8.0+ +- **nostr-rs-relay** - Schema compatible with v18 +- **NIPs supported** - 01, 02, 05, 09, 10, 11, 26, 40, 42 +- **C libraries** - Compatible with sqlite3 C API + +## Troubleshooting + +### Common Issues + +1. **Database locked error:** + - Ensure proper connection closing in your C code + - Check for long-running transactions + +2. **Performance issues:** + - Run `PRAGMA optimize;` regularly + - Consider `VACUUM` if database grew significantly + +3. **Schema errors:** + - Verify SQLite version compatibility + - Check foreign key constraints + +### Getting Help + +- Check the main project README for C implementation details +- Review nostr-rs-relay documentation for reference implementation +- Consult Nostr NIPs for protocol specifications + +## License + +This database schema is part of the C Nostr Relay project and follows the same license terms. \ No newline at end of file diff --git a/db/c_nostr_relay.db b/db/c_nostr_relay.db new file mode 100644 index 0000000000000000000000000000000000000000..caf0336a79cb0b444e40ed91e9688a855733e4de GIT binary patch literal 53248 zcmeI*!Ef4D9Kdm#rX~a6E7V zv9CSQG8yfO5SpfClJGR%XL{&Ui;?4_t<^Er>G^2ZecG@?5mC+yxp_U&vClf9c`QGtjYcIbT(Rx$iso<sc`hhsA zi}KjLbjPlL;ZCB$J(*j@3{gHNs*Kr@7ow_{-My>5ir8^?g`0)6z)fan?+OA z)n$yFvAmGL9Zyv2+wM%rhSgrr3%sH4^cBrq%kuNK96|ENJ^w1Yo@d7``{=MKzO!G- zQDupi{mgFJ&6?eg7*wHbb9zz_$7QYBu2t)H*gtiocP_2E(d5`zktwN?1P{xs?uKE0W9q*o%~xOk{S`qIjmS0$ot&}S z_YCuqsSoB7%x*g=*;o;LzETo6Dz)B?^sH%``X7(gyi{GJzB02&(cDx;AI#2^*3pY< z>t#6RmWJ_)nfcniSs1d>1>8M{WSEobdEdJbax{qH(m@w?>;ilimkamCIl4etmGTe-O35 z82Ul@aJuV(_P%{wxg5y%iinSfe*bM0Y~o!l23q}4jP6iY_MKolb{C;6D`S}7n!2V& z&E%)PT-kS}=ujPZRZn*xsbBZxD|Rz{&8h|dgY;gwWJUl11Q0*~0R#|0009ILKmdVH zFOUyYu>Sw_&}2XW0R#|0009ILKmY**5I`VB0oMO1;*%Ny1Q0*~0R#|0009ILKmdU? z39$ZO6EtZMKmY**5I_I{1Q0*~0R#|8QGoS-iuj~P009ILKmY**5I_I{1Q0-AO#-a{ z*91)(1Q0*~0R#|0009ILKmY**QWRkQpCUe~5kLR|1Q0*~0R#|0009ILSd#$%|8Gsu zq(J}y1Q0*~0R#|0009ILKp;f{{{EjLKB*Bv009ILKmY**5I_I{1Q1x00PFuXL6Zgn z1Q0*~0R#|0009ILKmdUh1z7*5h)-$+5I_I{1Q0*~0R#|0009KnB*6NAP0*x4009IL TKmY**5I_I{1Q0+VMS=eT9^EH+ literal 0 HcmV?d00001 diff --git a/db/c_nostr_relay.db-shm b/db/c_nostr_relay.db-shm new file mode 100644 index 0000000000000000000000000000000000000000..c58baa3b3366d79ad5a53ff4330799ff63ba45e2 GIT binary patch literal 32768 zcmeI*J4!@B5Czck?|l60FtNG0U^an!F>?iO#08ie3Su^3JfWFeZ~FcD0ySSz2=o-F$)ZA_r$Eir6#_j4 zwsUs$Vb80(b$fw9?8Z@h1pqw-hOrmNJ+JQ8?FB}$A1Cb<0Q3|X$3fH|XoLU(0t5&U bAV7cs0RjXF5FkK+009C72oNCf{{`Lw8A~h* literal 0 HcmV?d00001 diff --git a/db/c_nostr_relay.db-wal b/db/c_nostr_relay.db-wal new file mode 100644 index 0000000000000000000000000000000000000000..b1cb61c9701b4f94b2d5b9738f159bebbb9501b1 GIT binary patch literal 90672 zcmeI*Z)jC@9LMo<@41`aZPzm~MHtcM($NF!{JZCz>qx;I85N|BDTUhkcWG{OV}A;t zEViN%Qc*!v_8^0j=us_1lzKFa2up|-VSmUFQVIhVwG{iE9q-r-O}p2w8}A>Ndw0*} zo^!shb02;8`+bi6ROPU={K#TSs+FSgsngv4@!-BiE0;8XS2OlKmY**5I_I{1Q0*~0R#}3X#&H`5>-Q)p-d`$_i)BC96e{-zMl;> zHE;q?x5Iqkg??ZKMo!nWu5B2$r8;?6HFH@bXM}mfGs3Lnn6B&Fu50Ff$Fj2dtd+Au z!w5V()ZDDChlZ!-vz`%HTGlaa@y4+9!1pxYQ7y+)bB=F&=Hq*RJi0dQ?Ch@3_jY&H z-?Myu*y(Ps?+^R>8*-W!`l{{+j_x?B?RbH&=lq;Fu4VXn&lfumeBU)yTeqF8s+)RX zn!cIUR9n+k&o@=g)vZ9(C%&!Op(s^%&AT|4_1k731z<|9t;E!KmY**5I_I{1Q0*~0R#}Z`2^Oa zhBE15dmf^!R8(4_FED6!E#5xz&~x+!ZoUi786kiG0tg_000IagfB*srATUK>qAyVF zTn4efifEsOzQC!fmg>IOPL9TXfwPiwcFGa?69EJeKmY**5I_I{1Q0*~0R(1+z#KW1 zj@CDbVk)D83Vnf*i?96k$kXp8;=aInNjX0&%B2Vd5I_I{1Q0*~0R#|0009ILxJ*En z(`m73PPDnu7g$lT?7*G7H?E5N0(&H7&t+fYEdmH2fB*srAb8HTn_7(5f0?*Xp|V>N4+a7VAbRAQgN2fxZE6Yv}j4id!Vsw>%MU>lbB3Qv)XZ0=t$5^SgKUJ|E8);68=rz3dzv z0R#|0009ILKmY**5I_KdD+-kAK84lM5en-Fs*j#}`=<{+X^i^iK=bq$4Xz_fJ)QIx=B?%+qOo;mSF#ku?AzQCxYj85MR z?+`!$0R#|0009ILKmY**5I~@;0uP9r5!9tilr$hJnU*{FW%HpIzFNJjJ8Bjrm31O_ zP&_dZKmY**5I_I{1Q0*~0R#|0;MxRQlOiEdX(~D)FzgrEfLp>S8*oc|@W15_u3X!` z^R1eLUD14jq%t5W`^6Ij0R#|0009ILKmY**5I_I{1WG5+k{Ze+iv5Rw@`mINx&s^D zS+Oy9B<>5GkdzZ750>u2vnK=)KmY**5I_I{1Q0*~0R+k_P$4UcFnN{S!5^Lt_HI4& zUNY_rj7!Qm$%AFRVjLU+1Q0*~0R#|0009ILKmdX37Fdu{niA7jA|$UZckuh(!v}tU za!`r;0v(dlLGs{r^YaA+5I_I{1Q0*~0R#|0009L4DR8gI3sj0;J`g5vXzt*_ecx4&6_)3t&Qdj$jV(4xr5?`fdB#sAb`4 /dev/null; then + log_error "sqlite3 is not installed. Please install it first:" + echo " Ubuntu/Debian: sudo apt-get install sqlite3" + echo " CentOS/RHEL: sudo yum install sqlite" + echo " macOS: brew install sqlite3" + exit 1 + fi + + local version=$(sqlite3 --version | cut -d' ' -f1) + log_info "Using SQLite version: $version" +} + +# Create database directory if it doesn't exist +create_db_directory() { + if [ ! -d "$DB_DIR" ]; then + log_info "Creating database directory: $DB_DIR" + mkdir -p "$DB_DIR" + fi +} + +# Backup existing database if it exists +backup_existing_db() { + if [ -f "$DB_PATH" ]; then + local backup_path="${DB_PATH}.backup.$(date +%Y%m%d_%H%M%S)" + log_warning "Existing database found. Creating backup: $backup_path" + cp "$DB_PATH" "$backup_path" + fi +} + +# Initialize the database with schema +init_database() { + log_info "Initializing database: $DB_PATH" + + if [ ! -f "$SCHEMA_FILE" ]; then + log_error "Schema file not found: $SCHEMA_FILE" + exit 1 + fi + + # Remove existing database if --force flag is used + if [ "$1" = "--force" ] && [ -f "$DB_PATH" ]; then + log_warning "Force flag detected. Removing existing database." + rm -f "$DB_PATH" + fi + + # Create the database and apply schema + log_info "Applying schema from: $SCHEMA_FILE" + if sqlite3 "$DB_PATH" < "$SCHEMA_FILE"; then + log_success "Database schema applied successfully" + else + log_error "Failed to apply database schema" + exit 1 + fi +} + +# Verify database integrity +verify_database() { + log_info "Verifying database integrity..." + + # Check if database file exists and is not empty + if [ ! -s "$DB_PATH" ]; then + log_error "Database file is empty or doesn't exist" + exit 1 + fi + + # Run SQLite integrity check + local integrity_result=$(sqlite3 "$DB_PATH" "PRAGMA integrity_check;") + if [ "$integrity_result" = "ok" ]; then + log_success "Database integrity check passed" + else + log_error "Database integrity check failed: $integrity_result" + exit 1 + fi + + # Verify schema version + local schema_version=$(sqlite3 "$DB_PATH" "PRAGMA user_version;") + log_info "Database schema version: $schema_version" + + # Check that main tables exist + local table_count=$(sqlite3 "$DB_PATH" "SELECT count(*) FROM sqlite_master WHERE type='table' AND name IN ('event', 'tag');") + if [ "$table_count" -eq 2 ]; then + log_success "Core tables created successfully" + else + log_error "Missing core tables (expected 2, found $table_count)" + exit 1 + fi +} + +# Display database information +show_db_info() { + log_info "Database Information:" + echo " Location: $DB_PATH" + echo " Size: $(du -h "$DB_PATH" | cut -f1)" + + log_info "Database Tables:" + sqlite3 "$DB_PATH" "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;" | sed 's/^/ - /' + + log_info "Database Indexes:" + sqlite3 "$DB_PATH" "SELECT name FROM sqlite_master WHERE type='index' AND name NOT LIKE 'sqlite_%' ORDER BY name;" | sed 's/^/ - /' + + log_info "Database Views:" + sqlite3 "$DB_PATH" "SELECT name FROM sqlite_master WHERE type='view' ORDER BY name;" | sed 's/^/ - /' +} + +# Run database optimization +optimize_database() { + log_info "Running database optimization..." + sqlite3 "$DB_PATH" "PRAGMA optimize; VACUUM; ANALYZE;" + log_success "Database optimization completed" +} + +# Print usage information +print_usage() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Initialize SQLite database for C Nostr Relay" + echo "" + echo "Options:" + echo " --force Remove existing database before initialization" + echo " --info Show database information after initialization" + echo " --optimize Run database optimization after initialization" + echo " --help Show this help message" + echo "" + echo "Examples:" + echo " $0 # Initialize database (with backup if exists)" + echo " $0 --force # Force reinitialize database" + echo " $0 --info --optimize # Initialize with info and optimization" +} + +# Main execution +main() { + local force_flag=false + local show_info=false + local optimize=false + + # Parse command line arguments + while [[ $# -gt 0 ]]; do + case $1 in + --force) + force_flag=true + shift + ;; + --info) + show_info=true + shift + ;; + --optimize) + optimize=true + shift + ;; + --help) + print_usage + exit 0 + ;; + *) + log_error "Unknown option: $1" + print_usage + exit 1 + ;; + esac + done + + log_info "Starting C Nostr Relay database initialization..." + + # Execute initialization steps + check_sqlite + create_db_directory + + if [ "$force_flag" = false ]; then + backup_existing_db + fi + + if [ "$force_flag" = true ]; then + init_database --force + else + init_database + fi + + verify_database + + if [ "$optimize" = true ]; then + optimize_database + fi + + if [ "$show_info" = true ]; then + show_db_info + fi + + log_success "Database initialization completed successfully!" + echo "" + echo "Database ready at: $DB_PATH" + echo "You can now start your C Nostr Relay application." +} + +# Execute main function with all arguments +main "$@" \ No newline at end of file diff --git a/db/schema.sql b/db/schema.sql new file mode 100644 index 0000000..881376c --- /dev/null +++ b/db/schema.sql @@ -0,0 +1,66 @@ +-- C Nostr Relay Database Schema +-- Simplified schema with just event and tag tables +-- SQLite database for storing Nostr events + +-- ============================================================================ +-- DATABASE SETTINGS +-- ============================================================================ + +PRAGMA encoding = "UTF-8"; +PRAGMA journal_mode = WAL; +PRAGMA auto_vacuum = FULL; +PRAGMA synchronous = NORMAL; +PRAGMA foreign_keys = ON; + +-- ============================================================================ +-- EVENT TABLE +-- ============================================================================ + +-- Main event table - stores all Nostr events +CREATE TABLE IF NOT EXISTS event ( + id TEXT PRIMARY KEY, -- Nostr event ID (hex string) + pubkey TEXT NOT NULL, -- Public key of event author (hex string) + created_at INTEGER NOT NULL, -- Event creation timestamp (Unix timestamp) + kind INTEGER NOT NULL, -- Event kind (0-65535) + content TEXT NOT NULL, -- Event content (text content only) + sig TEXT NOT NULL -- Event signature (hex string) +); + +-- Event indexes for performance +CREATE INDEX IF NOT EXISTS event_pubkey_index ON event(pubkey); +CREATE INDEX IF NOT EXISTS event_created_at_index ON event(created_at); +CREATE INDEX IF NOT EXISTS event_kind_index ON event(kind); +CREATE INDEX IF NOT EXISTS event_pubkey_created_at_index ON event(pubkey, created_at); +CREATE INDEX IF NOT EXISTS event_kind_created_at_index ON event(kind, created_at); + +-- ============================================================================ +-- TAG TABLE +-- ============================================================================ + +-- Tag table for storing event tags +CREATE TABLE IF NOT EXISTS tag ( + id TEXT NOT NULL, -- Nostr event ID (references event.id) + name TEXT NOT NULL, -- Tag name (e.g., "e", "p", "d") + value TEXT NOT NULL, -- Tag value + parameters TEXT, -- Additional tag parameters (JSON string) + FOREIGN KEY(id) REFERENCES event(id) ON UPDATE CASCADE ON DELETE CASCADE +); + +-- Tag indexes for performance +CREATE INDEX IF NOT EXISTS tag_id_index ON tag(id); +CREATE INDEX IF NOT EXISTS tag_name_index ON tag(name); +CREATE INDEX IF NOT EXISTS tag_name_value_index ON tag(name, value); +CREATE INDEX IF NOT EXISTS tag_id_name_index ON tag(id, name); + +-- ============================================================================ +-- PERFORMANCE OPTIMIZATIONS +-- ============================================================================ + +-- Enable query planner optimizations +PRAGMA optimize; + +-- Set recommended pragmas for performance +PRAGMA main.synchronous = NORMAL; +PRAGMA foreign_keys = ON; +PRAGMA temp_store = 2; -- use memory for temp tables +PRAGMA main.cache_size = 10000; -- 40MB cache per connection \ No newline at end of file diff --git a/make_and_restart_relay.sh b/make_and_restart_relay.sh new file mode 100755 index 0000000..2c472e8 --- /dev/null +++ b/make_and_restart_relay.sh @@ -0,0 +1,94 @@ +#!/bin/bash + +# C-Relay Build and Restart Script +# Builds the project first, then stops any running relay and starts a new one in the background + +echo "=== C Nostr Relay Build and Restart Script ===" + +# Build the project first +echo "Building project..." +make clean all + +# Check if build was successful +if [ $? -ne 0 ]; then + echo "ERROR: Build failed. Cannot restart relay." + exit 1 +fi + +# Check if relay binary exists after build +if [ ! -f "./src/main" ]; then + echo "ERROR: Relay binary not found after build. Build may have failed." + exit 1 +fi + +echo "Build successful. Proceeding with relay restart..." + +# Kill existing relay if running +echo "Stopping any existing relay servers..." +pkill -f "./src/main" 2>/dev/null +sleep 2 # Give time for shutdown + +# Check if port is still bound +if lsof -i :8888 >/dev/null 2>&1; then + echo "Port 8888 still in use, force killing..." + fuser -k 8888/tcp 2>/dev/null || echo "No process on port 8888" +fi + +# Get any remaining processes +REMAINING_PIDS=$(pgrep -f "./src/main" || echo "") +if [ -n "$REMAINING_PIDS" ]; then + echo "Force killing remaining processes: $REMAINING_PIDS" + kill -9 $REMAINING_PIDS 2>/dev/null + sleep 1 +else + echo "No existing relay found" +fi + +# Clean up PID file +rm -f relay.pid + +# Initialize database if needed +if [ ! -f "./db/c_nostr_relay.db" ]; then + echo "Initializing database..." + ./db/init.sh --force >/dev/null 2>&1 +fi + +# Start relay in background with output redirection +echo "Starting relay server..." +echo "Debug: Current processes: $(ps aux | grep './src/main' | grep -v grep || echo 'None')" + +# Start relay in background and capture its PID +./src/main > relay.log 2>&1 & +RELAY_PID=$! + +echo "Started with PID: $RELAY_PID" + +# Check if server is still running after short delay +sleep 3 + +# Check if process is still alive +if ps -p "$RELAY_PID" >/dev/null 2>&1; then + echo "Relay started successfully!" + echo "PID: $RELAY_PID" + echo "WebSocket endpoint: ws://127.0.0.1:8888" + echo "Log file: relay.log" + echo "" + + # Save PID for debugging + echo $RELAY_PID > relay.pid + + echo "=== Relay server running in background ===" + echo "To kill relay: pkill -f './src/main'" + echo "To check status: ps aux | grep src/main" + echo "To view logs: tail -f relay.log" + echo "Ready for Nostr client connections!" +else + echo "ERROR: Relay failed to start" + echo "Debug: Check relay.log for error details:" + echo "--- Last 10 lines of relay.log ---" + tail -n 10 relay.log 2>/dev/null || echo "No log file found" + echo "--- End log ---" + exit 1 +fi + +echo "" \ No newline at end of file diff --git a/nips b/nips new file mode 160000 index 0000000..8c45ff5 --- /dev/null +++ b/nips @@ -0,0 +1 @@ +Subproject commit 8c45ff5d964d6d9bf329c72713e43c89c060de09 diff --git a/nostr_core_lib b/nostr_core_lib new file mode 160000 index 0000000..33129d8 --- /dev/null +++ b/nostr_core_lib @@ -0,0 +1 @@ +Subproject commit 33129d82fdce8cff280bc0b5ba7ed5e49531606d diff --git a/relay.log b/relay.log new file mode 100644 index 0000000..41e8a3b --- /dev/null +++ b/relay.log @@ -0,0 +1,11 @@ +=== C Nostr Relay Server === +[SUCCESS] Database connection established +[INFO] Starting relay server... +[INFO] Starting libwebsockets-based Nostr relay server... +[SUCCESS] WebSocket relay started on ws://127.0.0.1:8888 +[INFO] WebSocket connection established +[INFO] Received WebSocket message +[INFO] Handling EVENT message +[SUCCESS] Event stored in database +[SUCCESS] Event stored successfully +[INFO] WebSocket connection closed diff --git a/relay.pid b/relay.pid new file mode 100644 index 0000000..cb155a4 --- /dev/null +++ b/relay.pid @@ -0,0 +1 @@ +320933 diff --git a/src/main b/src/main new file mode 100755 index 0000000000000000000000000000000000000000..81bd73b549ccc3ae50aeb5bc5e77b3717b2152e4 GIT binary patch literal 202976 zcmeF)d3+Q_{x|Rr5E)dQL5YqcIAYL2Kqn|V$l-)TIvR9T(5#3FU~o{5F_}nIj=?0F zF-FHb?s`OBHD0?>S2y6Q11O1i#4FzMq+?Vf3dV~(-|GI%r01jgy`JOs{I}VcOzPd= zuCA)CuCD4H*tIkBPwJPEp(TI%Yg09qN_1Efz=rmH`r|iE3uu0Au>9%Oj?nfH9U%V& zu$4dC`dJ&2pAN*S+|+(ykl)ierhf&t@g`QzqYGCnJdrX@d<$2(p2Q|58Mqn?+FN^2mRxxU){3tI26eC_^)9{i0SnnV;G=6x&t&zm9xi z^t3aJ$Nl}j72h2H;)=)4nZJ63ic#fO`=}W8qw>QkPo)F$n~G8GQK_wygv+CBn*67J zyu+_K_>JP%mR;F(%c?1lP8@9Mpa1EQ1MK@JoBGKC{>(4z!}sgMUzEK6ET1o>Qh(;l z`-pGrBmc@i{LVh=bwMA;UD`+dNqv;(u0D=?OCR|Z_u-TK$Y(0n-AMts8l;`a};y>)8oLzl*RUi51^^yOEKH~4|BmYDCsP8R( z#DCt$aVz?WpVmkG4}JJ`efZ=)d_o`oP9Nk&ijOH=v~)#jv6f6pe&JMNMYwo*DzRwA(z0dC z3zsa99CvIA7cNj;_!l!;=)VDr)cq#OSZ2rEeqv6Dk^UPg zS+q?0w48tiOSQ#jpI(ekFPp;Bc9D|E3aPL9xXAigsx2#CR$455FH*8x?Os?|y4-fO z(#ZKM$|Bn0lHy`*@#3Xrr6DWQ%5AA!HidfUZOO|Rnsu4saiMe4WXv|0Ii)20`WKkC@jZ^x&8%jv~qY`>!(JHSj2)J{eErt5E!Y5g8r0n-06)0x^Lr1vx92WZQYE<0$+Ahk*; zM>=w#O83($C9Qsv>(1nOU;nk`by$xky$D`U{^2UQp~z1I`48|$^53tp;+x2SMSOxh zW3?6ELOu}QO1=-gjXVq9PCf!|kdK0Qk&l6QlaGgM-&o~Mw%a7QgM2#NMSe2eO@0R4 zLw+{gOI`%`k%!=Z^3&E@dnmh=PlNZ42 z$cy0hkOUvLNcf8Z|i&2TsQ%Wx0*n{Y3AYn4?FANdD}_mh7L50H1jb@DEFkbD=s zfP6Q+hgTKd%?@eUGOOR{_tw{TC08HARh;BBtI73 zL_QgwAfFCzA-|^%{h$2W3d`HbPewlNyKc!+!pJWT#7 zyqtV1JWBo^yqf%Dc%1x8cpdq-@Otu{@CNc<;f>^(xSuwWXW;&uAm0PtLcR~Ym3%0? zjr?GEJNcn-gZyxK7x~ffZt@9mtt-9%Pl7whPlUV3XTja%r@=ksbKqX`d2k>3Lb#uN z2|Pf)46c($;6d_L@B;GnC04r>kyju-L>_~O$>Z>H@>}6i@;l(w3Kg z%ymeU?+tg64~4tQhrvDMhrzw%N5FmLW8r@C9C(0yB3vh*3J;RcfESRT0xu#z9UdY- z8y+T~4=*P#hDXUSf>)DY43Cqqgx8U;hS!rHYsda0mHDxQo0Q?k0Z??je60?j`>a z?j!#c?k6|k0rKr|o%{!Qko+fj0eKI+h&&U|D?;RZ!NcT3;N|28z@y~D;nn2X@HqLA z@H+Awcs=>?@CNd!@J8|(@Fwz;;R*6H;VtCnz+1@|z}v_#fVY#cfE(mx@GkPp;oam{ zz_lOJ`+qguL4F*8XntTM_Reza8!+zZ>o&Z-D#B{|OI}KLyvxpMeL-pNAKa zzXUHLe+?cYe-|Dm{}5hI{y98K-T|*B{|+7}-vzHD{|#PG-Vf`|2J%7hM)G~&P2}rv z-V@~eA-;v&4R0ks1l~q|`rk1AlV>B|AU_h`MVWDFg>XOla(IBe46c)31`m=);RWPZ!HdXi;UV%H;9>Gx;pODF!=vQ) z!mG(2g2%}pgV&Ki39l!A2Hrrv8Qw_#3cQK@O?ZO*Va%&r$lphNEBR;eHu7!ocJl3T zgM1gfi~M(ZH~9d(7tnU5_y4`&4)XorF7jb;H~FD(5BU*rFS+j;s~kRd#QVvQg9pf` z!gcZ|@38U@lFvkZ0r|=BBJvZ$Zvxu$nS=?kUt1-CI2V9jr>V?JNeUagZz1T7x~NZZt^$b+OG8e-v)P( ze++k#e*t%scfdX5-^0D+yWl?ZU*Uf847@K0kPn3GFl@+I&V@-V!W{1SK@ z`6_rjc_rK+kHNdhHYt&a0mH4a2I(4+)e&ZxQF}++#kK<|3F?;N|2m!K36`;nn1A@HqL$@H+A@;q~N8@I96W@~;u!Nd7&%iTo#cg1iUb zLY|p?{z<+!yp4P)yq$a)+#o*`-bFqN-c3FZuBk7W%TMzC?|8U_`~B-x|NjPekoT{);#}koxSQMw_mF47z2w8;KJsk1pL`5FKt3L> zlb-+&lAj1KAU_FSL_QlHBA*Knlh1>flP`ov$(O*Z$xGmI@=M`$$cMsp@?r2Gxd&cAJ{n#`J{}$-KLH*lKM`I| zJ_{Zt55lX-3*d3`1@JoZ5WJpz1-yZLCA^V*HN1(u5}qKhg}0Dj4{s&E1>Qz}C%m2f zKDa^t2)v8@33xa8hc{ULUi&S*|F1{9gZx>zi~I$+oBSoXhrAW;C4U$0BmW5QC;t*2 zApaV!li!JXZ;-qT@df0&;6>!W!9(Qz@V-7wJ`i3`J{TS)-xpp@-g=XDp5o*OAij?L zV0b2QPmYZnRrB`FzA{ zzo+;AMQ{iCg>V=73b>m*0{4(#3ipz)gZs$cw_D}&lW)K}D?lDYK05jJ@E~~|yny^H z%#(}A->I|86C%GI`Gm>uftQmvz@y}k!K=yF!{g)|;dSIM!RyK2gg21C2X7?*7~Vww zB|Jg?4ZMZ?M|dmwukbeV{xw!Rx0CM)H^}#ecagi{-Q)_Sox4`4%x5Mkm?}OKqKMHRke*)e}J`C?qo5{1BtHY* zL_QCmAioCh`&!5sA-25%>izzy=t;9cYu@NV)LT+2xB&)2~n`3vwM`EES#Eg*jh@kQisz(eG1@G$wd&UkwzMlLicmw&b@J4d&T5J4iBHsg^Am1C_LOulEO74cY zkq?KrlV`&X^3m`v@}uG1a1Z$za4-4Ua3A@6xSxCh zJV1WLgVy=d$rmF&NWKhSKwbhbBAOcnkR!cq{qq@HX_>L$!EgL$@Ab*@*upLyZ|02FND{TFNW8XFNZgfm%tmzSHhdfSHTnH74R1F z7`&DIT6i1zE%0{oJKzTSz3?vbhv41hkHfVA>HU8L+(DjzyU4e|-Q+LBJ>*;AUh?LGUp75O_KH0q`jKaCkNOVemNl zXm}m@QSf^5W8e+set08!0NzAC6P_SH8Qwx3gtw9xz}v_R;qBy$;Rg9Kco%sIyqkO_ zT+2-F|Eu8+@(Q?%JO+1@UkCS)-wgMX-vRfL-wXGXKL`(y{{ya*{|g=@Uk@)J-v}=v zZ-$4+UxA0ox5CTG+u%|1kKxtiU%=z!U&HIjzlYb8?}Rsy{|s*=KN#OXZzAtOe1d!c z-kY?L?*(rq-w)nKeiY`h?c{F68|35vYK==>n|u^p+ataIpM?54$X~%a%SAp8 z`MAl)!#(6(m~VK=CnDZQ9)SDFPl5-?^Wi%ATzHWDTzCQbLUCP=hlj{Ng@?(%hL@9n508@n46i2FZnVbpIQc+$9k~-;PksQrfqVqKk$e=qi98eE zgG!K(MSKf+6W-&rl21T<8~GG?JNXQ_L4FFni~J0DH~BemZBTmuUkG=QFM+$rOW7m+^#50O6s50h_zmy|A6>b@}J>tk$4^0(k_@*P-5d&u8IyqElAxR3lRxS#wRc!2zSxK7>;50d{5FCg!a z?*SH(4}yothrq++`@_r0i<9#o@`DgxO@1gmPCgo5M}9QCp8QyN1NkI)BY6PcM6SaV zkzWe$CSL>B_D=8rBa-jG$SV-!{83`!{9FRBj9fGac~d$v2ZW>M7WQ9 z=bcu4{p3>-A0VFr*U9tXL2~U8EB^xWAmWS2&xVJ{3*lk%Vt6_EGI*3c0A-DtH(974UBI7+iCv_y6nR4)Qv< zi+mcMgSpA?LcE9k0l1g^VYrX{F}R<6Jv>03fa~O2;6d_N;RWPxz>CP+;34vMc$j<} zyqtVHJW9S3UQPZBJWj6R@1xa`XTs~r2g4i4_lGx<|Aymw z`4#X0`PFcp{5p7${APFo`Cs8h!~;UV%hc#ae%UxfH_^6C<+KSaqdLVPuOF8WWL{9?q{k(a^i$uEO9kgtO`l2^l< z$m8$?c^$lk{4RJa`2+Aa@<-wA*_|Kg3J^A>w`HpTYg)U&90B+u=I-*jpZrI}7m)t~FCx#t_pCzX9^A*n zntT{MPCgP|M?M-}PkuDKfxHFn(@1_i;+x0=@C5lu@D}pf@K*A( z;BDmd;qBxHB*%a9V#IfmFN1fJm%_FE()<5qa0mGna2NSia5wo-^m7mSay&Qll3#;- zeB?L4{p7d71LXB^o%{woZx52+kN5)eN8v@}N1&gF$R9_1n0!6FocvjMl>B*kHTg^M zIC(3)j=T+CPyR8yf&5E&Bl$P*Ci3s$3G!}u3;Aw%D|vt1*V@Pj!Q09Afg9xe!@J0b z!@J2x!L_03{eKMHLGFXQ$o+6P`Bb=vymO6p-}jQwM7)oDHr!8sDEeW5d@ka3^7-%} z`T6hy@{8a_=|BzQBzMA}6c%1wucpdrQ;PvExhc}Qv1aBmN z4BkY(9-bh74&Fk(8Qx0%61D!5hg#@FwywJVAabyoLMJ?^l^?GX83c$hp3UQRv?9wk2vUQIp*9w$ErUPpcc zyqx=csKctaP5Hf z{@;LkvV*)1@huTqkdZ2gx^FYW3#=^0yIR zME)T>ME(UlO#ThLocu?4l>9e%HF+l9)5Xd6g4dC|;PvEL@CNc>@J8~H@Fw!@crTD3 zUzPm#&&iKOKCR?FcpLd~@OE+y|G#Di`9CpUb&*d(KHcQGaLt|G|4)KD$nS5o`iYDD zFsy6bO#V;QM<;AC~HTkvhIQh-+I`Z4$_2hqtH;_LJZzO*L-bB6uo*>@{Zy|4i zx01gOZzF#f-cJ58+#vq~-bMZmyqkOl{yw92V0!=m0r3vk}2`JQkO`4G65 z+zt1UZ^QNFCm(_M0Qo4mPCgbMBtHgTKt2gxL_Q53B0m`(CO;isPJRwNO1=PIO@1Lf zPF@PHBfkt@PreS`KpulPlE>jq$-{6Tc^TYKemOiqeg#}7uZ9Q7YvBdtH^Ga@Z-a-( z>)~PY`{3o|kHVwmPr|FopN7ZDH^b}5Kg76KPu_y~2J+Y7jpT2_o5RaSczkryLAM1CPWOkM&nC%+UPC0_%tCchFM zC$ELqk>3cfC%+xuKz=X0k^EtJ6ZsSH1o;Md3wZ+GO1=f&M*b?io%~I>LH+@}i~Lh~ zH~Cj^ZFqYB{|4?L{}JvY{}t{g&%k}kLp~7hCEpwFBi|41CqED#ARhtO$+O`>^3m`D z@}uEJj5) zf8ZYSjc_k{Gu%i18r)C56&@gOgX`oU!Gq+V!wbke;6>!$!9(Ofz{BJ}!^_Dt?zQeO zQSyQCYVyJGIQdX`9r=OqdU6lEfqWFak^D$_6M6hHtNaOaAL3ickAt_8PlmUVPlLCU z=fMr~)8Jj?bK%|O=fJf?())iA+(BLpcadKRcavWX_mG#vz2vLmKJqKze)6l}0rFb7 zPJS~yNd7l?0r}tIMdS~`L*$Rc!{q;lmy`bo9wpxduO@GX$H`xT*O709*OM>8dc1-B zUBow%e*|wL{~Vqm?|`?Ee-CdZ{|Vkk{yV&#JoE3?b!U+81@9ss0`De20Iqq``~L{I zgFGAVA|C^Hljp!a)_SoF?gK(26!F$ZSZ>XyWtJw55ODAAB8uOUwApjfAW7JzJ+`Pyp_BI z>-0AA1mfGtUw|9rFT=aYPrKhLXE*s9h}RBH@Bi<>9poRuUF4s`-Q*o`5BYYumwYGO zNB%q9Pu~ANt6l-}8vMIqI{6^P2g#lA0&+LJi2M+Ei2QJPn0y?(ocuU=lzb|@ntTR4 zPM#01BR>OPPks)(fqVhHkvs%%A`in8TFw@(Oqxc?{l8{we;Rg+YEj;=9P} z;N9f6!?ls={r?`ggZx3bi~OH(H~CX=5BYb=zyC}AEaH9STi|~3SKtBix8XYZ$M7Ke zm+%7eZ{bDcJK-Vn-{E2M{uqDC$sO=0`9AP!@}clJ`9bhHau2+od=$KadIb_mN)(_mf`>50Kvo*U4{#2g&b*7m&Z1{QEED_aZ(-{t!G&{siU; z<>ZecK1%)+yqf%Jc$_=|uOr_AuP1*Q-a!5aypjAJcoX?Y@C5ni@D}nQ+Ow71Kztkd zxA1oIop6KvS9lkBKm5CY-Q*6qmYv@J_klae_lLX4hr`|EBjFx$FWgH$4(=mA7VamX z2oI1?h3n+g;6ZX7UO;{tyoh`*JVZVZ9wuJ|FDG9DkCI;uuO=^p$H~{g>&UC%_2f11 z2J#!=jpVyA|7;@v8{!k>_rP1o2jKqLO8yYy+sGR+&uM2zyg~jHyo>x9csKcGxHc-i z|Gx}(kiP|Yk-rCblYavDkZ*%~$-jg9$bW?U$$y3i$bX0Hza zc!+!iJWPHByqtV2JW4(uUQMp8v(`It@(Mi9sUx3=eCo-k!W+nEz#GX=hBuK1;R*7y z;4S3m!CT1}!Q03~@OJVQaD#j$yo-E3zTewTz8dk`;pzRq0`4HMfxF1Bhr7w^u&(!z z-->uI`R#BY`Am!(e)7K~K0y8;TqkdY2gz&De+tO|jrbz+XW=387vN#?SK#I3Tj5di zcj49KAHn0~pTg_Nzk=73e+O?M{{h}e{u{iByg&YaYl3_)cnkRucq_RJ<5e5^frxJ> z9|1SWN5Q+u$HKeGkA-VTr1$^Ha0mHxxQkqeyU9<7d&vI+_mUUEedLSbe)8q;0C^c) zCtn2*l2^hD$ZOz5!o$;a%ikz`Myi;hHzS|9=m6 zkpB#Kk?)4P$p<`Sofi-J-f%DZesCZ8L2y6$NO*wU3)jiV!Gq+-!VAbJ!;8ps;URJz z9wraM%gN7%N68D})#Mkzw&BzE)3OhWG~Z)$m60DtHt5HSh%aP4E`-zrtI| z?}E3H-v@6ee*|ujKMwCAZ-RG|KMU7Jr}zI|Yps6kAb%e5F7j94Zt{OsqyLk?iFhyh z`*0umCvZRcm+%1j*KnPDJ3L7KBfNn8S9lS*_OMm25cwcHe+ZL15MNI2f=9`-;ML>@ z!Q3wZ$EO0L7($n)Xt8Yi3U`rT3U`yQfqTd+;a>8q;Xd-~;C}L(;Q{i$!gca{;6d^S z;05H5!i&fshlj}j4G)vQiTQIm`F{`}CEpCMCO;hgEl%Eo_&V}e;q~Ni!5hdwfH#u2 z!<)#r!4u@)z+1?_hqsdd3~wXf4R0qOfPb&pAm0n#Mec%klOKcj)W)Ru{~h@Eg&gEr z$j3!K4DKdB6z(BE0`4Ur3-^(ahx^G-fCtC}aGm@lc#u3FUO;|2yomfPc!+#HJWRd- zUQT`iJW9R{UQJ#KkCWexzaLjeUXFN&Fa7)`(;o7@ORf8wmprgbGk+S6w({9SQn?nH ze>U3OZSy9ZPqTR)@>fT*d%_k!-4-9W#oN8b7JsZQzTOsZ_f}i{L|c5D%_rNu-R4tl z-iZ7&#R4{O8)vo8KryQ&t&QzAw<0VzY~J6NPnXRH*u2~3>e#8Dqk504&Q0>iVRKcc zovHuu_mFPr;pzPHW&HXm&BfX(-@xo&f(&4V^~*}TB!Lu_7T z^L=d|viW{C58HgG&C6|`W%H=b_qTbq%@447+~x<`yw2ta*}UH7!))GQ^Mh^PX!GGV zZ?gFan*Uw$JpF!^RYJf*?gSM{Wd?!<^h`@ZFAk`KAQ(^o@4U@n~%47 zkdC2AyY#z4xaW*fv`SCW7+T3sRYMY;6^SI3?*}Tr?Q*B;v^Am0U-(UaR0{?A+ z|F*z?Tj0Mf@ZT2rZwvgl1*{hM!8zg=z2X~(UX%IaX&IVcm5B7~Y1J#9cWgETdM4a6 zJ44&iGwNRXa2}$m_>kJNqvO+_o}RdwRtwl29dD%4YKgg{V`D0<7OXou9!sUwLUl*S z-Kn%%pzi3nK9yDr(;Xe_QfW0=-_db#DyYqxhg`U*^_hkJ&X8O}qS}p9P{;9ND&`JGMX|<4( z`lr%r0VnlOrPabs>Yqxh1)J1Al~xNisedZ17HCrcR9Y>}r2eV2T98ToQ)#sjllrI9 zY5^woPo>quOX{CWs|Adr2eV2S~yAlQ)%^pO6s3X%hmeoDe7vSp_$j~pQK;l z?Rn|(FHrg!NG_nNOX=B^o=NE`l%7E8 zag-iK>EV>lqV!-&XHxoiJ^lRbr1ZCx{*uxkQTlC4ze?#BDE$njpQQAol)jJBcToCf zO4m}liqfkoT}J6;lrE<9d`i!y^lVDcr1TU@PoVTTN{^!Sa7t%UdN8Fk?djaJbI;11 zn>%OjnR2Ig>#<0-qoOCndF^hw>}zN3u3gci$9{Ojc;nmr+EKS%t!eqSQ`CpPv3-EP z@yh{v?TkHKdd5yYHXyt6WalAK*-guhuE_|OEt8LaTG?WKVyCn67`ct>HJ7_KsM}}G zE@PvN{}o2Yl-)g==DcdG+FG&MrB6CI+gVv5A^A0>j+xG@h0eO%c-D#K9OKr_S0 z%>39_`LUmj<&uTWD2`Jhk!$i}yA}S$!WzAH%3vvG?tj$u!FW?94SLOtY?+DpjkDyA z+37NqGjqOBGDAdWCGHuq_IdMj&&{2mdtPqgX1#V?r|kWlnsDf`?~K=E$d=FN)D7F% zD2bfJb1HYecEXLdLi_xx$Y_@Mszh0-G2#^Ui?ixlGiJ)8F^TzFc959Q~+(tL}LavoACWwyrmoNx1DZ=9#cKGQ3{=nBr6nUlzQ zQA(xi-;?$7%na?v-gxo#C&@goHuG`yF|j+6B~!mG5~QYngL>N0KQ`l2;$vp~ge%m? z=-qm(OW*k2iTcLw{(8n1{f*s`p_0MP$iUIlzSz9)B==YI!|0URvaeP)X0Bc_WxPbn zx%x7)w_Y>lN=X=bKlk()=Sx|)WR8-!jQO&!X5YVKrEElbRDKmb{UQS^dYqAdo8#u( zwKi*Fm3fj+kq?}5W29s&hfwu5ZjtleoO!T1wEUQ}TU&l+9N|Fdp${~Fmz z?a*?4=b;KN?yigl8;HCBI^VYOZ{0V3+5LRI zX22*l3Djei*GGSi@@lzK^J2eAX`Gd7<8s|TV<9fzi*aeRazSrU zjV`5sMmnfbAk8ahRSjGXDm1p;m=I9AtTJQQo3XJ?&#HztI;ENOC1Srni;%rF<3%&# zC*v`BUT)^`thAc8LwAmCHO9%I@?$;5&8ckz)Hbzg>rN?A>=R={a+4G@b?8i$ao9Y= zle3Lm|13l;@{HMLh?Ltn#_W>vTWqs&mHb}uYjLDRdgDB)u~huAb(&W3>wb}y(lD-R z8FG(1Kigw0m)$q0YOYr=QOy&yF~2r*rvyK%M7r)NyHpK6lPvxsiVlrV-1DQ^ts8RX zGANa-I&%`tu5^Qxn! z->uB+E7oi@$qNe^Z0}SjZk7D5$2OaX&95nU&6bO(D!+EZHFDW>?z1>PyXHh8XXSD^ zsp>?ic-hxbzsHZ1HtEh-^Fhuom3&N+@@bxS)xGbQv>Zb9>s?dKy&hK`;cM&n`_(R| zs*vUZ+0ufNQ$Oc*zsN6g9#ywsZy#-0DEF_su?I_0<#2F16jb zK#G`*NOdW*uMU?8Rf(B@7NNSW@vC`)hGv`l=QStP#b6ym^<3jabF&=W*rX1oieb2= zi*y_=amG|xiEU5^GOyjvaj|!#m10A)jk%+xN68754b6rwFR0X>4qXO_r)5jc(Cms$ z83_GF1}1@tt!p=$K}Mb|UlA@N!zm-A32u|-Fus?fnD>+9_$H&4ImPozi;q1q!#>Q- z$aYI(N#9sIyXuR`WjIBSoV{9u`q~*G~BSUeyP; z>#a)()**Q^LtoYK_M%(3ZtE1-FGsyJszfH6gu>eBlEn39pa z3|z@JzClI-bw_w#_LR$DM1E|C6m_n31Y@Y2^^Sj-ojxkv#4NEIxXrGYJPF2mvTvtT zT^E=juSVaH@lV=EPKmSX2HD$q5{>6lt^2&OzbdWtys1)4^X9e1n4a9zYTh5ESyaTq zy%C3|B2w+E=6F?6zZQwA*tlHxGEkV;@={#O2TR|UPIQPGo9gAcQR=!@Hy7hODMv?t zGjzK#L>|-R#+s`VYqsWmkerXkK9)TzHfI=z$=1{rv%fsSK)GwwUK2kld#CeuW4k(+ zf4tw%m62lcDpj_fm_As(d3+zH<)Jv?eulnDbh5KBMMq^|G5>@M^5h z`lsY%*3?*LWw5`>;L|S2tad`Y-fTN#UsXaW(tK5vQC9Izmqs<-li|yp4wQ{Yb7j?7 zMGML?)|^B7JBLhn$$uXC&)0E@ z^j&j(GF#oiLS|3?J+HPrLyzTWJMz`k?fq|hdgPijuZmeJKn8QYM%LWY?s8)`%B3%z zB`umSw?Vl(nkVp#WLr(&GGKsaJRn&}Hw~%+4U+U^8sZ>TzhPsl?)b)l)d z+3{}c6rU#hb-XEqNbfbFZtdHRcSfq6&Hntl+WByE=g4Mt4?jVEm*)6dnnN~8l+i7v zla6wx=trU|=u4AiLM$Co_;v7W7FVrQ^{mZ$*5qAQSNyEgtVE5>W6b(?oFPHx6|G7% z!z|Gjvy~>A&Dd;cW~rQ&qb02t`YyE^mxJA*%J7rS>rP8m!&x~1JI#|@t~wV#s$1f- z=1h7@wvWnZQ$Ut8`nC-W^|OCq*eU3_En zYXkCJVyK*Xd6v;^%r?)obPySKUR34$M)gIR6<5kWs=TYr&!3Dr`^#ABk~*eZu%8_I z*?!VTr8(7E+u<=QZO-<#Nz19dr^-!MHO{+8pDJlJ-ERS4_Ly>!pGjLM}q z&&WPkPJEC0C8TEP&6U}=%T9aBfAVWwJ~Wq0Gpbbk$1+Dq_GT9|r}wp^)|RQdWi=-+ zx15CBM-9JKmQUq)#v`(0=V86qkCgE}@n;R0JQycRpfqRoRdSS?%pu}(1%4zM-Y*%d z=SET>Rk>}3S-H5%aGokj&6Jze@2|`LazKR1ez&Oonlnpfqxv!aVeNRf3LYst%HU9Q zQ?_a{nOO}rm#k$avNlkyDo?T0{i-(WX4TlW6E;L_!w3RstVW-L!VfXvxlaTT0rb8oJ|h`NOzUHd*7Xy8q18WAdXm}h=+tWm}k6p!ktpy0lI^@U zEwlL@DdbLE=f#|a^QocE+LG2lboZc@BWh>3O%C&gEXMTFU+Yy{*Nhj+n0)Rf2UZvhC-1lNFy~W=yhQ^l z@-y0|swaffo|6x~WN>Zp<2>iR?>Mg*r4AW;R~5%=zBbh>e#u<(tzNsjpD{@Wo7`BU zHmYU!^i1iI%kYx7awX+O%6Zj+vbSD4cMmj2MR(?!$D^zFIJfMP=<0!GW%o-q4&z4| z0b`qNxvac)Ml7-JGnL_b^*(oF`<|n;-<;Z@*4jq(mFf)x1e;`*(v#by77p^Hx_frU z>#?8mDz<0H6U;p-ww+kxEs=YE^ zmFKG+uS!MCjf0aLJ3lkiYV0zDJGYu?RT?Y3$xJ&^>Bm*t+Vnu~mJBU#%)!}u?evV? z=9zAnM9q%PmD4HXq&Z*g*d%qH5!;em(YXC{0xWjzct*_hNGf>l39%Q*H zZj)N>=30C2CBLeWp574E4o&T5h>GqoWUCqCk_Y_iM4366FRqi@`jPq|!o zW4}W_#)tiHypvk>Sp#Ciizf z70Hx5f2le4v5Cj7k9;w^COccQD|_134tX-OmHc)^MyOfKvr++R!%vctpQ^QfR7xYO z;Z#f6R&2fFrQB0wDE!HML#4)(N}077>cWnFtsWU1oa|4N2d?zxRlJbtd}^8uM4K`y z6A_;ba|f^7fAXF(eCt0(f6lmMpUH<@@_y%ss=Z}gdMg<#`$>FRs|-@7)@C1>R~yOh zZ#*p*iOdzvSwf*Xl9Xr6u3eQaz3tc8@))b1`QY^m*|J3&sY;e#J8qKPuH;==w%kS~ zzfqm5dfRvMxsk)F`%7D>5m1ekdTpU9$vjMk zkM5Bx50O6yUUGVFZJ>MdK`ZmD0kbkcqum@Z`E4Kd%*AcHUF63)%o(Ga>BtR7Z{8$br^lH4smw&w9Y_k0*%1`Nd6L{mb_CMW zB^|!B^x_UrT6%ehD=i)A(9+TiJGx|ONcLx5#=*^J#>P>yLbgsu`V)$qzE(h#*p^6+h2Pu6o|WK508 z9XJsgVjV}#ixouY&|ED zTk)I6dEL+IGN}5(S^2X3V$xO5%5O3)n^j^y>eUwwxI)ipl{4PkpJXliqSSmw%`EQ@ zr)!o^O?>4MjixQ05j$V5JW0ypPwv5^^RqqbI$XZkc~ruAb*0?CesHcC{7R{WI=!un zopWRoa8#B`JFi+STP5cYcH}ybY!lw>yn1@3Y^a(df0~cR_1eMnBy__;l6Ou5i;iu^ zaA~gG*bDML_CV*A$4D+xHudaTw>|m$RPvC+|0!thy5FLbY1s>U z%~~mOPvk$wtdC_7ify;f!&in^hGl7ZbsFmRno^IP3cLm#A&2k0PF>f}s(YDwxbo}I z60FuZB|TEKXNrZJwZ#jTmP!MK26PmPn(i=2n#(kv#a#qFc0idc{naj4CtT8EPCpr$Y_&)_tI2wHmRu>j&=AE4K7EQwM$^ zGs1qx`|8DrY9RIft}WWw88!1|(h$s;S@l80u_chlyRq4Z;_Z6U<9& zn=wKLN44wzs!}zpHFdi7(`)9-*8x5;uK!RlW3wKc@5(b@JeiLH%q6KR`@-aL-;+#o zH_Vr!S`8;BMkck(+!^KsI` z8&|3~H)`kKWx(2^oiL+jX7)_!t^=e!2dOfk{XRBdq&h3#k+44;%`k=~4|Rj&VYah8jgAbI6RB>0HD%dy%@5qtZ%>u7okeLFIg!pv zweXe3sr=16gJtsVkL^Z_Jn%D1f1+8e*>1h+#mE6U=KN&K0M{VBMo7)eWZJUbYPgKq z(mQ*cRhuOnbEsDNM3edaB-PAaKXRnpBLn@^$mHmFNCMS$C*zaHyk?$}uCHyGk)5sO zE&50`rK;T2WDZYC4l`v|lAW{F9DY*km0agjdk?MNTRMy?&zoi2^^CSH$)2QIx?{EM z+B;s#3D{G03acYKtJDQ*cFC%(5m}4PbnCTYb)guW)jJFG{LB6|v$LfM#^@ER-5FAb z7j5}z(j3xkYP};6W9xfTfZ93PQlbgJEHgLD$SOME$`;aPLSWu&33DyJVWx3 zldBrf<&dx=Hu$kmTHjEqE9DJCN9~SyCX{lo74VhnQ zDRWS{5ArS0IeI|0bF?f?)E^%8hp#8=wCoHi^NPW<ycQm<7Pyo}VC>gLM30dszwZ_R-tuS;Xfy{{+h zM#;8wW2(Q(9JVKGr5L98ud9sZ@W;v%KXa0=(z5#5q&sJ|8mG2N5mi90dhV;&OwR7f znkGAUER$|)xKs~OL)Is%ugOHC_o0GZ^kwpdT-Mr=Wojxj@!d#q)r-!`T8XTg=2o}p zb+2l2wst+=ugoc{A2s$x_UOs_Vx+V}&0JZ^Eb`BYT_l(O*2pBi=46M}Q{^f+M2{V) z$4-&Q)#o_WoX5IX$vr$OdCOcN&E(w7w^o~aY@W<6&E-kwhrQR2w1-hGS2>y^Q8V2t z+?PEv>~@?d)wJ3*IiFI`m*iyCs$%xfTq7Up&sVJXA2RtqQ#Nf--_uev+ikh)WhJXw zD_868Sv$D#LUjtiaa_2svG|>y9(5|uQP0eJ3n%^8Jf3Rfr)7V=W}xaF$H*d8s-M|- zs1!olXP!PO^DU439?QBK|D_l4Ca#^8FX(8siMf&ON=+wRX90Y4Swd=6l+h(%$xX%v*LBxB^e!- zXyaLlR8e24Lr(m>bYfoZoZ<5PtDh|FD)XfC*Xpg&p9eXwoGX{!|)_Jnxt{+uxY0Nx3wJH#KiIzoLliPPfXW$M()Hsj{kJ&QNP7e0YHrY?N7r zy;T*e7Rf2fZ9d7Zx~MgL>#^Cc&bWEp4cO~`*~@%qWwyXqTdfw*)sj%T5#wsRy0cpcGF_h2sf)T&RGpZa?!0x~+tf_#s)4d4PacBm zv6=4Z370#o)OQiB$CmQSf4<9j&3qF#)0H>zdFPeGWS`vVs!VOQv+_#$U6(JD=6s+U zE;iHE@tkC?ZavN+@{%qfZ|DN@hA!YUCq3^;iSuHzRF8$!Jzpjda_62j)8VXmQ1(*0 zt9!=+^Hs}tvQ{@vlIPp%yk2c?-fo;^mSeg{p1XL2WEXijq%x9BN<%V!YSGF4PAEIi z%viEBvU>A6Sq3&4M5d`j>^5KTU_YPQ&-v7Vev`*KtFDqFJ{ORz^I{+M%=K8Whn!VM zN(-4)_)v1OZZXDRWR}+Px}?qfl-v~6qdqNf^oKbg=C!mjNvLgk}Wre?Z&M_ z=4~l&;tS5osGM<`Rb<{>ELVe+Z*A!TG7TC0SCy25L}gn`=>YYeuH4vJ-u%&qWMOp& z)j8C2D_Ld^ImxGI%yjk4^>z%9GNk&4I{R`ZIjbf~&K;-7AJ+Xq9o~L%$oJXJi^H5) z|J3_{qoYRNU`Rhr>t}bVsdB2H&6Drvs)?@oc--n|i_MoZ=x0wz3C(^sU%qLlTFu_i zIzv`@{=BP27N)wHxiXWs+e>9FufC*L1!QKadeyP2m&>Lu^@0Vx$XPK$X{tw*P5DD_ zD6295%8cAOJMWezRZaJzVwzXNV}g_CJF8wbQPx#?GUIW~jxBceo&}Ft zFIhZxTq)7UQ?06Z&6ICYebM>O;H#+1F3yamVJ}EePFeVx__$9QMpJS4V#@~ zKh-%_tEB8_c8-!gW9sR}kXgRXap@)r^H(8t-!kv#y>rO7aR-v;3ARTletCy8dO&vS z*@xUFiw;ubkesaM%=!4WcY0|~n%-Ai)zi>7bZ@KGU^UtA8FM6FjudTueHc?lPt4)%2&DuIImRk z(G#~jtGd)LYcrm9R;l|%bZx)qoK+vnZ>kyoq&TMH-b&vKT5` zq$aa6Ia3qJ{!)Dz(K=+Enuo-;b;zq3`PCZ#b9y=slw|MqF7Li&?9`lyE>jQ3t{#z zc?Y<)<3=;mOq&zLj%h#0fz9vFN%RCsnE9CfagI9u=8Pkl)(_=T-k+>vq>ok0S9dji zk$iXQ_uQC#JLr?1tkMHiHy$-2mNxj@ZcpxRM-gGYU;2YPan=U}a) zPHhgBIk(85A}_(@nlK+T$h>Qg`N(ak^-A~vIjKQ4Cd&8OW6eETJ^N=!2kCszcuiW( z*0{Ld)cQ=-@PDUnTgzvwr;56&LA_)v7tn+1W6@SUcC1-ihbq6i80Go0Kys<3E{}bs z_~!fP&OKB8K`N0}_Dm^z!DjRPs(zO||A&|_ie&P=zqGlyUbC-xvz1pGKdN!Ve!i=A zmHYHXyVMcA^6p1HM~fV)+Wla8KVBm}{yTHgw~u()cC6#<`%CY5@+;f} zdQkkP^e4&Fd2>Pv?2za4Qi>VY_aD@7b5lZ}_>=RdO^(*GvFi9oB+Gwz+Tu!DM9%4{ zGQlo!NCtsaJ@nYhb&Sn7D|Rcm*ACWP{-`TJ%uHGfyD zZ+ZJAvRe{>D z$hS^+YjRZQkRo{k9l*P&fHj+Mmi*;N6JL;bO0u$4E2dmkX=^p6o-Ga6V{)s~rPkBT zC8fNJoTt9cChaF*aa8S@6kB`DbRmOSKpMn4Mk+Ejo}jvj)p|y)jLk)| zv+^2Aq9xwjPg=ry>3!z^hlZFZ-|&#Tea?$=J$6j#!g-q~UoTYGXR_brZ0&r<-rrZM zPE#UpM&(WD_v(AKC#hGMsz2|q%kw2^)0t-LHJ@ZPzI;Iroy58iOKV1@igKOoq4tw$ z?^)8^LHTc%tJC&8OONF`I@kA(w^AP40Ackfxt{#Eo{q_v>q)-rR<>W~{^t3bZCy`7 zkxcV?%8za7INls<&Erdca!NZplGnR=e$@4IueyHZ{K){-ah2Jd&3Lt4y^JwR)f6qZ zeZJfXJGRNx2=z&AUu4_foW5Q4ctef%$^4H<&;JkG2c~bA`i4{sFG8i~nWrJvqPp(c z>b#uM+g`Tuv{Ub9nQ)s+0h(GYL1g0AV(UXudZF$K0uF2&zktN^hZ@U zsfK*&gj_2yY-H#P2%Z!cN}l2b2bwO!w*JLGFAa=(&qovd|8wlZR% z)m)uTsq5R^+14JaKl~r}Pqpq$&71zbe`0KS#%Yd{k%O#(A>e8bn9Eyhm1(XpRVPz7 z5o>;<%9A@cFSgTq1Zq4Yhst>|Uo8OCx7cL@X-t$wpfuB9nRS_y7PB8XP2HBXNP-L_KA!+oF5SL|Kmp+BCntIWp%k^NFF(R)4OyuBdj zw0CRo=H%b+bDIT}h0cs@M^DxTgRNFOPL@UjYP~e)Ec5Zji_Rgs+-`NB^fPtE$Q@Wx z9@#PAE7`9(pr+DmrN_CG*ST6;dCYx}ZYkMU0%WJC8DQN9)G|C4^kNrlKJ~xYd-wP# zi?jcKb3h`{Y*19Jv|ZX*!J;M}Dky3KiP>O)2zW$WHpwPg*zB%*$bn)B29#|`OI!Mg z)wZ;?t*t&SZ9Sm1NYDaOwTOp$;Hgw?-3Tgbi>Szc-=Dc>b0XUI`+Q%o-yc8GozL8J z&&++zHP>8o%{6o1=`WvG9p2iT%&Eqq<@0~&o8ES2<}2&%FX7P=)$}npjOz7wPf`yc z`|1JOBv(bhUPxVQ_R=vJr8th(P;zRgdCqvU{PpwPJ)9IX_FzByy>wXH;Z1m&6$}*~ z)k|vc__XyM)4v`4COp$$T61Q`|465&KAjo=GL6&@gxs6F+~c&RXnrrflBa&T zvL#Mgx*1kDUaAi*f8G$~WnA;@4=;aMf4no|!}_CrP=CDk7daIEb${qQ=zq{37jh8u zBl}|o_4Reu;p3-Q|IreON`HOq8Q2C1(SM|me~w{Kl|$;|*AS#$x-#E*@y_l#yd zRd#H_Kg3#p=9Jgtr#OT4!MCQlPcFNQ^Ue5oRP6Mb%;irht2c>f;qp8(C!VNe;btsR zj(SbgxXpOs7!1Z=u%2GW>wp35OmwARdeDroe?jk&|1rI9Pjg=>dcVPzH2 z9Moh?{JEF6pFNzn>Q=+wY6Rt9@mE^Zp*Ow+S@>_PW=m^RMixq;6=UAfw*%-ssj_pb zq4rqI*kVz;YcRFX_EL+fJ5Ul>5>OmbL^N?klgH=ou$*rWH@%u-}(ruN_KmbZ*k!9@v5#;az(RNX7dt9W%5l< zX-;$B-Nv!-zb51#70j3ZF*mli)+y6k06w!0GNyYJrIaiC=Bh09(%sO#pg>U76@SuGRg z&s#3PKCK$palq4Z949q@Ux`lWJ*J6VN*}XfYZi8z@6KyiC3{WbXNXEWiH_&kbtAGr z{&?!9d(7Bgv8mL(<{@)*1bOk^JG#InYq_AS`XD=akH>B^b*v$Yhr7Qn%>bQ=Z&r1- z;E~xM|6*llGeJ(S4>uvF|BvA}Q?6t(hb{*|(Et!+PMq!#-(TA$~L_?L?0&AiQ=Tj5Q z%`5z9PE!&3Q0i^U9`T^(K5`*%R~H$f;|9l_jyVcebI@sM!wP~?x$-VeIWTwpZNNKYx=|Sm)eganKKV#J1fSXC^Ns~ zKtaM+)fx6!$+?761@UT~V_Aof$-Hd$Q0hBG*`#jq&ghzVqN{SsQ!RN_St)#=&7bE; zfKD%RMEQ+W=uUBtePmW}y-K{DF2zphd!1GGllZ*=AG0Qpmoh@*mODK!HQDf=7~9YT zt1|xMe$xTx=`Sr!KBfiJ2Xcl!siVo7X!lP$K0A@`?ObfmV+>3E4PNPyud(s3DN3_X z{EX6Nb%kSfFsCf}mTkc;e%Nc7nOJ)&CwS-(F2I+ z2t3z|Af9{!_2JI=ak_t$#&)Gvc)z`80#E$fdEn^see{ja%eaSDL=&ZaTNpadj?&fIOzJ+LDy#vx;}5vb@`y{szKM64!ZuecOX5p2i>1P==$e_ z@?SRSe(j*^@SyAXpzCFWuDb?Z-!$lY-Jt8A4Z8ke`M~zv)c@MZ0|JMzvpc7X7tQu-sGFT3VqpLuQ3+cXszO~LQz#$Sz&|^De(Skzs;=W zg_FtU>>Ii7#u#YBmf4ca8P449R|>j!k3I4LX=xK4>V75f$OFj>4^~dum*D-Yjhh&p z$W`Zz$(>P(Y|c$?D@|_e?io?qz3CI(o7`m_=TGh{-qtzeV9pE=iTPREl5cjup40vC z@Ur6W;y1aRHkAW;iTky8nuj;M^!~M{eQg?FRdX-qY*bNM=jFL&oz;0|o!(KM-h$4? zF?^A&p!4#voh9SS*t#E6#vb6&OwZzs1Twc~n{vF} z+m5c%JD&Tz#oNesz?(Cpq_TTko;Pb7GS4f9!@Z9`TvgodEq>D^O>31@{W2p}mG^)4 zmlOVMCF?A`*xXrE)p>bdRcAG4-@IelT<~~18z<V=ExxJEe z6y4j8qB;4M<6rX@?;y?Fz4sucEOp%E_gS1dn$j4`{!kd;2(V_tsDI zn$yhWE^qOBq|F@vWMx)2smFVZ|6G-%C>6X_Rs5ni>pkkI>^M;EUZDkmw(t-jnnv$) z$D16k>#^RfeQbKM0fyl5!YOgGAaPa7QUP079af3O6|hG$pNX^4Rz~ZY!;`#ZL)^4cCd^=k-VDQ{a18P z-jUl#9&SQX#vZvXS#hvx%9g}qj$JSP$Byd}X5Yg-dN}b}EO}?MCuHi^eVvJ{Og^1) zi`td+eoQ*E-&xf)HOc|m)2CwttwyNjW_Y10`B&#$tn&hxc3mIL*6U=c8MkBAOnE=? z6faSQ%zWOjVUGXC96gpT@c~PPw`EVH*5EehrKpR#II%rtQ{o(*%)Y2HYZq_FZJfrZ zr^Zo_)UZS8lld7tX&*UR+Q9nLc8JZb#)iyS4lL_^aNo-0z@j*uviC zzG1r}+CbL3S>_cqXNlkSsf!t7I>^w@4OqIl)O~l^QE7C5cgoYvqj*1f)KR6~f6Xg9 zavQIlHXlhs)lpeol_tAOyL*nRAj8NDSB@I)T#qX4`0E8Wt!j8y>AmNvR%$A6?jJGW zes<>mW6gOTyD!kga-_h$UDI+@dDy|sxtN#r<%@lFTV-) zx$oYE=OBCf%5k}BMt=L!ob%#&(^uw>GB1mFP19^Dr$TNev#NF(J9bhGM!Gk;%et<3 zfN8e0{e9jmYb3d=^aoRkHks&-;@6nFrykjSJg-51!M%Jdsl-kz!!&T0|Ea|Noj;Vh zH&$eocU}GfCkdQdjv^BYKX9)2z=5uIl)^MpsA?(|eVx>)rfZQ_Q_Tl7o&;OzNfhU5N5_Lsokyw8D@~nc$+scx1-^Inytv_| zwj63JDeG!{09vU|Z_r7`NTE7i?RKu9QT2UrWV5O7JyJ#E+mQFd>XPLgxVn*~((c`( zuuEP#qI>sIWk+tBiMQp*XUdLzCfVKHQ!stys8KY+eZ$q5S2L5{JqtmjKT11Nr9Gdd zBdr{F^pqCA*0Yh@&baI7so+{4qH?auU(xfbDc#OfT^)N$2b}LJB|$VT51Kkz5A!Vo z?dojnr$w-*I`&kU*Fec%G2pzwYxkZ{wLMeKBT6{JZ!TNSCHl1VcjkJSshw-*@~6G@ z8GYz{-PW6Y3f0uw&k*DrAq;Q^GpBQS2}-AP+$%J|`EZh11d9LCwnod!qAaOQ{_Vc; zq?#|HyutfQCE4;P;#90Eyg2pMlZ?`dUoj|8PO8qilL$+tbbUf^)^JwM>bh5)n>cSc zH}uXkIsw&)sbf!XO^0Ev83%$>d_UgezT&r0oYP-=kMuy6dqq3E*z-Hve@1ie`R~-r zKUJ67{l32M{Vvr{B>Mdy zG6y_WU6cMver`tJ4oyAhpR<3Wj|uVM$?$)&Y94 z4$ZYGH~HAg#zWn&k6r0am#mB&Og`5AdI1r=dy{w;Uh<(9akZ~s7h%GR`~3z2-($3t zU6A?>Ct`T2m~b2qHd`?qrHfF)rK7AR3aS+>=sAZjxAS81Eh8duvgcVwfz$ujrCz3; znf{l*X+~d9au{QQDwToMuLm)a`gI^iQosJI(7P2|D;iboLGuW#lXhL1`B!3d0~Z

zTXILcrtFKpE&58}g>&jFRmIUF&UW>lcZ~Bnq-fR#37>Ix=z}dChbALl$9Q-JtQ&oq z?Cv$ben+f{zKk?0}i+0wF! z!)M+k7VUF=_ERS-5zGc;y1QrW${7bcy$6{uR?hf<$jXI>6wbgL+?{yZ%V$$)90V9u z(ecWGOjmd(e$X{D(%qAz?=Sk;!efX zD`%~%=lRSKFrUPbAoTNwWyXUQgJpZ=VD)y4Dr8${uQOJA51B77Y~`j^i9Zm@#&7I= ze~y=h&iC_{%@2MkhMI4%Ev-zxtc{vgY_Q?hphK=IjO-~`&D+6Be#%L{_&)PU`wa%n z@YExw3Vm@W!8u{{AJ<5}%9P@GlNJ#neOlyGXpxnqSdmwm)Vj>L-;vr&ykC$vC4`V=^Tf*dTU8x5!Y2Fbsp0LZc&EZ0n%7z64n(~bNEYV?iaWx z&MS<#C*D%n4rT6%w-&Aj9evkc^hK$O_S3I2*5j0IiPvU!U0b5Bm0j~aO&yNY`$njv zPPC%lz36Hs13slNE+RrSIiZy4=n3x85tdV}9ONh|O8x2k>Q82ACYX0p-3Qba@1%~T z-Uk*yLCQ_c>2LS)vz#YDtvQ3fl^P+Ma1xy0K?NICgs5%BXpZ@WotEf0txnSF^sIYy zV+k}=r{?lBb>_{tWIpqyZt3lJ_ln23r0>;x);KU9tcT64q3y)@Sn7@+Xft6G3;7Kt zHgz7A1ok&|!k9V>GN#T8RFyF(!YSX)ynGRF2N(L6 zS+5N_Um%CFf7WrR&DuXb8rcivU+)|(HyL9PtLW*Kkwf^UCSx~Y`QX8MnyVtB?>f;5 zL|I@f)kE}sCpwnsK__YrravWaqcc%;>egP7Ph5$(tNZ&IOuhEf($sa|H3LS3zQbv* zd-KVucYohu7V62(%o~i2Ui;`)gwin)zMXmq<_wg#O1)lk2@ld!hHsB zEi|Y0C*D!0oh|poJI$LK?uqvo7U}vkC9d|f(XU&x+KbSyxaF{A(XSiZDJ>P7zg9eV zULCxH54a!MsFX_x>Tsow4;ElLLJrPSa2Ohbw6pyY(D_)LPT!2<=tVS~~ zKs!fLXWgU+OEZgYe}3O|eJXq-nt2mXOeKhA2;(@vp5t(*S2_3c#^K3E6(g6!86uW7 zmPE0KM29~__*g-PPH~D9zN3#GH`H>41C{V9-WeN2xqEbbJ5O?tUb))9>U9Rzs((^$ z+m4_8S4QVc)cOVh`tPA?Qu^t8@!(IXr&&(&yh2Sid=ii_Zf9FwTC3AiA*iK7TvO?i z!bs2BjNaYz-PCsau+RQ8_HoBv-U{nAeOSsFX^+p{q}EU1#I-tI9~kfeK%^)ADFzCljyt?$eYBeGN>3h{>&L5#}1RepCCsxYdk!< z188f-Mc!4K_MZ2`XJG4%{S20$!>P9Y^6(f}m&#`*|28xEkCpQZ-R(;X-F#2)5Fbf(ueg(j z*m)A4pFZ2Db*eEN-YCVvg@(*ZUAJFb-J5laXE;yeYlQcCC@1%H0n;+C&&}u>b+&AH z)(mSf34gvmlh(-8&6m{ZbSvv`q|RW74H_Gt)Y!0g`at=A zI#t%6p8odEHrn4$;FlL}Ac1+U^M0edjXAy=X<)iCR?l+cGgt7f;6Ivgpp_@LV8!$8 zA@}#Y^R~HH_|0wB=T{La-M{q$_lh~D^gsCa3GZ(3LKGh^j`LRwPX87Skb!tQ_s!z_ zIkO$aC#>&*=f)7I;sbb*+lE)U%eGZ^zi|YdaW8LBb=|KV;mzUWlQ=A-I{EA&yF^zN zdPICyY1fx9q#0aQ7!gGbB8i|!LN|7%KJ5L5w{uKk@(5O`sip(wgvyGaP|iqtk&*fm zh9r@BM*Qg_gt}bfJ+pw|5)Z-EMReHt_*0VmtS!tY@ZU9Ae9}~zxYXJIt?V4LrLuV5 zA6WM>y)o_*uaz?2=?n4?$uAV)75Dhnd>OFB+relmaqJ(Wd}m7|SFglQh&Lw>xVQD3 zWh9snM!d4x8Wyr))Z{+>D3Ml|_LFB*C9SD5%Y{1%+quov%W<El}e5)H8t$l(DCq5+tjNsX<5mi zGy7BO1rs&j+3L(z_MehnzL=^h+Y|f|u4nEYPeO)bI-|-MiVOp9*P6n1?$L^M0H)lk z!ghmej2NYI4b_PWL(!MwB`o|6qdH#0dwecJj9p*)J4YWcH{#N#lKsf9B&sa=Oh$z9 zt(Ca$8^_a1J99^uPRTtgdQ$KF?!H0#UfY{E%nrJjpUqV!TkJ)ez~Y0JiX*;S_o&b?``s zva|J487j_ritgbP!P*KKdOrbH_U$Ja9`5;{)I(qIn-6;~gDd^~xtaYyGe>&+O-8u! z?V|guh=jaJ+9!xA-gP009KY{@0{0C*lYl>7r_PeQ(A}v7N9((${tq(a>V7|aOF0{Q zx#!;ZV_wMp-AK~C?q6-5_@3t6AgUI+7i-ty%7S0RD{O3Rw`|T70-?xxYF77bb#CM00;Qw93iG6DK~Kd|JCDt|ty9pY5e3L&x$~xXRO$V+vEvoqWUGBf7fITFf70sZY)u z290C*@ZyqTOe|+Uo%-!6?3yVYU2uQn5(;WP%$xsx6N%)^n;C+5ew>bcF7-a;`Z@fJ zy!E#y^txidE?mwhDV)_Ts{bBQlst5wK6;J4uD|Is4odl}6vuzg=oa1J&Q>k5Pw!H5 z%wH|2`suBCjQ}vkBetVrTFWAG`EKum051^M}fkJ>Dk{@XA7I$IDrz@9f$$ z%F(+{RvpGrd-{gsG3YBbxpzdGk0zy_qFKHArJsEBHaL1lV$7Cc$Ghwtn?1q5)9lPr zLV7>r{QbUR$2+N?CdH*ztm7?O7QeGn?QxdHw=2trrjWlW917Ix^#C2StuB2)!X@i> zA!W@&btdmgWE9rRNhu__=@8yTV8}BH9 z)bU?q|L-SuoS4U?>U#;|JIuYrI5dbP;~h8ru=?uejD0HknG1P`edxm23cob%*JtM3 z%YUS}8TM~7?2FttAO^_FdzH!E8_`GHJMSqE^YnLlr4=34jKa^=dX%I1W=C7YF+q;`828@npNU%EYn?}buja^~lA8_m+seYUcX8Qxn2?{2@ z>)kr7QPGZWy9=`H+PXB?eD8~LrOAKj%`Dj=9KI=GGVyx|nu=B~FKkyXR_xe3K~64q zIYuD8P5+kOdm?v@pPX6TSOM;B*iy?#P04@ASN&u(^l%M3GsYG>h~n-lV{`FGYT)Tb z7xO&6H8uKZ@M;)6zaZ6txM|K<@e4f!Chw#*CgCO$%!I?v#XG4zndIw~uj?z=F#J6v zwsX`&*L0TGl_hI~BC-UMJsZc93(Gn=y)LzkuQ9WGS_0A2*3M;(eVtWV`2;*5j7`Bk&kL+$-K zlTBXRldtfYO14&Yj7RHa7E8RC?_RF&xiKvpaek0E&%Ho@{Pb_`(JZOMOW{;ofg+gT zZR)e7MMR1eDIrp#NHvjaMHUcQfIS}0;fI-^lTB|BovVWLRj5`47U7r4EhG|KP}+Is zFSUv<(0+%?cOjeah{zUUP7N+cNStohdirF#m`J`UQ-u8@Uz%Vo4TuSq$ zmzS^alXvnQK8tspSAqt2#XoJ1x(u3ZzM{#-*m)0RF$JWWS4bxfb5lZVLfk@g%*Vm* z`-I`3J5AoSnNUp(2lgSQdO3P+``APu<~#iyF?BTfH$ zmY6B8Cjq1;LVB+~ss5$#cgQG5lt-E~b)3lAI&6t8Ed>d&?W$>@d065y<+b8GZ;q|v z6EX(#dR{UNVyyQs;hb03`!7;~6#5i}28fi`h*bP|`3LyBOK+(%JSl%qb#$1Peun!4 zRnj?-SFIO(4SkHvj21MEA6gG0Z}W@x+@1O~4ed9%TY0ziMWF9mM0fyw=739j2>PZk z?@yl$SSv!apfB=o(Fd*g7767q(;vbzI@{3d9$m|bSX8~|N2!H8s81f8`2>DC`;L&j zosF!1Brh+#jc#+e`XzDo3bAw^1H?In!^)MFlSa;^8c1K#K&sbes2a9wsaT>h#HhmO ztT7bPhM{=vp6IxmQ!B4CP3I4&Xnn57^|gK~T4+5fnePG6x-&IPvD7#H;~sqmSGX(O zqrW0QD-I0z=$NjG_WU~aRXY7c`$zKgKaUP*Y?;3PE;&A2e$E)sKmDe(|Bd|og_a*q zAF_RUCExt$Jn#GuA+J=P->K?|v*EPU5xC!p+fLvsFOGM2fc8p-+R@ENXr4W67})PR zRSl#FeQCaL(W;$h{!Zc}gV<%o`5HW2mC3ivxvl*r?loIV^7bXJLT|?}gQjeLGgxfA z#A#}xb0jBM7F8vmWtW9$jihB^byafb5G400+|+0hf219!4*mUxiRf>AFtuvg0FGwp zho7SV^*;`x-*)#E(C-j0@5{DCrARM>tPJV9jr)QGOFWtd;)kF&b+f1(*{dHvjL$EV zEo1M{-#9Eq->kv(UG9jZ=;QrL`g=As*#2hN;Sa*oN|NpN(hb>IG1|Ps(U+&WUIUQ% z3*UwYAoCZS`KcSf3XQ{!ed*A5dEbB&9~&P`mGz^~k;UI^*W`jiO~*ee@jFK36zFq8 z-lb~Ga-Jqd@sawviAwq@m)`OA8EKYJ>TErg^3kow(H&LE&8enqjcPdlFgBhEnP~0R z4EcXsU*De!LFtE$H%()YJuHLc2`J3C=dfPon@h(pow;Hse+wNS9V9v;{#Jr<_iP4>fvIue+D< zakw?xD7Le2KJW%MHO<;m>K!(vNZrlBeoh-z9V4%ozUSWZ0%pbn$~aaD6T4&FFn#v+ zKfG|hi9;9V$!z=h-^U#Im;)bk;A0Ma%z=+N@c%6bvN)qX$6xCYH2dp3vBpHaF5D9G z!~zW=U(j{J{4>sMnqN8V!r51!Fu$nDb7E|S(oS!hKlhU9)63`19gtkyG{1b#oY`{* zB%aYU|FY6Kvnpp*^d;BToL2kgP&gKkemUw7`r1ydt8q>Dg+k%DCmivIJaxXfuf`Yi zyUJ|DQyUJ2{I&5wIOM4fhGYIZS0quh$lvCwjrx6Yf8CdTaTheZF7yS0v?Ct&1VS-? zH16>?`$O?_Aly~1m?stwN68TA_YkiyR2K|{8a#8#FZML~V=-TY-xa8fxh@PRLUm3J zp89aq6HC;@YNLUO$Z++RD!;UR*1X577p5v+SrP~nbJhZQ0F7wyS4c9L6$9tb+p6Z%?kqe?-<+JCOyQWtuoZFAa46FJ| zAI`>Lpr*xN6El^^JcgTMvLVYg@Fx6h4%GVXy-UOI&Ea4dGt2-hj6H2Q-PAQFzoT^U~Kg1<4;RO64jE(t9PNdSG(hC~w` zHlRlX@c_(S?C%qhxp7}KuD&yJT;glC(yOwrby5q_aAnnB@l0PniO*x zkvzpzbxI@}j)!Z*#%HY&I+Q5-W5{VR5NkvpGmS9<=Q+jG6o|zf!mddKBy#lue-NRn zoHe(6&OA@$ta-DoXiW&zP4YP6Jjv++BhQmOivl6;&}VTNF-c=`qUX}ms!Ph}dL~>j z$ur>pi5V@@Hv%G2f5aE1zl`A9@c_kfzhSH`;0z02ka1Mk=JB@%V)593;bHUjr#tRz z@EEE?z9#=94-%gMco>&)e>64}nHd7-mRFTepXd4a+w8e;&g_}C5tn((=af^{bDrk{ zOP8(hLnv_yi{8OSha43BJ2X>&M#2URhEt@T>a4U6mOWFluOA#0DAV_%hgAR ztwztIY6f9T?3~k1D?a_~Q;Ybo_?*f76HjPxKwa`|j9!Dd#a|YJ+}X z2-V~K^LS2)O!7D*(o;Pf(d1thQh**d19L*1zuuP!N{QA@9N{8=qw;GYE35f8w=vw} zi8lsf9+Pa9=mS?JI^ze{_t=^laHy*HMBl3 z16Lf5`l2-KYiuuO20r4Ox-y zOdG1EqO;ENc<0TlHcA7{JmBfvn=yCxEKNFQnzhX~By+Di!Mt4S!9vtTOk-*rtr6Df z)AZ)F6H_Ho8&5?2L)`1N+WHq@mbTRnQNq~FA5vR9fKH72nj%A#(=+Xn*7Z)G!I@0o1nMv<0vIJx*|zF#=U;|{vzW`VUg5g2 zf-#s+ZSe<#rx^2$tQ!~yxA9xcZzaE0evSMt=V!*J#_7NKO(yRIeq;IN@jF2N6u)Qq zZR59r-);QX@*7;&O43^SHS)Wh-!qio#_z!Vn>af*4u4#dIZJ0L1 z{FrLKnm{lRZv(>&A;M-qVNBL05RTRe;!$6%xlTk?BEf3JXY6)?VV)xJhD^1!&h5qk zwicHGN^mO{57ZJdGaeWX$6|n4JpkdTE~2aigb7w#zB*ro+Uh7>R~OLJ>*}2QbqlGC z%cg)rCS$^8U#OOc)yXDz`TWsvjhUT^IYUH7Pv1E z3sN}fi#8F~Box+$8?kzQK~_(M0Y4>!O<46nIHb&xMjxTE7r|JV0Hcqv#n(n#5G8+t z%O>Aq>|ZeKgoaa1Fp9^swro?4q1r~VAyms_blD(kLJiE}zEA*_0xU8~AsDS@hC+r> z$YrA*8H%+Sz*q@E6asxsLb+N4e)TWPfm$QsV7Nh75x+`!4r%emCY~B^G;0)JJWNl-2)K(HBqDku`256V0V{s4r0`p-40s z2{BAg#0^&yak?fEPc)H=JWGoc>+cx zL9V%s!F@s&xIjEk2i5p_qCy_#rxg_P*ZMTfKv7!b_tz63=ZZHV;5Gh6+9{Ait??tf z<}w&=(G^wukvE0@dRSJYuB-9GP1OyTA&>ddkHFXX+rp|MP}_#F3@Q$*YD~oeN1kc| zaEKsIl>w)CP#sni^wCX(A`<49Mg{3%uKYgT*N|r((G!B ztVblG4bGKVAdV!k1`K}_G1ckxOQOwWTD;g*>#IR({X&{eNR_oxzcIk1)6KOgJc1_1 zj37cFXBc5E(h5K>VTgeMgjE28LN$bt8z5rqjRfMRTqG_g*D?V3(RG441r1?^5t;{} z^#~P3$_W}31VYLZ(~tn9{IK)%gGmiSB%09QfElQWlQE&bVYnC#QLFyM!6zRVh+-$F z;{&Mmhhl0v+BacR>2;of>Nf;8YA&g5^wA51O%drEB4J82GF8D;kRA~5Jf;?S)#yi7 z2>ohBBceiJ+SaH6SBrHb;Q-C8KoCYW1~`5RQH^2EWdI&N0Qo^L)HWt+H3@(rRh)=Y zI?y0y2GIIUFGQjmr?vQp7OQSe?zMqtMoewc7oabLB8*usP|uB^uSJ1kQZIy2>J6eS zs1ox|7iK;J>LEWEVAK&BVuAtHgu6^3?36Q7AzFYiL1R!isx+Z82?-B_*sv;?Fr5{a zVyg{HA=Khv3SolOhS3bG&#Bt$yK%{RG)4(iAUik!v~yP z$RL+Us<|1eW17%)eSj$*Y%m4|5xG;95W|*H?V)&-X&-D3NKe#CwGb$YiBmZusyHh7 zkj^C#H7IaXk7*5xMrjGtK&B#zR5+pe7Kn;bC^*xywlJ=^TInE#8rVQR1mQwbpcbNy zU=%ADJw57c;lWYgVyZ+)mC3J(5AdrFKYc1KrJ)qX*j5;W`B7v7{zSuyJCvb;=q4>1ZM#@}f8)4K3(OJzH9e#gBei@8nC=kc(MdYlhkcbtWRHXAR~=G(h8o7!VMV zuwy8JN0O^pqZvGi0?M!s)mn#v#K-|61Y$X%lvy2O%0R7C>+6`;8`b_gA3{~9!CB{L zh9t8e)f)qt&vnIsmiv?l{V70R4bo#KySPRZ>-;8HJp*4C^-eB^MV%kfP?AOlAr_6Q zYtqC3h&^arvCJ$S=xhRNp-!5oF3gHlAO@QeMA*P61k^CbkmMh1A<(#~LkU6+W-m`c(qKMY8VXDmVl$(_ zh2G!}!+}78gm_CRWn|VFJw^y6n2GhSx;E4>Pw^wrQH4;1sjb}&=Cek^SioFRm^jI( zcml_c0z+YJYd?=vBmG*tkg=h`n#L+XoCS~sWZPJ6U|4z#6H*i- zG%PIqAqZBjp$3g;K!eI3f+&-^P<4mcCQv8vfKU;NPoD@X>KFG3;<4(edJVdyZPf0z98GG_dE zYinaJMij$XWNNl#U}63c29X7St7&;F3QBxv)t;4rW~r5}%(4c8T9t4q)79VVuT7X_ zf3#LA!{6G7(GE$ijRCA=kRhkgq?rWzfKF{?;;Iv9-~omSt&Ak3pG(O)U?wkrt632N z*k}GWZnU8c5tMo#I(RhsYCX0MKbS@+8x{y?d1&0?gp6qMqMn^db!0u%B&3Gw{Rpm#$>~6#7>iov5XcaGEgBHDUR|6~F);58$q#N}32JA~LQVoB=tS~@EmM$?V1x8sl zuuR~m+(-<8AnUj^@c!$ zR4?jBa?lW9KB(7aG)TaRZeV4qK*9&83c8)Auwkuupee#IYY2v!qzN%p2x&i|DNP49 zXl>dcL1+lqFt4~5T0oSET zQKhUwH9<4+>QPL;Du$F1uxubyY8WF|c`z{*LS0n;j|K)GITMCDM3YJr3>Jm#tpU{4 z*5qnr2^c2~))O=_BM6L;q2cPdV5O$CDDp(uEOBkbGeWRf4`8(uM5zGGR)o!Mgh)su z!q(_F;|7-rOIbp;MS&>s09*?J%mR&?f|=yGrLoHH8Iu>4glllPG-^j)K!*|p>QLpN z22rEjAB}hclmg!yarP3xUII=N2YoX(_S?H69bEO|njfF^}<}om&Q0Gb(`tjDg7KUs1F@-MF#J&&_kkl-ctA$Wj z@g|i;6pbzhyLY|F=lCcN05kNDuQ-mNyi=mly zd>jiE-DNH`0m38N7t}&YA=0ao%{~Hj4M9xm9gE!L#pY(wZyw?I)ho`hAw<8)zLZ)G z`cb6_tbUqAg8qQ9&oDRXoS>5wYGCLSj}e4f0Vx|kJl!`gCwUWS6RQy&qQU;>xpsFN z;WP4%bAg9RZwKXDMEib z^g00DVX+9WpR^-_p(zmiR(Ub%NlG*rLlu95?2KR{Br45{H<)OZxCh&?qhhWmJQbR7 zns_opmd6l=P-U!1l)|*!3>#;^ZPU_9-X>w#g@xvI!0A z0Ard9`EUW}iBYZOxr(yNQ&tv<1ft2bUBi)Es)~*vh+@wG;!6{5>n1D+HXtZTZzE#> zm9zLz4};XrB7{u_+T^cOJ$Ms*gh8zn0A8(28az zG;-R+hL5si|3iM5B`&!chWw zMzTx*Qw6ht3~^b@%G?|vl$WOoZ>`c3A_Sv55J3pp<1y_ZuX+Kq5ItfdYPwbfO&C;S zSQ`uUdVvwurZy-G`DCf+G!H=p)dCn_c8HE|+Q`CB;s#frj(L{Lpwf*A^H1;g4Di4=y_ zt#p{A91yXZqS%WvD@D*;Fk~8(=u{*%5K6!g5o~;5VfFy9u-XXB6vMLuh&Y29pOHaB zZMep;JY3Uk{7SginZKERkYhOwG;+f%MqPLf_0uJN0rBB_>D%xk34=y}g|IKG@=>{b>p+|)TI68BizFG` zx{HTJ3zBeDYjPI`s#)o>&uZpkP=qGpI=cXv`7*3E5QYiaTjYibx3-~pU11{@uJB^* zcSM*M;C@8PAc6r-BY-9X)Qm#dOh_vUP1i@TCZk;8T~uDik-0%+oh$f8ro=!-O(wSABSbV~DC8l`5K0MS zjSx@{yFww8u|nO_)CMqV63}|Nk0W6$H8fT=a9*jZSXs4}+3{^1@ zU#*&eYBU3ihz2E&0JA|3O7iVVN1@rp2jeOnWpIl|WKd;foDnjs=$@e-r!BhNT$)b6 z3ud?wWg~$+(gQSCD-FpBzLtckDXa}dE?K9T+h!nhjlML+!u7gK&zh>W5)zTzRJXHU zfkBwHh8}aGgwxFI_@g)A6)a0|MmB4Sbu{Nx2WIriOxQ@lITb^sO4i6FASUo z9H%)E`A-0(4qzD}YMB%W=|yEV?rA`#17I`|C{Lw5(+Hx$RX#?waG!|WTmW{rAwR~j zFcimh5CC$u?gPv!7u42P)GsSs*erKH5HNHYXGpY>Ig*wKYV^}iBHmm*3bjiE0t|he zxzaUAhNf7_vkBEAS&#&xDa@|0Le0iVw~Q^M*LVh)tQRrA<0w&JmS{*U)-|Do!dQ|~ zwr*9We1B2R{;a(9(^-OOLKAC@hw+ov2F$uI=ELHRNpfPSaNXu$Nl4&?9O36AG0%u; z`X^9B(5uSHi3;o+FpN_Ulspicv-&aVWx2RaNQG>pny4`cW0>2zYNWdbQc5u$I8hBa zDe>FKh%jS`?L^S$>{Q^IrA@I-HhvBKSrQH@(fTwUTo;(gd42XYS2`MH`EMK zjvXx`wZY6B7(LPh0E9^N7*(h(ExiD^E*<8OhoR|*I1B=5uoz0nsn#qEI7Y&1EG8Z2 ziZvQD7hpX>XdDHg_@LA21bUj*xY!G@P(DS#XZwFW0598Ldq=C7EWxDv?{A=x{viF zftIs`W}wH+tVM_@6B-&>j1dpZvH_?;ewUc>aB`SXRYEJ-o3yMZ)b?)-hapW0$yz7m z0W5UkwU)TCu&%M;#NDvjc+@Kw#IastSZoBQ?Ksmok*6!hGoiDCPei=Nu$PU1(7_XF zWJH&#j8Nq{w?iNyls}9>G~r93fXp9-#fi?8-dWNc~T7D z*@e|MJXc~UCs?X{;v5$mD-_*JIEFna z`woy+v~T?gc`nnvM4H0lYXVn`LV0#UbDs2H4pEpffH`#kF9Iv(BIr9YDe2fF*<>1zNnz1~Dm$sw~<| z13ccS9bH1%8~{T!E;pz`qZA}JB2Y?!%^nz!4C&gqOmv!;IDNy4m!BM_Oi(dqe@^MC z)>&1b-OP_`g`? z5K2k1Iv1x{z3WsT7&mKHCP~`L+zs!rJ!FN%!^VIB%u~?-akeE9cC-+onyKvqRjrK` z0|*Kip_pWp2rV^7G6=P*)%Z|=lrcv1D6_k-1)r*9FTxZ6u0dm>f#=cDSc2?PP^~l#0CR-$TM%mT0m9M*K4Z=$0NUnA;478|j)i6{P*CSh zfw%%nG4}AD>J2u~Y?a|UK{+Nkt|xmef!kM?5#t<7L|8z;`UJ~9RgZZ{pkbb1Cx$xN zN+KnOp-UOr4fHEhf-&ZseXKO&E+B!HY{n<-C)B(_7=%Rttl^lje%caj<|u5G&^f8X zi8Ym$CECo9Nj05rZRW8U6!I)+?WBb!sH**(izbkpNexxMfVyCZIS4Q?^{jy5X|ueY zA}>&D{1+hNNp40|m=N}(usCaE^g2EctOYLF0fb8+?(AkxUMPUMgig9dx>+YFTsp~0 z+?*K0c@8hAR%fKB6%&=pTWkQKE$sgipo66N6lm4nA_D1oc6*f@Un8w<(MBjiL;;Ga zh3P=4uoB&<9Unj)1*qMC8ObgF8sg3>4)w_yw>qwMMidb>`xG3p<#F)0#q2l`Dk|%d zP&G+$5r`OBV+3ezo-HLrphwGR+@f_80VP-ZWT3#>?)}`-}u}&%g(;$v~y?f{nPuK?z;c^cxnjnQBK*Rkgw`t@Jh&pK-Bi9b7H*Uf7_ANW)J&6~S-&Uo|t-GQ55 ze(A*7ldrGX_{LpNt?JzJy>7SvmOEM=`QfO-MD`c12v6!crQ_wFhJLX;KI6&vOMk!X z=j*S!YwqZd=T^SkeESRaug|&o{IvzA-+4;Ymg3tk82|p-Z71cde>wk+ja!#q@xbP9 z+;-mEBX@5(@n<8xUGdGIJr%w#XZOXm+mDEZ@<%jGoH*v-X*(}mG2)(+KL7CNubMpm zgi~KT`JpQhHcguO!f(Ixy`w)ddG23of7LlJHo||~Z(64QZp6aozyI>Jic=rnyzJbw zt{byrQuJGOId4||F!jKXvWlkNe^LD>Z~Nwr2X;g{FWOwRao!O>zBssb?3_2AK0fuW z#|wV$zohVpo1Q+|bIoNd&dUGChR1(*dSu_x+P3`Wogcru@e6aKFTK@z&6ZnypMUkS^H177eDkL9TQ^+)_^0>me)Q>UpL@0S z@zCo_XJ7lz`@+XXf6=h-h@Y z?>&9a?cqOu`MAp~W~@5CEU3TVE4h=*N!}!Dk~7JeBgv5DN3tWi zk<3V5BrB2=$%y1bvLU&UOh_Ii3z7rLfcjs3uYOmbtH0IP>Sy(_`d59cepR2UKh>A& zNA;olPkpC;Q=h57)K}^!^^y8VeWQL+pQu077wQM~f%soKZF*Vxg%#e)85dQ}oHe`p z;yH8YU2^GVmtS$Eucnp*h7FB@g^Pksp>X6H&Y~omTUy%|7Zsm=#+he*@$AV{zHr*$ zL7n+lc?M78xZi1b6wq`{%A0jU&bi};xm-UY9yu+Yt|sm|gLi?zYH&5U?~A;n$rmpR zrlix8xptkydqlbqZUWoEXTbH~K5#df$Db@O;M>|Ba5Y#0?gK9eca%^b+&w*={s*|J zES(-h{j1B<=`+FV%5=INEV(G1UJvdA$L6_Q3#!uTNnm6)^np9TRl@3Y`blv0#p(2@ zBcX2|?@)o&;GM$x(8K$#>#yQFP2jq#)9G8lP2hU4;7iai{4(#Qg6qL?BPr)krH1U2H7y%C|9UQ}#yVrw7;0~|~+y~Zy1tH$A z1zlnK72E`_S9&C!eh91vcY*7`F>cxkP6CUrp*$D?mw=nVJ3&{J_h-QcU|s>w0Vjba zG4g}!!PQ_voc4m%;LG4@&@~$Rz;R#+pS|>g>%ew!H@FroYNmcL0;a%Cpyw#AThi$u z=-~^~8^9f4?$O9$8+-?MEQU|uZg3q~&DS;W1S8-!a5cCKTo3L8cYwKLpdTCy7VwR) zNnjD^1*^fUz;6~$4KhpspzFJo|0MW5-jxR1 z!BybCA5g!p*TJ_FpyNlpKM1b=338=ta2vSbr|?(b%l{ee0t>)-;EvniAGq)5^n=oG zhu@##8VrJ){u?^M-M@rS;JRNSm!IYuoCFrEr#^5!SO+fnHRB$v{!Kc42e=R10k;2^ zaSfK-3Ew_Ld9V;{zl-wVCU73O;BNE^=;FP{HA4Q;o#tQG;yJFY)`F}PMh(x?pC3^= zB)fK;kxqYJIm(73*)CThzccxbJsBSG=~!2O!G-x_XSheUxM{wlJpHjr0*qt$DnkzF_-qHNdGEv*M<27tFp7pN95;~qYBOQ3%Tz-E1kYr_n|An z^Q(wXC4DvNFXVDTxo>1o%OCsooN4)JY9m6Z~_hsk&JbOg` zxeW%SGWT;S^Max(|3q9|A#{GxU($}`K z{Ob9TGX1cIsj^AbRYzUhO4I3Ad8YHMnfYU{&$&2%->{rZvqt2PEj4_Yu4gk`U2Dj{ zlDYGT#2wnRZ-nKBuJzm-cUn4qEI1^O2GEs#XhQym@K5b7puLlr zuk+My`sc7d?cWC_l9S0iaZrzXo%*k$j(r#MhwfE5|H}A9NSj)bPQRxF>R**zDqfW4 zdpdHnn|gUsHgbj&FVD~Ojx6hqUnm}($xr?A59;~tQ1vAG)e|)Ar5cy3mAv~Xx5SG+ z5&wp2pW$DHWR`x-DW!XcXkh8c>@`Dnxp!pt#X}WvP2h1s#z|yGI(?C7uIO!hMZPDT zKeo)sK-rc@&;i_DOWrl){Q&z(barWMtrDNE&n?TJ&@rqczdb8wB;-vf%lE)T@-UXl zM&{%zjeNanRpQxXetW62!MzIc=q+V^nS-M*<$=Ge$p2l+eYiXr9VVUgw;{)qcVtdp4tFYs8VL@qjXdX}E7Ixn^i2M#KbDZT zkF;TSEO%x1^@r`_NY2%RyLj42$L;6?5Pd5BE(snAr^bgOrW4J6olKqPj zM*1ABhCY@~Yotv4D(o1Q@xsZkqiwAoTF$)#-1~y=@lW)vC2h>rwtY734$?|U%g~~H zn@F2VT83uLqfe4HnKVojJ*!|ggI{^~+VWQ`oqtMmp%+#Ss&fo!EBn=1Mz#s0Ehp_A zl^G~kKR~XKOXTXpkrkO}*@IiW=(L>cv#-b`Ir@)!rm`N<-0hkz9Z=TGyX>VS9?TzG zxrLs4BtLJ)NF;Z<8P(GmFsd#0`kYnBis;@zJ$F)1hw9;<(jFnLh_#K^q&Rc;ZqkCJ z$;=ZkD&d9t;{fULOuCsLtE3{$y)mq*T8FqN-WG9hN#>rdPw8ILmk&x8O$$h0Mfyv` z^-pteoV3OT>Ga)72MLM$1NZWeqxW-<{l>z`c38H>l12-Zoz}pv@k#YVBK1xm7BMJY`oi2E8M% zFm}&H`FY}J0fXy)^86?KoS^C`_ax<-RJYLyNbmJIRr#K;I>Up>sggW;MjRYlJ2d_E zz4Xr*K)-l$2jxmAH(AdxJc;(o>^APzeaO9+xwmqNd!k$G{5!b!@6bJoy!*&I6yHCL z?lC-~j&ft`4wu~}q?eFhB{~N6<44J^d<>TlQQsq^xBrLrd3kg$6T@1{{mRnub=ewS zvg222Zogi3e8&;l%bdBq*KRKxS!Ilm-UQWEOm+Awe~UQXI% z(juylf6BL(v^9-(4zp=@khX46+9uNO7?k!TX&VNmrAT{dP})J#cJxcr7%RZvvx_tj zac7KOX~vjnlCNlYzx>kWC8X^oO?I1;|6rm&#-%X#Sn|b;o&aI@5Z-=+B5(3?0ux|>X){Fv|MgHL)sI{ z#hiRte^%s|WG`?eqt~i7tC9)R``OuLBbx>q-sRTL)wpQn8T0V(nKd?(($_Ulb>)<6 zjA83tk3CLx98;S zMf8VUG*Or8i&NhOe3Li-JN2zQBBw=ugTq&uSdUISAELh2YX;hi&yaR2X}GVYki+wK>KYeKr{&I5f6TJuW-aN3q-Xj<{dosz9@28Go)9gYR6dqYZ?|iRk65oG z#W}NwcEOa5lwKRfB1}HoQ6DzmD)QH5W#8=#gkG5}9jQL`QZ|nN_Mk0$7#(I^ZGAyI zvU0vUw2T-Zh}ycA`U>&;UO`;{O8DJDT7a9=--G#U{54PSqiQ@FUcbC4PNK z9XVG@clTdHUpO-RTBjE2r85~UYwoU#q;~AXZ^MKQa`dD%k%ZwIq9K!ZuF(-l23cb z(sX+N5cTzq4;&9UIbTPPhNMe6W*+tEZxSsbj!c&@K^q+-nj_rX%e{Y#=1q74hZb1l z2a?QgqP|fptW9fds6l<9RXfJifbPT)6)guTyMVG64AI`h{Fm+7IZq7DKhq!LUlqRl z>KoGOKic~2d}VZepB+_^zau+mj`)YJK*)LrQm?;t4fQ;eOs79fovxXVPEcF#AYH!y z?eOH!$^Sqf238z`I6MdICL z((WYfu75q=%jBtA^%1<29$QI$g?!KTgnwNh49v@Zdq_u2sqs}kNnHo1YwW*MS4nmv z-|Nknif>CeJM@)X(RoIiywP*?;@3?wNTAKhVHAq$qpE zAhl)fNfr6{-^U#Im;)bk;A0Ma%z=+N@G%EI=D^1s`2QmZ*1hVqdV$4VOSE9(Pn}!* z;5z5}eS4jCt#duxV!p*=EPmSJi59N7Vok6fW<8qAG7$B#a$L(viO$ePmjI+T+*TQ4ST(N zxpV!vz5b`g4=m!yGyFc%qTAwe7C&XNy4}h5W2<~&fY)SVv)r!T0Gz4bc>Z1 zt1Vt`ae>8pi%k~e7MED;u-JKn^ZZVG{Y{HETKvAnTP^;##XBwDZ}CBkf3*07#T_f0 z`k%Mgf3?_Sai7Kg77tlG%km}H%F9TL1s0FZV3$+>ID7qBixVuKV)0CiQ!P%n_@lK> z`LB04IK!sTwRok)pZ=F~zs6n%EM8-=#o|{juCTbu;u?!LSzKrFXBO96++cB&#Vr;e zwYbybvljni@oyGiv-nSo?^_&ZRT2#bH(-kF+@2;&B!~WwFrW$rg((o^A1bi)9vPSe$F| zN{c>=4Hm-|6BfT>@j8nuEq=pd+xH#5-Dt0GvG_xaw^{s^#k(#3-r_?Rw_AM7;-4+< zviOq49*eJA+;8!q#cZq3jahav_XnQ@@;%6*ge6!PzLVJC(#bS$RTRh)lnZ=7N z&arr<#TttXEru*6EH1X#Zt(_-T^7G(@kWccSp2cYUs(LL#k(y2-r|E6AF=p^#oO&T ze$HOMXz>+`Z&=)K@dJyw)(#kD@o0-DSR8NhB#T8l%+0?dd;LX==Ucq%d(QoGdp*Nq zwZ%&KHH(C6l#h+XJmBqU({?6h<7XN7R35(BI{ENkx zE%sP^)8c-M?_12W_P}t9`4-1mJi+34izit;)#4czr&v7SVwuGm7OO2@YH_|rpT&U1 zh{a}$ODrz8n6$Xs;&&{5*W!;X-e&Pv7JqBy>mGakfW<#pe8l3D7I#^E+2US{|Frml z#UrkF^DLHIoMG`|iy|@13(}PfhOuLJ~+w=ppn@AOR9eLJ}Y}4*>#1S|o{p(gaj|6a_0P0*a!K zf+Ff8V#Cg37kftqdquE|zwbT!-Q>i_-}V0UzVErN-Ot=J_srZgbIRkY6Uf$NCYeok zCG*Jv?RtD zkZZ~F$xY-{B6@o=Gkt8^{&pd1T5q+s=brcP?W57IG^&iSzai-j{A;{7vL-0a zLiru?L-IKJ6*+5#t^YmcQ{*3{$>%s;GLlRrTaoR^4rCXyCpnNTAWO({avFIF_fxYe z&nFj=4dglGdh#OjGV%)YTJlEnR`O2r9`XSBDESn5n%C23DZfI#MSezm->3Wu`6YRZ z`_1nt|4jZ)8kgGr?;#_|STco7BeTekWOuSRIglJqjwZ*G<>X{?23bw!{M)u?F6Bk! zQnHZqX&L3!k$jiyCCbPp8T2olQiXz&++k-v1BrtMz$wAlHJIjWIu8kSwxnQ zWn>i@BIlBe$fe{mauvCryokJvyo$Vm+(rJIypz0_e1Lq6e1?3UJVL%jen1{0za)Pk zPm#ZpZFv9v7iBm189_3h?8bSSLOK6BdmPd!XOkmY-t$~{x;pyg_w?ufl!rO_$g$)^ zaymJST*B+|WbV6a8NZlpB+nt&kiMI2dpA(Nh}=S6LtemkZl}D9yo20N-cLSEK2APE zK2N?%zD+(zKmM5V=j1o!kL1tfAEb->Yd;xHCX;Q+_GBkAkL*njCST|MMmwWHxE>zM>1|1KgMNraYJ|B*&2Da+w@vXM~H&WhB z?ji3X50a0NPms@%uaIw&?~@;qpORmZvv_^|k@BzPU!-55$eH9kav^yZxr|&ze#v!U9p#PWrQ}uQb>t56W^yli zFL{tWL_SSEPrgQ;;JWxWg6_n@G@2;c#{0J_X2V zavJ+RfpRM{gUltnlYPiRWC2-BjwdIPGsxAvF3hI9ajET}3n&-R{yoY>{JVZW8K1;qr-p23k-=v(&{q=j4KPJBn8T3mJ z$aUm}WCGj0nAe$07=Jl= zExFT)=k4k5oT5M6xy6p6pEaBnObg$r7@htRkz)TJkJ%8M%hMfV`Bvg1n9Mb}Qwb9--Ih6aYw#$j){nMF@KZ{&O zt|2cVFD0)cw~;rKcaZmx2g%3C!{kfko8$-NG4gA237>cUNclI?iX0eOu4lKhT5MgC5@cG`a8Co4D)VkysJ`6-ms$Sktk z8asa{%6ViTaxgjSCR=Z~Ba>suaRBuOzP}capb|d&&FA z2gxVM_c%_6DZfa*O1@2gM1DqoOP(VCBt19U{SZwilC8;hWJj_)*^?YV7LX&!abzVq zo&0vCU0)65Gs&go3UW1h0lA61g4{;#BJUvYArF#=$fwB{$XCgC$&biS$*;+idTaxxhr$MN~y9Li^s=khsl9pz=@N^%`}5xIrj zN^U22k^d(5kq5|!$j8WM$QQ{YHL_SVF zLq1O)A!l>Fe3SC~t4zS$uGEW9-{md`5alg+s^k26Ihsr%)5#8GSF#T|ge)Y-lI7%7vYMPv){%|mN^(7U z5&03HJ8Yr6mE1<&ME;w+i~JAyEuZ^6LiuU(dGdAg1M)ce4S9ww7THLyCeJ4?CNC#XbN*dV`6kj$|GJ&>KKlJW%KsrB zCZ8amBVQxmCO;%UBflk2lE0FFk|v*L_{mr@g=|aakX^`p(m2mvPu6q2@6Y%lWFc8X zmXVXlGsxNGd~yl7jGV#y(^Zt$lNXa)$gNyQuc5q+EMh<3O!?pBKJsGPe?R3%$fwBX z$s^>OOAMvf-Ol9l9iau#_exrD4ISCFg84dlPb%gC$9>&TnP zTgkoTe)4|uRnFgsC_h1VV0#Wzet~?Ae3v{*enx&x{y_dpo+jP=J~l|kkUd%734TA2 z$oSS|CYeKaA@j)r_Fa6od^fr{8^i!wp*N^J| z9fLIaw*&H#_lL^|ujSXD&rO?*Ha62fdFQEom!7B_~e3;ER)Ky+WXc+bx?_Fd? z^6oeJ;8pnggIDG0&+=DnI`K#Go?Q7jc5i*mmYsJ1I`!k4kOFRnPeTgmfTG4B=?br$d}2Z)W>QTE*1$5*O+gx4m^XXj@&^}imngDuWaOqAtJ>CB_++T*>f zk}M0>J9e>6+7XE`&w3@f4`R}MUin{iX>M;SQ0$A69!pcc&yX>4jD%g@BUt6ql;;aX zMSO=9($gW?PO-dQ5%mt%4o_~I$xtqFTli!zCVwZ_QHz2|A-hyU%`}ie?&-+RqguYsc@g9Sb-hNt0@%~~$ z=&ywg?=ie;(mOy4x!!Xn&p<8Yd1t0U7^H=M-g`Pj7_5b1-Wgcoy~DIH+Pe$uv$sGC zW!|6^JzNV_-fE1dw=iiN?40Sn2W|I`Opq55&-bR~Ll~{CuJfKR^%QHN!MjpQD+&Gw zO<3;Dj)5>PPI`N-_Z{@VcYO325Y~A&qQ|_YzTGHoz4s+tu)GtZ{{vy8cdZmKCGv5o zZT7y5!S_x}K7^F5-g@+#cY3P4zIvxO0asJ+3~kPB-fKHRIKwaQu+Muvt~1_Hpc8}x z-ccD4W@_6Gc_$@9nB{v31w3g)q+>{Z-qb&!H|8a{g|~B5+(YO;>l&n65}o`#PGVJx zj6NbSLpbgQamWWHR0o5srx4PLA3BQhhV?VFBPAkAPL_=H*z3K9(P?wkw+O?x28(aq zg5LJGj=u)liC&C-Ou*+GAS3WhZ^H;gMZ74rdV8Yd0@1PWAnY?DzQ$X;gFWJQK^nI< zIM6>s^e@arj-6|3VlqXU6}hvBcY=E=Dlo z7xY5J=$IQ3wszyx@ot<-(P=?9GKDUonxq)1#(GRn$52e^2B|Lx zT2{G)q%$LaKrvA%@imBukbhAbm71_z!ecN+qFN~M)rj1?^66N0|#l+t0dj#3NRT$^k|5*P**zj&adt&bo zJOROFL|zT(F@F^S-OG`NC6Q4Q5$A=Ok?$j9$tmhHi5rEt=SKb@adL|KUE*@!5s?<= zq9v!OXq@5{S`?WoadL`kk5l5;h>M9k9de42e*hb|7daw7mN+>@eJyc8@qnKs zPEJv>gN-Z0fJ6omvg8z%AaUXVk!>YTPEolM_dpin@+D4AQG+CI8YX$PS}dV})Vo|lZ6&l$Lei~KmqJ`+ z)Luf5NT{!b4ohf^gpS}8k%ooh;d1{(;ty zAu6sjRvb$TXuBNKEMbw-v#G88@7M&x73)E*ZQsO&Awjv?tPo*6ZU+)vu6VsXyp4OE z1k+{rQ4abmCGoGALLa>9aX4I}p%O$eeEC?p5|u6ae6L|4OmwN#bx4abtOy|YImD$T zS}H~&eR2n!=y7tl4?yxdxi@1V6a6YeItaOoq2IOy!;@$#Q;KRkz5|4SG9@KqE`+2u z(R&fL_T!Yf0rtF%fT>SeXpJdR`eazIBPFpPO6!-@Ax09tyV0toocJq|V0bG<$PGw` zrg+z21yAZ6(+75Bc=K?zO6n3VD_gGj97H8`^+~yThSd?pB;JdD&P~dQIYY{vwK4}MwFoZaQa8ZWP4yC*>Q;R* zYuj3r^zI(rqa#V*Y~|uZ4P2&hd3Ya}4jJ;*RI~oRL@n#g+hP>kpjbmSOW`J&RDpVN3)3 zXP5I3j`W}kOZrv!RcdR`GDN7`(In)qqd?y z4V8v5Rmz93OWlONHPuhpgSk~#G}kf`2V$&-rS^|Gin6_1x*105fGFJ-?=D<`QwM6n zmsQ=k+(;d)y(z}K9!{4!L_1}Q_h#6hIy6G^xAEQyyHke+JE7t{?+J8UYJnE|*{hK3 zc=ameHX`;*>Tpxq6e;l$7EY;!(UGXuio;3wV+RE6ev}Zp3re~>S`GAEfEd+FMo3M- z7E!4Y+I2oagH3f0Ji)EH!!0b;7B1pZSD>4`Y74x=tA=6<_|$2*onQ5VW}MW$qRRy)JU zR?DD2LVbau9H5S)ui}*l_C=`q(2rEV!-1kyb_O;a>OXMQ7T{@YQ&IsQ^&DRbyscM z8%BRs)yps@DtTTrR;|S}GSoomxK$E*EvN>=9*?>yPkQ8RSk*x-!o5d=N=HAYs(WDb zI8}&D8mSD&D+f$Bk&IY@QHq#3Nd=*c1KD|kYU zdK$G%P*0-da@7O14^>`_+A!sY%>^n6Z6B`cF?JPdBgVZ_%|PB_^&50XsdlKnNY%rw zMyv7gkP>wjN;|CDpdbFL9>G{Vs~*89Jf~98_UBb3diw?SBYN^h^&m#=CG{8(?#PAengG4vZ= zK=qVU^$Dq3FMT?sjnL-k`kRfgMX5{|qBDHo!kyA$ zmCl#zb3s2X$(HhbU8VeXw$#tJ4K9?HXG_C;^F_12Esgel4wp?EVM}GcBN+O$QA#(g zDsLWIS*)~`)AJf3jJ1WCz9T)MSE_<@V9|V^bU@k!rF)>xSB%M>Hr3W#?t2=OJZ*+8 zt@OPMhfJ%srM13Mm>g*}D(FRfHu`cgsnQm!*vV+~X5Zx*kj}ED%YE`6cGFhac3tV~ z1LsaV*Os>WisAfe7u(V{Ur5T_Y`15pZ;(h=*wSsjlieX*qmtUe>V0{2XD>0XQ=O|& z`2pYA((O0ch8^_nTKu3rvfKX z=1X?p#;Cw&81y&o%2QO}cTB){Y$-zpK8CZsuk_7$t_sY9R~@xY$y0$2aEMQA&3-Cy z6&&IVTNkRA3z3<7d_GU3A1u6?hvhI&F8^ zd=*H6)yk#4u}%dRU=6ff+K-m2KpZjzTv11Xl`0Sc#NsX!-GcwnGaYs&y0nvRR)JSB z>>0Lnr3%Ez)x|Dls|rj;8#=nQe{55Me2ha6TiUJydok<#xq{!L=$$HXVLsMg+m=Hr z&=$@*+?Ecj!2M`ZvCA(zp_f%)Fr03(%O3-ueM<%ILwz$`$%&ATs=zbo`PnXiCrF>G zz)4({=DW0yf3E`N7{7(KWxuFED9bRGy7WLBu7H6u8*Itv3Z%k&mf5w(xB`<D^I#o-2@#E7Vq(9@~DdzzUSP&8~fzD-eKX zJM6U4uE23zg>Sag%3Oigm~gx8v?^EN#omT-yPY=E705-u?6K>c?+VRf?$ zu~hG~rRA=`btvUtSF2vIWvwf45R>YDSG>X%VxucC5v@IFYi@Q07GR=2ClEh!>59^*PcZHU`92Y%)20uN6c>elOk;oFT7 zNh`23`FwHc*|ZTXFmF!HT%(BP`Kn-F+9+Gf@VzE$SBY&$t}hl7KW)4%<@rWRk!7~j z&v%im+7)g+Gluy-#w1OfXiJ5@g&6X*$!riE;2ystr|YTL*%pNvM@Lbo1{DkE}+bcl38;+8(B zBR%AM1A_Ml7@s~f*2L0jcneT?`a)kD2tMy=RGYpyt~Z1jZxLFYewJ?rgcR>VI8Azk z7BaHt)*9){Q)VF{*DE*Z>F4UD1vM^4`Ic0Yyc**#vzkvK^iv7>B2vp9qod4Rd!Moa zX5iKYvL)Myjxuk}k7O@Xs8d==id{vBAZwXlhRc3vtPHGho)F63q9gdrD(8M?M1iw)SFn){Aw0-0!scsHmFWx za~PrG^JE*p6YY#rMOe6_)eyK>jH#fyVH})KAIC?2fZNl_#s}A9uuH*q>hPn%RGu1jcXggJ! zDO=?pXmFO=jBvK<+fg>~Z==I<)L+ogRo|fGj_NB|(n+0#7Ijw7VXAgf2eG7gRfRZr zQ}3crx~reiU3sbvmdPHfH)8TtHPNJwSS>(rl&H^8!Wi`)N*Jr&L*8+!3~d~*zD6yjYA)i- z)Q5nU(5s=uA|%A%2p&2`!zh_F^7XsY28;MO}iPnW}DrCrwj* zVDxmA1J|0NKI$U340&i*NWJKi>*V|Bv1-*5*3MEE=E7{11pPTG0(RCI$&*pdEATe$ z&}Z1s&OUPFp+>v$C{}zjqxVp_quPYNHPiyMOR1wM$E6OSWK(%DYL@B=`#tJ?_z!N) zU^5=LVomX@>F}8hH5WcGOnr*N*Q&PYv9(6xP#`aTlRjQKcy-!;);455de6P?3emlqgdq!^)I|4G2{=CFoExhSQXW z8J3)_0Z2|hfP704&;#VMA0uR*=eY|}syBMwP#fVFN)5)<$ECdRCPS5AoR!*#O@d2Z zrq_%oQMB96+%C`avSi+i3`Wn{2pYbg^D$<4GSp7~2fGY+`)*&0&aH4KLtTX_ri^xX zV^-+2Uip~cY8e&|L%odRl{yn=m-Y=?;up@v7w z^7uN+j8dXcmXU$84@S%iF(B1Xd0 zoDKRJd50h(l4H}*S$^{{e;d%@w;a?MA@%yr7)JKaN0U3NHga9`n}-;i`!mL~P<2zj zd6>AqcYC2Lx+`s%;cJKPRe35POOxA3mK)h+(9)AY8NKB;^(K`j8U1Dk3yj_bZ*|`t zy9<@?MT+@_OxMk5oBeRRVo48}G0W4Z5B#iiwIxPIA4hjYR3%>pt@k94M4BQQ(dBS} zj5n0Ts0_sFX97=)k70BSQ&veM^)#A>7gt0}RElWH8ev4&y3tS1CwD`P9On1vD}3)K z{U~zu9O>AnT1oVHNw2`ku)dOXk)!2*cIR%>{a7#QO)HO)`Y61@-NvPVH1ID;ld6rr zE3g)%rJCvniISc+qVLDKV=vu+15T{n6@A^PsZ*92`JBXSlT&|(=cPrOO7eaz(Q?8z z2valqO>{`kac!;_qh?AQY?~xxMDGJc}BWIu1*w~a=cU*J=Mk(jccoDWcNk;TA4`QFx#oXIeOzIC< zjw4f|Q)G~ylW3X3@T<;<{>>hjk0n~h#Yin{kLVZT^l<&!9Q|K-f86}Q;gR<_J0_S*APzFjw>4@qA> zpgaHa<^nwEy2!pU+OUTuT3ToOs`QSjZDVYk@^L8Hj!CTIyxrHDO^H^C6z_*M7O&_dh+&=^bJz4EJpo|=-rqpoj%oRqa;m! zMqvzF4UajcC+gpSaiq2tVMXncf{WKF6(uEMDbznR>hYG2Yb8Hv3d1iE3p(KcLq5)h_lXL|=|0{*OX5;d6xWQBFWAmCs)) zr7f~~qKahKZD1FU6*!&Rn=l((s#4|QQmh7G`pjZNRR(t0$mYUx+SQ`sT8Y$UVX0GF zq^?2gCZ@KAIV006 zQ>GE$rU>6!vn=HVFkAE|-eqONw>+JpRDjZkO4CnJJ26YQPW3?J{N`GxHp6{{3Xh?0 zh9EntRQb{aXQPcy6_PlOi9?wf{FONyhoglqT74fh-ANRR(ZVb3Y<}|*ry0RCCV$fr z8M7(K1u@u#!V?83WiW+&l=6;K%9~Cp?sZI-Zv53r-r1CVMObnQ#z1%F_f5&Shb8xQ zlJ_~?61(OyRCB-PcG zNcl=xPoo|^I`VhL^0%MUXS_v;Tcd3ai&Czw;jMBbVmM52`$zKUwE~RDu zLuoRMw%<6ZE+^pl%{R)L;*=%7*p^@Y`+NOES$=bz(|>l6j*`m>U~=ovx5~Q3De_4a zC)-*7h<_-u**quJ3F-e@oQF7gj zEb4@^2Fg4Rm z@&M7?viY2tBt*WVA{HTfK*QO_5tR^!U@mNEha-CyPTk@CdX0YD{_V+B#7)9ca1M4V zokO8o<8|eiJBpZG>^~5eii~)fmusCF-~>>V{=BCmQxW!LLuo0>s>9J*y1`L2oq%j& z<15c>wC!1>q+IPT(-&W#PPo z#VaR(%e(%(s4RKl>=^{5Xf!<%haLe((R2d13)Y{G)&-J5Mj!{u**N@%|EZBH?MAL` zZscn%8Y!J@I-cPK+Od|8l+^|M7v14QQN>`#E=SRH0@%FkPnOkIGKgK1p*&ISO8p1V za5{fqvt8NYF4124Yn)?O1G@0Ivi3;P(nm{BtjrQU_>Pvj0a_ic?N0p{Lg9R=zxly2_3IgP2V-1LAd9X1ukw$nBt{)GhhyAf`GSu-r@&?&O|s$NQOIn@u{u@hTXUyBE%UFCju@Elw;kvdksa33t+q^jPk z1IEJ`RJ$0@Pi4%ELGqZbw;GJ~$mm>GUymoMZKa?#F;U1cOeKGcgf=lFQswz)n{F8* z$gge2s0>?p0JCD8>L^$6g3FOHW@5fP^vtzWWq(?sI&MHAF&WwNyt9j)kP8E=RGtVi zi}U2cW_P=$5|lSn_0smtYMhJbn}g)OTt01Le)Sl}V!P~^ZxFl0sYjkEELAFL1y|fKn1`5!$ z9@4&Kl_{3Xt@8^w2Gq{Mv+@Xu@}fcuhh7dFSOP9Ac!Hp;r_fM6JLT6d33AAC=xCXb zK`YC;UzEi2x0*Ae3s&0DZ6)rj{_JVbhS=_sEk+BSYvybhgm36AvovlbeU#!A%t5Z}hpp|mIVneqBr&6eBNq%WOq zJfUwXJr-6a97$5~v)aGq$9wTOy7W6?S5%0C*Oqa|D7n+ zodIz=jt-Tm?(Jl|x_xW1C!j~q>5Z%0HB`^32CB2A*XwQXa>-6DVwl#`a!KTK){i%QGw|r+fX6c7vpn zSE$@{Qy#pYsq%EneLDqtkakL|;^y86?q{?->T^FFR$MEL5bbt9AC|X=j+Z6|UklIs zVOZWt&3XObhUG<Yr^t=*D~(`E>nj4QYy(~NlzbXtQhCs&FD%*ch=DpU99)fro8u2sV9_%GjC7B z{d`#7c`fs<F>?kM)LZrK&s$th39OXDJi(l3K1ymLh|js3#97gZXst=O7sFm$jp0&kR!M@&8Mm=uRW}8x?h>es-r+8a$5{=^-{j`YkQ8#m%H*&+vG{d z2w5U0>cy)&)(H9Qi*)P?rs+kq4$@p4NgrV;ny#a6hIpMsErUPK&{1zed{Ls>_rzkU zqatvTaN$Tw?SXH7b<_}ueQ>19H=8qc)NF{=qV*EyakY+;Z?KnYEv)vndbPh7;vO7& zwO_>5e&;G#?HB9SKGJ6xe?pA9$Skjh7H}f&Ufc^~^;4co;Gkho=(4Tm1)L|63VJ^gbQ&rT>hZW1v+60_ z^e4&1d6T|0%3p8n!J#jKm+A}X5r{A1$WDa%T2b$U?{&6l5Bm=BU*Jf}hSOcHqhhe> z4dBS`4E0n|mrLi>tv^MLeG%UohrSl?%#^F?QizLj=xgz=_Pq43>JrK6ut-{m&$|PW z?ebjwumK%_Ui{`cZqWK_V@4?~j?394EPKJi8TWe%Jv9@epb*0;I z_oISb9~Wx{xp=sX7%i8sjdniwmaVHicwog8=Nh>Nb_L@YsaLOg6y)2RCtKugWD2j6 zc8P}D^}TMLAw~Y!WTfHl&Un2H4${k@eyH8F{@u2U>)&W)465;(+95BG{kaY_%JN7qD3MG`Aa6s zo_PTi(PsZoVe$5b4$axXNa>)h&C2e38817ceN8nO{zHrp_mkFg|I>;I$_aF^N8h;5 zDjtHVEzc;9!goKwp>H%CJ<|!aVup^^K*=C$!7ot$35UNIOK(~W;GM%5r>B2wS$E3V zoh~O3!y@PSZ@3%s3;~Tp+Q7Ubz)V#ln3sY)n zsnooY$_i7uwxv?@j;T+W(jzUEnzu%y!<0U0snoOwa!(6W@(pXzI%gMSxDQZC7KQ#= z346}EdaQu>$UcU9qn*y$@EkLU<4({S{LA7{L*4D~u5D__&BlgaZ!t(||o@rPQ`J2U94?H`GRG zeIHmnOm8#~ePvsVVbmXdWi|p=ThulS{{ke|W;%p?6m?j8WTA!3h78sDMv$ zHnRDAl0!Yy$>1!o-K_sOMtui^z7eB3QMsj2EuQ2!rNgK};YRu2W`pD_ek@&nvw?Q# z0djuXgiYSiM0|xWo7y3=uU7q1H%Om%JAtiuzO1mVGa}4maiLum2>jRkGyiub*7ndHO3mPgyh)T1lQin)0+#BO%-M zv{Lfi-Okg>&NK6Bw8VX;(xsW=I)fKuzbP@c?St?<*tK{A%8-qGtA~_`8Deigqx%3! zER@8>NE{LhVdqn*8;0;=35#KNrb6AwenyV?_P#F>k591;+*%EUnVz4KhI##eSLFs%Bcwml-X$hTo6chrcZq{;#Ik-*k8FMm=prj(BGtW13AU7zGn#zjFslz6FOip%?YkSeVe!>M2Un z8~=sEGdTR?snF7duyuhZoIyR?1erH88PjaSlTx~DcaEdv4{>M{=1@;fhY45(923k4 zOgkL@)l_I{Lf9%m6E3BmZGz0`&5UU_Vbo}tAa|+hC^;2}HsMOggoQAnr`2DSq!$K5 zVIU6wo&RXUjHVTZKRM|7NDC9>OM}gPmhe7GmXF#tVhlI zrRhp$Qt~ds)0PXfLVM3GP>?@`wvDEe{>Lv_)=9_choSHw4*z~CwCuMQj_hiFI{yf@ zb?>zn(_dyxQ{Z~L3C5pRH>VBqhsdwu&~5k77N!}BhL`w_TRgij&F<7)Ohgjo5D_p2bR z!VxbE?%JPNffI1X^%@m9W-PMH()$y%PvX$y>L{8{z!}#pYp`UHagD-#R|F3Kbu7N+ zxVD%`pE$i|1b0(gkE=FspJSdA$fDZ~P?29c=5;_#Sva(Lj-u%VoUf;|toI!AhCq3c zn0NU9*}Px)z!VPkS_|`J|KY^AoPcBAKo!|{96IAcC{035l{oZIC*~ngBZB;NVi^LB zIE-o;^Ix2{IswBOe0*6wOGewH2XixWY>*;km|sEQ1rfvnP9pHR2;wL5(uB@9X3Ln5 zI*pV2XS?%Gx@^Y&g~-uRDa4Tx?2g~yP~#11TnML&jX;9DF>D5o_OgR=wA3-IvBOnj z(Rft(9TJv9<7^zBPas@`z=a~b31J%oTXFaYu!eh4vP=ejTkPI)V2wvVQQ5b~gG$6P zE2*eXqLLz&_>@#4&bS}N?8Bi~a!1RQS2N*jU{MkWd=?6arM@XFCfot-r&E4&TeIiY zP)&PYMhaTIlQC87icZoMsYp16Vvpj;$cFF;wFXe@mr7`LK*DKg{DMP!ao`ub!<7@r zwq@gDmHF>dwyRAXws$zR7dwik6G&i=EbDs7AZ_dg<$N6ewk-ai+W1m)8#__WZeu=- z?9P}cS$ORPSl<^3<58^q{5oRCJA=M!YWlj(e?FC*>k3RY+=Cb?*N95y)L&fTDX+hbiTz-sV?)N} zsQX7ISePR6X%O$b0tt7ai|@b@y92^A2t0wK<_-w0;HcT6wgPO>v{HhlVcWbJs4E$0LMN1FK zQ<+@G*CT$n9q*AW?PCmgCDY_DT%Kbd{ZlzzD4wUtbEWB2lBd<5P^lg^%?XQVWrn^} zeS`_B3p%-8WlyOr(#f zDBza?EJm7?$T74hFt(Bhnb^YWMU0dmEDUeD8XE3ZjF&y+)TWXQ_eG4CF4JPURYARGT0Q zEH+#zt5fhS#4?KG(Wj%;D*XAPLePpff|)*v#4lcY#JiI3m%n)SMk42c;J{%2U~zmu zl;C58NFUhGzH;|1^R;pO{NlGRx?2cBaHra^AHQU(AYSFINO_e&2%j`H+=z@InkMClnVE@7YL#F+H<~ac(;Lim7su;{WO^jN zeN*Lji`fxb7!_am0A>f(#AM0Pp=HzI6gk~k<*<}<^cdtebr&p^JRQXj33PI+7B=YA z&Q3ent}a34G@z@v!a9{I-q4MmsT-W>$w|$~R8nU5rcMs_&(!VB$TWND>hg3entTf* z(nEK>;~pHieA@-%?2g0(4DI*u#9qz5nHgOiADOA-O&YzO9?&;xzC8GrXhe6`tR z7xZ#cZTIX4ZKHq70_-U>phYjxvVnR|AuG?*?#afnXle8UtLdj_R0j*_X+ zWgwOWV_0+VS^YCDaSQ2WXUGbIx-W+}Yr(1hu7n?@*>fx0;qVv5JunJIO@8D!ab~y4 zndq&NO$kj?3O6E-m&%=o%8rT;n(3|6Cnm?L7_1(}SW_@oCG>nBelpv<65#g@y8GBz zX(xU~8?VBrr@ZRXC`~k^0r+YnxkcgxtO7pCRIc6BYM#{yjOj9*xqBPB^;Zkp1QvI4;Do562N4$8pGg zvMi7pI0oVf;W!tEOQpM_ySkC8^l6*K$^VRT9l%NdQrP{hh(~a;zP=I~E~ha#`K5qB8A9evoPrDFv`kLx<+NE&x60{$IXx+-_vG}w zoPL*+g}eG-tem>y6wyyYqvcdCrx|jZhf}2d0f4z�w;JDNa#0;Dk5a8h)4a)Zgr@ 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 literal 0 HcmV?d00001 diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..ec4e68e --- /dev/null +++ b/src/main.c @@ -0,0 +1,562 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Include nostr_core_lib for Nostr functionality +#include "../nostr_core_lib/cjson/cJSON.h" +#include "../nostr_core_lib/nostr_core/nostr_core.h" + +// Configuration +#define DEFAULT_PORT 8888 +#define DEFAULT_HOST "127.0.0.1" +#define DATABASE_PATH "db/c_nostr_relay.db" +#define MAX_CLIENTS 100 + +// Global state +static sqlite3* g_db = NULL; +static int g_server_running = 1; + +// Color constants for logging +#define RED "\033[31m" +#define GREEN "\033[32m" +#define YELLOW "\033[33m" +#define BLUE "\033[34m" +#define BOLD "\033[1m" +#define RESET "\033[0m" + +// Logging functions +void log_info(const char* message) { + printf(BLUE "[INFO]" RESET " %s\n", message); + fflush(stdout); +} + +void log_success(const char* message) { + printf(GREEN "[SUCCESS]" RESET " %s\n", message); + fflush(stdout); +} + +void log_error(const char* message) { + printf(RED "[ERROR]" RESET " %s\n", message); + fflush(stdout); +} + +void log_warning(const char* message) { + printf(YELLOW "[WARNING]" RESET " %s\n", message); + fflush(stdout); +} + +// Signal handler for graceful shutdown +void signal_handler(int sig) { + if (sig == SIGINT || sig == SIGTERM) { + log_info("Received shutdown signal"); + g_server_running = 0; + } +} + +// Initialize database connection +int init_database() { + int rc = sqlite3_open(DATABASE_PATH, &g_db); + if (rc != SQLITE_OK) { + log_error("Cannot open database"); + return -1; + } + + log_success("Database connection established"); + return 0; +} + +// Close database connection +void close_database() { + if (g_db) { + sqlite3_close(g_db); + g_db = NULL; + log_info("Database connection closed"); + } +} + +// Store event in database +int store_event(cJSON* event) { + if (!g_db || !event) { + return -1; + } + + // Extract event fields + 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"); + cJSON* sig = cJSON_GetObjectItem(event, "sig"); + cJSON* tags = cJSON_GetObjectItem(event, "tags"); + + if (!id || !pubkey || !created_at || !kind || !content || !sig) { + log_error("Invalid event - missing required fields"); + return -1; + } + + // Prepare SQL statement for event insertion + const char* sql = + "INSERT INTO event (id, pubkey, created_at, kind, content, sig) " + "VALUES (?, ?, ?, ?, ?, ?)"; + + sqlite3_stmt* stmt; + int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + log_error("Failed to prepare event insert statement"); + return -1; + } + + // Bind parameters + sqlite3_bind_text(stmt, 1, cJSON_GetStringValue(id), -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, cJSON_GetStringValue(pubkey), -1, SQLITE_STATIC); + sqlite3_bind_int64(stmt, 3, (sqlite3_int64)cJSON_GetNumberValue(created_at)); + sqlite3_bind_int(stmt, 4, (int)cJSON_GetNumberValue(kind)); + sqlite3_bind_text(stmt, 5, cJSON_GetStringValue(content), -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 6, cJSON_GetStringValue(sig), -1, SQLITE_STATIC); + + // Execute statement + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + + if (rc != SQLITE_DONE) { + if (rc == SQLITE_CONSTRAINT) { + log_warning("Event already exists in database"); + return 0; // Not an error, just duplicate + } + char error_msg[256]; + snprintf(error_msg, sizeof(error_msg), "Failed to insert event: %s", sqlite3_errmsg(g_db)); + log_error(error_msg); + return -1; + } + + // Insert tags if present + if (tags && cJSON_IsArray(tags)) { + const char* event_id = cJSON_GetStringValue(id); + cJSON* tag; + cJSON_ArrayForEach(tag, tags) { + if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) { + cJSON* tag_name = cJSON_GetArrayItem(tag, 0); + cJSON* tag_value = cJSON_GetArrayItem(tag, 1); + + if (cJSON_IsString(tag_name) && cJSON_IsString(tag_value)) { + // Collect additional tag parameters if present + char* parameters = NULL; + if (cJSON_GetArraySize(tag) > 2) { + cJSON* params_array = cJSON_CreateArray(); + for (int i = 2; i < cJSON_GetArraySize(tag); i++) { + cJSON_AddItemToArray(params_array, cJSON_Duplicate(cJSON_GetArrayItem(tag, i), 1)); + } + parameters = cJSON_Print(params_array); + cJSON_Delete(params_array); + } + + const char* tag_sql = + "INSERT INTO tag (id, name, value, parameters) VALUES (?, ?, ?, ?)"; + + sqlite3_stmt* tag_stmt; + rc = sqlite3_prepare_v2(g_db, tag_sql, -1, &tag_stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_text(tag_stmt, 1, event_id, -1, SQLITE_STATIC); + sqlite3_bind_text(tag_stmt, 2, cJSON_GetStringValue(tag_name), -1, SQLITE_STATIC); + sqlite3_bind_text(tag_stmt, 3, cJSON_GetStringValue(tag_value), -1, SQLITE_STATIC); + sqlite3_bind_text(tag_stmt, 4, parameters, -1, SQLITE_TRANSIENT); + + sqlite3_step(tag_stmt); + sqlite3_finalize(tag_stmt); + } + + if (parameters) free(parameters); + } + } + } + } + + log_success("Event stored in database"); + return 0; +} + +// Retrieve event from database +cJSON* retrieve_event(const char* event_id) { + if (!g_db || !event_id) { + return NULL; + } + + const char* sql = + "SELECT id, pubkey, created_at, kind, content, sig FROM event WHERE id = ?"; + + sqlite3_stmt* stmt; + int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + return NULL; + } + + sqlite3_bind_text(stmt, 1, event_id, -1, SQLITE_STATIC); + + cJSON* event = NULL; + if (sqlite3_step(stmt) == SQLITE_ROW) { + event = cJSON_CreateObject(); + + cJSON_AddStringToObject(event, "id", (char*)sqlite3_column_text(stmt, 0)); + cJSON_AddStringToObject(event, "pubkey", (char*)sqlite3_column_text(stmt, 1)); + cJSON_AddNumberToObject(event, "created_at", sqlite3_column_int64(stmt, 2)); + cJSON_AddNumberToObject(event, "kind", sqlite3_column_int(stmt, 3)); + cJSON_AddStringToObject(event, "content", (char*)sqlite3_column_text(stmt, 4)); + cJSON_AddStringToObject(event, "sig", (char*)sqlite3_column_text(stmt, 5)); + + // Add tags array - retrieve from tag table + cJSON* tags_array = cJSON_CreateArray(); + + const char* tag_sql = "SELECT name, value, parameters FROM tag WHERE id = ?"; + sqlite3_stmt* tag_stmt; + if (sqlite3_prepare_v2(g_db, tag_sql, -1, &tag_stmt, NULL) == SQLITE_OK) { + sqlite3_bind_text(tag_stmt, 1, event_id, -1, SQLITE_STATIC); + + while (sqlite3_step(tag_stmt) == SQLITE_ROW) { + cJSON* tag = cJSON_CreateArray(); + cJSON_AddItemToArray(tag, cJSON_CreateString((char*)sqlite3_column_text(tag_stmt, 0))); + cJSON_AddItemToArray(tag, cJSON_CreateString((char*)sqlite3_column_text(tag_stmt, 1))); + + // Add parameters if they exist + const char* parameters = (char*)sqlite3_column_text(tag_stmt, 2); + if (parameters && strlen(parameters) > 0) { + cJSON* params = cJSON_Parse(parameters); + if (params && cJSON_IsArray(params)) { + int param_count = cJSON_GetArraySize(params); + for (int i = 0; i < param_count; i++) { + cJSON* param = cJSON_GetArrayItem(params, i); + cJSON_AddItemToArray(tag, cJSON_Duplicate(param, 1)); + } + } + if (params) cJSON_Delete(params); + } + + cJSON_AddItemToArray(tags_array, tag); + } + sqlite3_finalize(tag_stmt); + } + + cJSON_AddItemToObject(event, "tags", tags_array); + } + + sqlite3_finalize(stmt); + return event; +} + +// Handle REQ message (subscription) +int handle_req_message(const char* sub_id, cJSON* filters) { + log_info("Handling REQ message"); + + // For now, just handle simple event ID requests + if (cJSON_IsArray(filters)) { + cJSON* filter = cJSON_GetArrayItem(filters, 0); + if (filter) { + cJSON* ids = cJSON_GetObjectItem(filter, "ids"); + if (ids && cJSON_IsArray(ids)) { + cJSON* event_id = cJSON_GetArrayItem(ids, 0); + if (event_id && cJSON_IsString(event_id)) { + cJSON* event = retrieve_event(cJSON_GetStringValue(event_id)); + if (event) { + log_success("Found event for subscription"); + cJSON_Delete(event); + return 1; // Found event + } + } + } + } + } + + return 0; // No events found +} + +// Handle EVENT message (publish) +int handle_event_message(cJSON* event) { + log_info("Handling EVENT message"); + + // Validate event structure (basic check) + cJSON* id = cJSON_GetObjectItem(event, "id"); + if (!id || !cJSON_IsString(id)) { + log_error("Invalid event - no ID"); + return -1; + } + + // Store event in database + if (store_event(event) == 0) { + log_success("Event stored successfully"); + return 0; + } + + return -1; +} + +// Global WebSocket context +static struct lws_context *ws_context = NULL; + +// Per-session data structure +struct per_session_data { + int authenticated; + char subscription_id[64]; +}; + +// WebSocket callback function for Nostr relay protocol +static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) { + struct per_session_data *pss = (struct per_session_data *)user; + + switch (reason) { + case LWS_CALLBACK_ESTABLISHED: + log_info("WebSocket connection established"); + memset(pss, 0, sizeof(*pss)); + break; + + case LWS_CALLBACK_RECEIVE: + if (len > 0) { + char *message = malloc(len + 1); + if (message) { + memcpy(message, in, len); + message[len] = '\0'; + + log_info("Received WebSocket message"); + + // Parse JSON message + cJSON* json = cJSON_Parse(message); + if (json && cJSON_IsArray(json)) { + // Get message type + cJSON* type = cJSON_GetArrayItem(json, 0); + if (type && cJSON_IsString(type)) { + const char* msg_type = cJSON_GetStringValue(type); + + if (strcmp(msg_type, "EVENT") == 0) { + // Handle EVENT message + cJSON* event = cJSON_GetArrayItem(json, 1); + if (event && cJSON_IsObject(event)) { + int result = handle_event_message(event); + + // Send OK response + cJSON* event_id = cJSON_GetObjectItem(event, "id"); + if (event_id && cJSON_IsString(event_id)) { + cJSON* response = cJSON_CreateArray(); + cJSON_AddItemToArray(response, cJSON_CreateString("OK")); + cJSON_AddItemToArray(response, cJSON_CreateString(cJSON_GetStringValue(event_id))); + cJSON_AddItemToArray(response, cJSON_CreateBool(result == 0)); + cJSON_AddItemToArray(response, cJSON_CreateString(result == 0 ? "" : "error: failed to store event")); + + char *response_str = cJSON_Print(response); + if (response_str) { + size_t response_len = strlen(response_str); + unsigned char *buf = malloc(LWS_PRE + response_len); + if (buf) { + memcpy(buf + LWS_PRE, response_str, response_len); + lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT); + free(buf); + } + free(response_str); + } + cJSON_Delete(response); + } + } + } else if (strcmp(msg_type, "REQ") == 0) { + // Handle REQ message + cJSON* sub_id = cJSON_GetArrayItem(json, 1); + cJSON* filters = cJSON_GetArrayItem(json, 2); + + if (sub_id && cJSON_IsString(sub_id)) { + const char* subscription_id = cJSON_GetStringValue(sub_id); + strncpy(pss->subscription_id, subscription_id, sizeof(pss->subscription_id) - 1); + + handle_req_message(subscription_id, filters); + + // Send EOSE (End of Stored Events) + cJSON* eose_response = cJSON_CreateArray(); + cJSON_AddItemToArray(eose_response, cJSON_CreateString("EOSE")); + cJSON_AddItemToArray(eose_response, cJSON_CreateString(subscription_id)); + + char *eose_str = cJSON_Print(eose_response); + if (eose_str) { + size_t eose_len = strlen(eose_str); + unsigned char *buf = malloc(LWS_PRE + eose_len); + if (buf) { + memcpy(buf + LWS_PRE, eose_str, eose_len); + lws_write(wsi, buf + LWS_PRE, eose_len, LWS_WRITE_TEXT); + free(buf); + } + free(eose_str); + } + cJSON_Delete(eose_response); + } + } else if (strcmp(msg_type, "CLOSE") == 0) { + // Handle CLOSE message + log_info("Subscription closed"); + } + } + } + + if (json) cJSON_Delete(json); + free(message); + } + } + break; + + case LWS_CALLBACK_CLOSED: + log_info("WebSocket connection closed"); + break; + + default: + break; + } + + return 0; +} + +// WebSocket protocol definition +static struct lws_protocols protocols[] = { + { + "nostr-relay-protocol", + nostr_relay_callback, + sizeof(struct per_session_data), + 4096, // rx buffer size + 0, NULL, 0 + }, + { NULL, NULL, 0, 0, 0, NULL, 0 } // terminator +}; + +// Start libwebsockets-based WebSocket Nostr relay server +int start_websocket_relay() { + struct lws_context_creation_info info; + + log_info("Starting libwebsockets-based Nostr relay server..."); + + memset(&info, 0, sizeof(info)); + info.port = DEFAULT_PORT; + info.protocols = protocols; + info.gid = -1; + info.uid = -1; + + // Minimal libwebsockets configuration + info.options = LWS_SERVER_OPTION_VALIDATE_UTF8; + + // Remove interface restrictions - let system choose + // info.vhost_name = NULL; + // info.iface = NULL; + + // Increase max connections for relay usage + info.max_http_header_pool = 16; + info.timeout_secs = 10; + + // Max payload size for Nostr events + info.max_http_header_data = 4096; + + ws_context = lws_create_context(&info); + if (!ws_context) { + log_error("Failed to create libwebsockets context"); + perror("libwebsockets creation error"); + return -1; + } + + log_success("WebSocket relay started on ws://127.0.0.1:8888"); + + // Main event loop with proper signal handling + fd_set rfds; + struct timeval tv; + + while (g_server_running) { + FD_ZERO(&rfds); + tv.tv_sec = 1; + tv.tv_usec = 0; + + int result = lws_service(ws_context, 1000); + + if (result < 0) { + log_error("libwebsockets service error"); + break; + } + } + + log_info("Shutting down WebSocket server..."); + lws_context_destroy(ws_context); + ws_context = NULL; + + log_success("WebSocket relay shut down cleanly"); + return 0; +} + +// Print usage information +void print_usage(const char* program_name) { + printf("Usage: %s [OPTIONS]\n", program_name); + printf("\n"); + printf("C Nostr Relay Server\n"); + printf("\n"); + printf("Options:\n"); + printf(" -p, --port PORT Listen port (default: %d)\n", DEFAULT_PORT); + printf(" -h, --help Show this help message\n"); + printf("\n"); +} + +int main(int argc, char* argv[]) { + int port = DEFAULT_PORT; + + // Parse command line arguments + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { + print_usage(argv[0]); + return 0; + } else if (strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--port") == 0) { + if (i + 1 < argc) { + port = atoi(argv[++i]); + if (port <= 0 || port > 65535) { + log_error("Invalid port number"); + return 1; + } + } else { + log_error("Port argument requires a value"); + return 1; + } + } else { + log_error("Unknown argument"); + print_usage(argv[0]); + return 1; + } + } + + // Set up signal handlers + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + printf(BLUE BOLD "=== C Nostr Relay Server ===" RESET "\n"); + + // Initialize database + if (init_database() != 0) { + log_error("Failed to initialize database"); + return 1; + } + + // Initialize nostr library + if (nostr_init() != 0) { + log_error("Failed to initialize nostr library"); + close_database(); + return 1; + } + + log_info("Starting relay server..."); + + // Start WebSocket Nostr relay server + int result = start_websocket_relay(); + + // Cleanup + nostr_cleanup(); + close_database(); + + if (result == 0) { + log_success("Server shutdown complete"); + } else { + log_error("Server shutdown with errors"); + } + + return result; +} \ No newline at end of file diff --git a/tests/1_nip_test.sh b/tests/1_nip_test.sh new file mode 100755 index 0000000..dc7774c --- /dev/null +++ b/tests/1_nip_test.sh @@ -0,0 +1,112 @@ +#!/bin/bash + +# Simple C-Relay Test - Create type 1 event and upload to relay +# Uses nak to generate and publish a single event + +set -e # Exit on any error + +# Color constants +RED='\033[31m' +GREEN='\033[32m' +YELLOW='\033[33m' +BLUE='\033[34m' +BOLD='\033[1m' +RESET='\033[0m' + +# Test configuration +RELAY_URL="ws://127.0.0.1:8888" +TEST_PRIVATE_KEY="nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99" +TEST_CONTENT="Hello from C-Relay test!" + +# Print functions +print_header() { + echo -e "${BLUE}${BOLD}=== $1 ===${RESET}" +} + +print_step() { + echo -e "${YELLOW}[STEP]${RESET} $1" +} + +print_success() { + echo -e "${GREEN}✓${RESET} $1" +} + +print_error() { + echo -e "${RED}✗${RESET} $1" +} + +print_info() { + echo -e "${BLUE}[INFO]${RESET} $1" +} + +# Main test function +run_test() { + print_header "C-Relay Simple Test" + + # Check if nak is available + print_step "Checking dependencies..." + if ! command -v nak &> /dev/null; then + print_error "nak command not found" + print_info "Please install nak: go install github.com/fiatjaf/nak@latest" + return 1 + fi + print_success "nak found" + + # Step 1: Create type 1 event with nak including tags + print_step "Creating type 1 event with nak and tags..." + + local event_json + if ! event_json=$(nak event --sec "$TEST_PRIVATE_KEY" -c "$TEST_CONTENT" -k 1 --ts $(date +%s) -e "test_event_id" -p "test_pubkey" -t "subject=Test Event" 2>/dev/null); then + print_error "Failed to generate event with nak" + return 1 + fi + + print_success "Event created successfully" + print_header "FULL EVENT JSON" + echo "$event_json" | jq . 2>/dev/null || echo "$event_json" + echo + + # Step 2: Upload to C-Relay + print_step "Uploading event to C-Relay at $RELAY_URL..." + + # Create EVENT message in Nostr format + local event_message="[\"EVENT\",$event_json]" + + # Use websocat or wscat to send to relay if available + local response="" + if command -v websocat &> /dev/null; then + print_info "Using websocat to connect to relay..." + response=$(echo "$event_message" | timeout 5s websocat "$RELAY_URL" 2>&1 || echo "Connection failed") + elif command -v wscat &> /dev/null; then + print_info "Using wscat to connect to relay..." + response=$(echo "$event_message" | timeout 5s wscat -c "$RELAY_URL" 2>&1 || echo "Connection failed") + else + # Fallback: use nak publish + print_info "Using nak to publish event..." + response=$(echo "$event_json" | nak event --relay "$RELAY_URL" 2>&1 || echo "Publish failed") + fi + + print_header "FULL RELAY RESPONSE" + echo "$response" + echo + + if [[ "$response" == *"Connection failed"* ]] || [[ "$response" == *"Publish failed"* ]]; then + print_error "Failed to connect to relay or publish event" + print_info "Make sure the relay is running: ./make_and_restart_relay.sh" + return 1 + else + print_success "Event uploaded to relay" + return 0 + fi +} + +# Run the test +if run_test; then + echo + print_success "Test completed successfully" + exit 0 +else + echo + print_error "Test failed" + exit 1 +fi \ No newline at end of file