diff --git a/macos/Sources/HypoApp/Services/SyncEngine.swift b/macos/Sources/HypoApp/Services/SyncEngine.swift index a4a1682..d9cb6fc 100644 --- a/macos/Sources/HypoApp/Services/SyncEngine.swift +++ b/macos/Sources/HypoApp/Services/SyncEngine.swift @@ -3,13 +3,28 @@ import Foundation // Helper function to add base64 padding if missing (Android uses Base64.withoutPadding()) private func addBase64Padding(_ base64: String) -> String { let remainder = base64.count % 4 - if remainder == 0 { - return base64 - } + guard remainder != 0 else { return base64 } let padding = String(repeating: "=", count: 4 - remainder) return base64 + padding } +private func decodeBase64Field( + _ string: String, + forKey key: T, + in container: KeyedDecodingContainer +) throws -> Data { + guard !string.isEmpty else { return Data() } + let padded = addBase64Padding(string) + guard let data = Data(base64Encoded: padded) else { + throw DecodingError.dataCorruptedError( + forKey: key, + in: container, + debugDescription: "Invalid Base64 string for \(key.stringValue)" + ) + } + return data +} + public struct SyncEnvelope: Codable { public let id: UUID public let timestamp: Date @@ -65,23 +80,8 @@ public struct SyncEnvelope: Codable { let container = try decoder.container(keyedBy: CodingKeys.self) contentType = try container.decode(ClipboardPayload.ContentType.self, forKey: .contentType) - // Decode ciphertext from base64 string (Android uses standard base64 without padding) let ciphertextString = try container.decode(String.self, forKey: .ciphertext) - // Android uses Base64.withoutPadding(), so we need to add padding if missing - let remainder = ciphertextString.count % 4 - let paddedBase64 = remainder == 0 ? ciphertextString : ciphertextString + String(repeating: "=", count: 4 - remainder) - print("🔍 [SyncEngine] Decoding ciphertext:") - print(" Original length: \(ciphertextString.count) chars, remainder: \(remainder)") - print(" Padded length: \(paddedBase64.count) chars") - print(" First 50 chars: \(ciphertextString.prefix(50))") - print(" Last 10 chars: \(ciphertextString.suffix(10))") - guard let ciphertextData = Data(base64Encoded: paddedBase64) else { - print("❌ [SyncEngine] Failed to decode base64 ciphertext") - print(" Padded string (first 100): \(paddedBase64.prefix(100))") - throw DecodingError.dataCorruptedError(forKey: .ciphertext, in: container, debugDescription: "Invalid Base64 string for ciphertext: \(ciphertextString.prefix(50))...") - } - print("✅ [SyncEngine] Ciphertext decoded: \(ciphertextData.count) bytes") - self.ciphertext = ciphertextData + self.ciphertext = try decodeBase64Field(ciphertextString, forKey: .ciphertext, in: container) deviceId = try container.decode(String.self, forKey: .deviceId) deviceName = try container.decodeIfPresent(String.self, forKey: .deviceName) @@ -106,29 +106,12 @@ public struct SyncEnvelope: Codable { let container = try decoder.container(keyedBy: CodingKeys.self) algorithm = try container.decode(String.self, forKey: .algorithm) - // Decode nonce from base64 string (Android uses standard base64 without padding) let nonceString = try container.decode(String.self, forKey: .nonce) - let nonceRemainder = nonceString.count % 4 - let paddedNonce = nonceRemainder == 0 ? nonceString : nonceString + String(repeating: "=", count: 4 - nonceRemainder) - print("🔍 [SyncEngine] Decoding nonce: \(nonceString) (padded: \(paddedNonce))") - guard let nonceData = Data(base64Encoded: paddedNonce) else { - print("❌ [SyncEngine] Failed to decode base64 nonce") - throw DecodingError.dataCorruptedError(forKey: .nonce, in: container, debugDescription: "Invalid Base64 string for nonce: \(nonceString)") - } - print("✅ [SyncEngine] Nonce decoded: \(nonceData.count) bytes") - self.nonce = nonceData + self.nonce = try decodeBase64Field(nonceString, forKey: .nonce, in: container) // Decode tag from base64 string (Android uses standard base64 without padding) let tagString = try container.decode(String.self, forKey: .tag) - let tagRemainder = tagString.count % 4 - let paddedTag = tagRemainder == 0 ? tagString : tagString + String(repeating: "=", count: 4 - tagRemainder) - print("🔍 [SyncEngine] Decoding tag: \(tagString) (padded: \(paddedTag))") - guard let tagData = Data(base64Encoded: paddedTag) else { - print("❌ [SyncEngine] Failed to decode base64 tag") - throw DecodingError.dataCorruptedError(forKey: .tag, in: container, debugDescription: "Invalid Base64 string for tag: \(tagString)") - } - print("✅ [SyncEngine] Tag decoded: \(tagData.count) bytes") - self.tag = tagData + self.tag = try decodeBase64Field(tagString, forKey: .tag, in: container) } } } diff --git a/macos/Sources/HypoApp/Services/TransportManager.swift b/macos/Sources/HypoApp/Services/TransportManager.swift index f4e35d5..65bea99 100644 --- a/macos/Sources/HypoApp/Services/TransportManager.swift +++ b/macos/Sources/HypoApp/Services/TransportManager.swift @@ -961,13 +961,26 @@ extension TransportManager: LanWebSocketServerDelegate { } nonisolated public func server(_ server: LanWebSocketServer, didReceiveClipboardData data: Data, from connection: UUID) { - // Forward clipboard data to the transport for processing #if canImport(os) let syncLogger = Logger(subsystem: "com.hypo.clipboard", category: "sync") syncLogger.info("📥 CLIPBOARD RECEIVED: from connection \(connection.uuidString.prefix(8)), \(data.count) bytes") #endif print("📥 [TransportManager] CLIPBOARD RECEIVED: from \(connection.uuidString.prefix(8)), \(data.count) bytes") - + + // Try to decode envelope header to attach connection metadata and update online status + if let envelope = try? TransportFrameCodec().decode(data) { + let deviceId = envelope.payload.deviceId + server.updateConnectionMetadata(connectionId: connection, deviceId: deviceId) + NotificationCenter.default.post( + name: NSNotification.Name("DeviceConnectionStatusChanged"), + object: nil, + userInfo: [ + "deviceId": deviceId, + "isOnline": true + ] + ) + } + // Process incoming clipboard data through IncomingClipboardHandler Task { @MainActor in await self.incomingHandler?.handle(data)