## nimpak/remote/sync_engine.nim ## Synchronization engine with bloom filters for NimPak ## ## This module implements Task 15.1d: ## - Incremental sync using event log from Task 11 integrity monitor ## - Bloom filter handshake for O(changes) synchronization ## - Delta object creation and application for bandwidth optimization ## - Mirror network support with load balancing and failover ## - Bandwidth management and compression import std/[os, times, json, tables, sequtils, strutils, strformat, asyncdispatch, algorithm, hashes, sets, math, random, httpclient, options] import ../security/[event_logger, integrity_monitor] import ../cas import ../types_fixed import manager type BloomFilter* = object bits*: seq[bool] size*: int hashFunctions*: int expectedElements*: int falsePositiveRate*: float SyncEventType* = enum SyncPackageAdded = "package_added" SyncPackageRemoved = "package_removed" SyncPackageUpdated = "package_updated" SyncManifestChanged = "manifest_changed" SyncKeyRevoked = "key_revoked" SyncKeyRolledOver = "key_rolled_over" SyncEvent* = object id*: string timestamp*: times.DateTime eventType*: SyncEventType objectHash*: string # CAS hash of affected object metadata*: JsonNode sequenceNumber*: int64 DeltaObject* = object objectHash*: string deltaType*: string # "add", "remove", "modify" compressedData*: seq[byte] originalSize*: int64 compressedSize*: int64 dependencies*: seq[string] # Hashes of dependent objects SyncState* = object lastSyncTime*: times.DateTime lastSequenceNumber*: int64 bloomFilter*: BloomFilter knownObjects*: HashSet[string] pendingDeltas*: seq[DeltaObject] MirrorNode* = object id*: string url*: string priority*: int # Higher = preferred latency*: float # Average response time in ms reliability*: float # Success rate (0.0-1.0) bandwidth*: int64 # Available bandwidth in bytes/sec lastSync*: times.DateTime status*: MirrorStatus syncState*: SyncState MirrorStatus* = enum MirrorActive = "active" MirrorSlow = "slow" MirrorUnreachable = "unreachable" MirrorSyncing = "syncing" SyncEngine* = ref object localCasManager*: CasManager eventLogger*: SecurityEventLogger mirrors*: Table[string, MirrorNode] activeMirror*: string config*: SyncEngineConfig syncState*: SyncState bandwidthLimiter*: BandwidthLimiter SyncEngineConfig* = object maxMirrors*: int syncIntervalSeconds*: int bloomFilterSize*: int bloomFilterHashFunctions*: int maxDeltaSize*: int64 # Maximum delta object size compressionLevel*: int # zstd compression level bandwidthLimitBps*: int64 # Bandwidth limit in bytes per second failoverTimeoutMs*: int # Timeout before failover maxConcurrentSyncs*: int BandwidthLimiter* = ref object limitBps*: int64 currentUsage*: int64 windowStart*: times.DateTime windowSizeMs*: int SyncResult*[T] = object case success*: bool of true: value*: T bytesTransferred*: int64 duration*: float of false: error*: string errorCode*: int # ============================================================================= # Bloom Filter Implementation # ============================================================================= proc newBloomFilter*(expectedElements: int, falsePositiveRate: float = 0.01): BloomFilter = ## Create a new bloom filter optimized for the expected number of elements let size = int(-1.0 * float(expectedElements) * ln(falsePositiveRate) / (ln(2.0) * ln(2.0))) let hashFunctions = int(float(size) / float(expectedElements) * ln(2.0)) BloomFilter( bits: newSeq[bool](size), size: size, hashFunctions: max(1, hashFunctions), expectedElements: expectedElements, falsePositiveRate: falsePositiveRate ) proc hash1(data: string): uint32 = ## First hash function (FNV-1a variant) var hash: uint32 = 2166136261'u32 for c in data: hash = hash xor uint32(c) hash = hash * 16777619'u32 return hash proc hash2(data: string): uint32 = ## Second hash function (djb2 variant) var hash: uint32 = 5381'u32 for c in data: hash = ((hash shl 5) + hash) + uint32(c) return hash proc getHashValues(data: string, numHashes: int, size: int): seq[int] = ## Generate multiple hash values using double hashing let h1 = hash1(data) let h2 = hash2(data) result = newSeq[int](numHashes) for i in 0.. data.len: raise newException(ValueError, "Data too short") let size = cast[ptr int](unsafeAddr data[offset])[] offset += sizeof(int) if offset + sizeof(int) > data.len: raise newException(ValueError, "Data too short") let hashFunctions = cast[ptr int](unsafeAddr data[offset])[] offset += sizeof(int) if offset + sizeof(int) > data.len: raise newException(ValueError, "Data too short") let expectedElements = cast[ptr int](unsafeAddr data[offset])[] offset += sizeof(int) # Unpack bits var bits = newSeq[bool](size) let numBytes = (size + 7) div 8 for i in 0..= limiter.windowSizeMs: limiter.currentUsage = 0 limiter.windowStart = currentTime # Check if request would exceed limit if limiter.currentUsage + requestedBytes > limiter.limitBps: return false limiter.currentUsage += requestedBytes return true proc waitForBandwidth*(limiter: BandwidthLimiter, requestedBytes: int64) {.async.} = ## Wait until bandwidth is available for the requested transfer while not limiter.checkBandwidth(requestedBytes): await sleepAsync(100) # Wait 100ms and try again # ============================================================================= # Sync Engine Initialization # ============================================================================= proc newSyncEngine*(casManager: CasManager, eventLogger: SecurityEventLogger, config: SyncEngineConfig): SyncEngine = ## Create a new synchronization engine let syncState = SyncState( lastSyncTime: default(times.DateTime), lastSequenceNumber: 0, bloomFilter: newBloomFilter(config.bloomFilterSize), knownObjects: initHashSet[string](), pendingDeltas: @[] ) SyncEngine( localCasManager: casManager, eventLogger: eventLogger, mirrors: initTable[string, MirrorNode](), activeMirror: "", config: config, syncState: syncState, bandwidthLimiter: newBandwidthLimiter(config.bandwidthLimitBps) ) proc getDefaultSyncEngineConfig*(): SyncEngineConfig = ## Get default synchronization engine configuration SyncEngineConfig( maxMirrors: 10, syncIntervalSeconds: 300, # 5 minutes bloomFilterSize: 100000, # 100k expected objects bloomFilterHashFunctions: 7, maxDeltaSize: 100 * 1024 * 1024, # 100MB compressionLevel: 3, # Balanced compression bandwidthLimitBps: 10 * 1024 * 1024, # 10MB/s failoverTimeoutMs: 5000, # 5 seconds maxConcurrentSyncs: 3 ) # ============================================================================= # Mirror Management # ============================================================================= proc addMirror*(engine: SyncEngine, id: string, url: string, priority: int = 50): SyncResult[MirrorNode] = ## Add a new mirror node to the sync engine try: if engine.mirrors.len >= engine.config.maxMirrors: return SyncResult[MirrorNode](success: false, error: "Maximum mirrors reached", errorCode: 429) if id in engine.mirrors: return SyncResult[MirrorNode](success: false, error: fmt"Mirror already exists: {id}", errorCode: 409) let mirror = MirrorNode( id: id, url: url, priority: priority, latency: 0.0, reliability: 1.0, # Start with perfect reliability bandwidth: 0, lastSync: default(times.DateTime), status: MirrorActive, syncState: SyncState( lastSyncTime: default(times.DateTime), lastSequenceNumber: 0, bloomFilter: newBloomFilter(engine.config.bloomFilterSize), knownObjects: initHashSet[string](), pendingDeltas: @[] ) ) engine.mirrors[id] = mirror # Set as active mirror if it's the first one or has higher priority if engine.activeMirror == "" or priority > engine.mirrors[engine.activeMirror].priority: engine.activeMirror = id logGlobalSecurityEvent(EventSystemStartup, SeverityInfo, "sync-engine", fmt"Mirror added: {id} ({url}) priority={priority}") return SyncResult[MirrorNode](success: true, value: mirror, bytesTransferred: 0, duration: 0.0) except Exception as e: return SyncResult[MirrorNode](success: false, error: fmt"Failed to add mirror: {e.msg}", errorCode: 500) proc removeMirror*(engine: SyncEngine, id: string): SyncResult[bool] = ## Remove a mirror node from the sync engine try: if id notin engine.mirrors: return SyncResult[bool](success: false, error: fmt"Mirror not found: {id}", errorCode: 404) engine.mirrors.del(id) # Find new active mirror if we removed the active one if engine.activeMirror == id: engine.activeMirror = "" var bestPriority = -1 for mirrorId, mirror in engine.mirrors.pairs: if mirror.status == MirrorActive and mirror.priority > bestPriority: engine.activeMirror = mirrorId bestPriority = mirror.priority logGlobalSecurityEvent(EventSystemStartup, SeverityInfo, "sync-engine", fmt"Mirror removed: {id}") return SyncResult[bool](success: true, value: true, bytesTransferred: 0, duration: 0.0) except Exception as e: return SyncResult[bool](success: false, error: fmt"Failed to remove mirror: {e.msg}", errorCode: 500) proc selectBestMirror*(engine: SyncEngine): Option[MirrorNode] = ## Select the best available mirror based on priority, latency, and reliability var bestMirror: Option[MirrorNode] = none(MirrorNode) var bestScore = -1.0 for mirror in engine.mirrors.values: if mirror.status != MirrorActive: continue # Calculate composite score: priority * reliability / (1 + latency) let score = float(mirror.priority) * mirror.reliability / (1.0 + mirror.latency / 1000.0) if score > bestScore: bestScore = score bestMirror = some(mirror) return bestMirror # ============================================================================= # Event Log Integration # ============================================================================= proc extractSyncEventsFromSecurityLog*(engine: SyncEngine, since: times.DateTime): seq[SyncEvent] = ## Extract synchronization-relevant events from the security event log var syncEvents: seq[SyncEvent] = @[] try: # Read security events since the last sync let securityEvents = engine.eventLogger.auditSecurityLog(since, now()) var sequenceNumber = engine.syncState.lastSequenceNumber + 1 for secEvent in securityEvents: var syncEvent: Option[SyncEvent] = none(SyncEvent) case secEvent.eventType: of EventPackageVerification: # Package was verified - might indicate new package if secEvent.metadata.hasKey("package_hash"): syncEvent = some(SyncEvent( id: secEvent.id, timestamp: secEvent.timestamp, eventType: SyncPackageUpdated, objectHash: secEvent.metadata["package_hash"].getStr(), metadata: secEvent.metadata, sequenceNumber: sequenceNumber )) of EventKeyRevocation: # Key was revoked - affects package trust syncEvent = some(SyncEvent( id: secEvent.id, timestamp: secEvent.timestamp, eventType: SyncKeyRevoked, objectHash: secEvent.metadata.getOrDefault("key_id").getStr(""), metadata: secEvent.metadata, sequenceNumber: sequenceNumber )) of EventKeyRollover: # Key was rolled over - affects package signatures syncEvent = some(SyncEvent( id: secEvent.id, timestamp: secEvent.timestamp, eventType: SyncKeyRolledOver, objectHash: secEvent.metadata.getOrDefault("new_key_id").getStr(""), metadata: secEvent.metadata, sequenceNumber: sequenceNumber )) else: # Other events might be relevant in the future discard if syncEvent.isSome(): syncEvents.add(syncEvent.get()) inc sequenceNumber except Exception as e: logGlobalSecurityEvent(EventSecurityIncident, SeverityError, "sync-engine", fmt"Failed to extract sync events: {e.msg}") return syncEvents proc updateBloomFilterFromEvents*(engine: SyncEngine, events: seq[SyncEvent]) = ## Update the local bloom filter based on sync events for event in events: case event.eventType: of SyncPackageAdded, SyncPackageUpdated: engine.syncState.bloomFilter.add(event.objectHash) engine.syncState.knownObjects.incl(event.objectHash) of SyncPackageRemoved: # Note: We can't remove from bloom filter, but we can remove from known objects engine.syncState.knownObjects.excl(event.objectHash) of SyncKeyRevoked, SyncKeyRolledOver: # These affect trust but don't directly change object presence discard of SyncManifestChanged: engine.syncState.bloomFilter.add(event.objectHash) engine.syncState.knownObjects.incl(event.objectHash) # ============================================================================= # Bloom Filter Handshake Protocol # ============================================================================= proc performBloomFilterHandshake*(engine: SyncEngine, mirror: MirrorNode): Future[SyncResult[seq[string]]] {.async.} = ## Perform bloom filter handshake to identify objects that need synchronization let startTime = cpuTime() try: # Serialize our bloom filter let localBloomData = engine.syncState.bloomFilter.serialize() # Wait for bandwidth availability await engine.bandwidthLimiter.waitForBandwidth(int64(localBloomData.len)) # Send bloom filter to mirror let client = newAsyncHttpClient() let handshakeUrl = mirror.url / "api/v1/sync/bloom-handshake" let response = await client.post(handshakeUrl, body = $localBloomData) let responseData = await response.body if response.code != Http200: return SyncResult[seq[string]]( success: false, error: fmt"Handshake failed: HTTP {response.code}", errorCode: response.code.int ) # Parse response to get list of objects we don't have let responseJson = parseJson(responseData) let missingObjects = responseJson["missing_objects"].getElems().mapIt(it.getStr()) let remoteBloomData = responseJson["remote_bloom_filter"].getStr() # Deserialize remote bloom filter let remoteBloomFilter = deserializeBloomFilter(cast[seq[byte]](remoteBloomData)) # Find objects we have that the remote doesn't var objectsToSend: seq[string] = @[] for objectHash in engine.syncState.knownObjects: if not remoteBloomFilter.contains(objectHash): objectsToSend.add(objectHash) # Log handshake results logGlobalSecurityEvent(EventSystemHealthCheck, SeverityInfo, "sync-engine", fmt"Bloom filter handshake with {mirror.id}: {missingObjects.len} to receive, {objectsToSend.len} to send") let duration = cpuTime() - startTime return SyncResult[seq[string]]( success: true, value: missingObjects, bytesTransferred: int64(localBloomData.len + responseData.len), duration: duration ) except Exception as e: let duration = cpuTime() - startTime return SyncResult[seq[string]]( success: false, error: fmt"Handshake error: {e.msg}", errorCode: 500 ) # ============================================================================= # Delta Object Creation and Application # ============================================================================= proc compressZstd*(data: seq[byte], level: int): seq[byte] = ## Compress data using zstd (placeholder implementation) # TODO: Implement actual zstd compression when library is available # For now, return original data with a simple marker result = @[0xFF'u8, 0xFE'u8] & data # Marker for "compressed" proc decompressZstd*(data: seq[byte]): seq[byte] = ## Decompress zstd data (placeholder implementation) # TODO: Implement actual zstd decompression when library is available # For now, check for marker and return data without it if data.len >= 2 and data[0] == 0xFF'u8 and data[1] == 0xFE'u8: return data[2..^1] else: return data proc createDeltaObject*(engine: SyncEngine, objectHash: string): SyncResult[DeltaObject] = ## Create a delta object for efficient transmission try: # Retrieve object from local CAS let objectResult = engine.localCasManager.retrieveObject(objectHash) if not objectResult.isOk: return SyncResult[DeltaObject]( success: false, error: fmt"Object not found in CAS: {objectHash}", errorCode: 404 ) let originalData = objectResult.okValue let originalSize = int64(originalData.len) # Compress the data using zstd let compressedData = compressZstd(originalData, engine.config.compressionLevel) let compressedSize = int64(compressedData.len) # Check if delta would be too large if compressedSize > engine.config.maxDeltaSize: return SyncResult[DeltaObject]( success: false, error: fmt"Delta object too large: {compressedSize} > {engine.config.maxDeltaSize}", errorCode: 413 ) let deltaObject = DeltaObject( objectHash: objectHash, deltaType: "add", # For now, we only support full object transmission compressedData: compressedData, originalSize: originalSize, compressedSize: compressedSize, dependencies: @[] # TODO: Implement dependency tracking ) return SyncResult[DeltaObject]( success: true, value: deltaObject, bytesTransferred: compressedSize, duration: 0.0 ) except Exception as e: return SyncResult[DeltaObject]( success: false, error: fmt"Failed to create delta object: {e.msg}", errorCode: 500 ) proc applyDeltaObject*(engine: SyncEngine, delta: DeltaObject): SyncResult[bool] = ## Apply a delta object to the local CAS try: # Decompress the data let originalData = decompressZstd(delta.compressedData) # Verify the hash matches let computedHash = engine.localCasManager.computeHash(originalData) if computedHash != delta.objectHash: return SyncResult[bool]( success: false, error: fmt"Hash mismatch: expected {delta.objectHash}, got {computedHash}", errorCode: 400 ) # Store in local CAS let storeResult = engine.localCasManager.storeObject(originalData) if not storeResult.isOk: return SyncResult[bool]( success: false, error: fmt"Failed to store object: {storeResult.errValue.msg}", errorCode: 500 ) # Update local state engine.syncState.knownObjects.incl(delta.objectHash) engine.syncState.bloomFilter.add(delta.objectHash) return SyncResult[bool]( success: true, value: true, bytesTransferred: delta.compressedSize, duration: 0.0 ) except Exception as e: return SyncResult[bool]( success: false, error: fmt"Failed to apply delta object: {e.msg}", errorCode: 500 ) # ============================================================================= # Incremental Synchronization # ============================================================================= proc performIncrementalSync*(engine: SyncEngine, mirrorId: string): Future[SyncResult[int]] {.async.} = ## Perform incremental synchronization with a specific mirror let startTime = cpuTime() var totalBytesTransferred: int64 = 0 try: if mirrorId notin engine.mirrors: return SyncResult[int]( success: false, error: fmt"Mirror not found: {mirrorId}", errorCode: 404 ) var mirror = engine.mirrors[mirrorId] mirror.status = MirrorSyncing # Extract events since last sync let lastSyncTime = if mirror.syncState.lastSyncTime == default(times.DateTime): now() - initDuration(hours = 24) # Default to last 24 hours else: mirror.syncState.lastSyncTime let syncEvents = engine.extractSyncEventsFromSecurityLog(lastSyncTime) if syncEvents.len == 0: # No changes to sync mirror.status = MirrorActive mirror.lastSync = now() engine.mirrors[mirrorId] = mirror return SyncResult[int]( success: true, value: 0, bytesTransferred: 0, duration: cpuTime() - startTime ) # Update local bloom filter with recent events engine.updateBloomFilterFromEvents(syncEvents) # Perform bloom filter handshake let handshakeResult = await engine.performBloomFilterHandshake(mirror) if not handshakeResult.success: mirror.status = MirrorUnreachable engine.mirrors[mirrorId] = mirror return SyncResult[int]( success: false, error: fmt"Handshake failed: {handshakeResult.error}", errorCode: handshakeResult.errorCode ) totalBytesTransferred += handshakeResult.bytesTransferred let missingObjects = handshakeResult.value # Create and send delta objects for missing objects var syncedObjects = 0 for objectHash in missingObjects: # Check bandwidth limit await engine.bandwidthLimiter.waitForBandwidth(engine.config.maxDeltaSize) let deltaResult = engine.createDeltaObject(objectHash) if not deltaResult.success: logGlobalSecurityEvent(EventSecurityIncident, SeverityWarning, "sync-engine", fmt"Failed to create delta for {objectHash}: {deltaResult.error}") continue let delta = deltaResult.value # Send delta to mirror let client = newAsyncHttpClient() let deltaUrl = mirror.url / "api/v1/sync/delta" let deltaJson = %*{ "object_hash": delta.objectHash, "delta_type": delta.deltaType, "compressed_data": delta.compressedData, "original_size": delta.originalSize, "compressed_size": delta.compressedSize } let response = await client.post(deltaUrl, body = $deltaJson) if response.code == Http200: inc syncedObjects totalBytesTransferred += delta.compressedSize else: logGlobalSecurityEvent(EventSecurityIncident, SeverityWarning, "sync-engine", fmt"Failed to send delta {objectHash} to {mirrorId}: HTTP {response.code}") # Update mirror state mirror.status = MirrorActive mirror.lastSync = now() mirror.syncState.lastSyncTime = now() mirror.syncState.lastSequenceNumber = if syncEvents.len > 0: syncEvents[^1].sequenceNumber else: mirror.syncState.lastSequenceNumber engine.mirrors[mirrorId] = mirror # Update engine state engine.syncState.lastSyncTime = now() if syncEvents.len > 0: engine.syncState.lastSequenceNumber = syncEvents[^1].sequenceNumber logGlobalSecurityEvent(EventSystemHealthCheck, SeverityInfo, "sync-engine", fmt"Incremental sync with {mirrorId} completed: {syncedObjects} objects, {totalBytesTransferred} bytes") let duration = cpuTime() - startTime return SyncResult[int]( success: true, value: syncedObjects, bytesTransferred: totalBytesTransferred, duration: duration ) except Exception as e: let duration = cpuTime() - startTime return SyncResult[int]( success: false, error: fmt"Incremental sync failed: {e.msg}", errorCode: 500 ) # ============================================================================= # Mirror Network with Load Balancing and Failover # ============================================================================= proc updateMirrorHealth*(engine: SyncEngine, mirrorId: string, latency: float, success: bool) = ## Update mirror health metrics for load balancing decisions if mirrorId notin engine.mirrors: return var mirror = engine.mirrors[mirrorId] # Update latency with exponential moving average if mirror.latency == 0.0: mirror.latency = latency else: mirror.latency = 0.7 * mirror.latency + 0.3 * latency # Update reliability with exponential moving average let successRate = if success: 1.0 else: 0.0 mirror.reliability = 0.9 * mirror.reliability + 0.1 * successRate # Update status based on health if mirror.reliability < 0.5: mirror.status = MirrorUnreachable elif mirror.latency > 5000.0: # 5 seconds mirror.status = MirrorSlow else: mirror.status = MirrorActive engine.mirrors[mirrorId] = mirror proc performFailover*(engine: SyncEngine): Option[string] = ## Perform failover to the next best available mirror let currentMirror = engine.activeMirror # Find the best alternative mirror let bestMirror = engine.selectBestMirror() if bestMirror.isNone(): return none(string) let newActiveMirror = bestMirror.get().id if newActiveMirror != currentMirror: engine.activeMirror = newActiveMirror logGlobalSecurityEvent(EventSystemHealthCheck, SeverityWarning, "sync-engine", fmt"Failover from {currentMirror} to {newActiveMirror}") return some(newActiveMirror) return none(string) proc syncWithLoadBalancing*(engine: SyncEngine): Future[SyncResult[int]] {.async.} = ## Perform synchronization with automatic load balancing and failover var totalSynced = 0 var totalBytesTransferred: int64 = 0 let startTime = cpuTime() try: # Get list of active mirrors sorted by priority and health var availableMirrors = engine.mirrors.values.toSeq .filterIt(it.status == MirrorActive) .sortedByIt(-it.priority) if availableMirrors.len == 0: return SyncResult[int]( success: false, error: "No active mirrors available", errorCode: 503 ) # Try to sync with mirrors in order of preference for mirror in availableMirrors: let syncStartTime = cpuTime() try: let syncResult = await engine.performIncrementalSync(mirror.id) let syncDuration = cpuTime() - syncStartTime if syncResult.success: totalSynced += syncResult.value totalBytesTransferred += syncResult.bytesTransferred # Update mirror health with successful sync engine.updateMirrorHealth(mirror.id, syncDuration * 1000.0, true) # If we successfully synced with this mirror, we can stop break else: # Update mirror health with failed sync engine.updateMirrorHealth(mirror.id, syncDuration * 1000.0, false) logGlobalSecurityEvent(EventSecurityIncident, SeverityWarning, "sync-engine", fmt"Sync failed with {mirror.id}: {syncResult.error}") except Exception as e: # Update mirror health with exception engine.updateMirrorHealth(mirror.id, (cpuTime() - syncStartTime) * 1000.0, false) logGlobalSecurityEvent(EventSecurityIncident, SeverityError, "sync-engine", fmt"Sync exception with {mirror.id}: {e.msg}") # Perform failover if needed if totalSynced == 0: let failoverResult = engine.performFailover() if failoverResult.isSome(): # Try once more with the new active mirror let newMirror = failoverResult.get() let syncResult = await engine.performIncrementalSync(newMirror) if syncResult.success: totalSynced += syncResult.value totalBytesTransferred += syncResult.bytesTransferred let duration = cpuTime() - startTime if totalSynced > 0: return SyncResult[int]( success: true, value: totalSynced, bytesTransferred: totalBytesTransferred, duration: duration ) else: return SyncResult[int]( success: false, error: "No objects synchronized (or all mirrors failed)", errorCode: 503 ) except Exception as e: let duration = cpuTime() - startTime return SyncResult[int]( success: false, error: fmt"Load balanced sync failed: {e.msg}", errorCode: 500 ) # ============================================================================= # Automatic Synchronization Daemon # ============================================================================= proc startSyncDaemon*(engine: SyncEngine) {.async.} = ## Start the automatic synchronization daemon logGlobalSecurityEvent(EventSystemStartup, SeverityInfo, "sync-engine", fmt"Starting sync daemon (interval: {engine.config.syncIntervalSeconds}s)") while true: try: let syncResult = await engine.syncWithLoadBalancing() if syncResult.success: logGlobalSecurityEvent(EventSystemHealthCheck, SeverityInfo, "sync-engine", fmt"Automatic sync completed: {syncResult.value} objects, {syncResult.bytesTransferred} bytes") else: logGlobalSecurityEvent(EventSecurityIncident, SeverityError, "sync-engine", fmt"Automatic sync failed: {syncResult.error}") # Wait for next sync interval await sleepAsync(engine.config.syncIntervalSeconds * 1000) except Exception as e: logGlobalSecurityEvent(EventSecurityIncident, SeverityError, "sync-engine", fmt"Sync daemon error: {e.msg}") # Wait longer on error to avoid spam await sleepAsync(60000) # 1 minute # ============================================================================= # CLI Integration # ============================================================================= proc nipSyncCommand*(target: string = "all", force: bool = false): Future[SyncResult[int]] {.async.} = ## Implement nip sync command try: # Initialize sync engine let casManager = newCasManager("~/.nip/cas", "/var/lib/nip/cas") let eventLogger = globalSecurityLogger # Use global logger let config = getDefaultSyncEngineConfig() var engine = newSyncEngine(casManager, eventLogger, config) # Add default mirrors (would normally be loaded from config) discard engine.addMirror("official", "https://packages.nexusos.org", 100) discard engine.addMirror("community", "https://community.nexusos.org", 50) if target == "all": # Sync with all mirrors using load balancing return await engine.syncWithLoadBalancing() else: # Sync with specific mirror return await engine.performIncrementalSync(target) except Exception as e: return SyncResult[int]( success: false, error: fmt"Sync command failed: {e.msg}", errorCode: 500 ) # ============================================================================= # Export main functions # ============================================================================= export BloomFilter, SyncEventType, SyncEvent, DeltaObject, SyncState export MirrorNode, MirrorStatus, SyncEngine, SyncEngineConfig export BandwidthLimiter, SyncResult export newBloomFilter, add, contains, serialize, deserializeBloomFilter export newBandwidthLimiter, checkBandwidth, waitForBandwidth export newSyncEngine, getDefaultSyncEngineConfig export addMirror, removeMirror, selectBestMirror export extractSyncEventsFromSecurityLog, updateBloomFilterFromEvents export performBloomFilterHandshake, createDeltaObject, applyDeltaObject export performIncrementalSync, syncWithLoadBalancing export updateMirrorHealth, performFailover export startSyncDaemon, nipSyncCommand