import Foundation /// Newline-delimited JSON protocol handler. /// Matches Python: protocol.py build_request, build_response, parse_message, encode_binary, decode_binary enum ProtocolHandler { /// Build a request message (newline-terminated JSON). /// Matches Python: build_request(msg_type, request_id=None, **kwargs) static func buildRequest(type: String, requestId: String? = nil, params: [String: Any] = [:]) throws -> Data { var msg: [String: Any] = ["type": type] if let requestId = requestId { msg["request_id"] = requestId } // Merge params into msg for (key, value) in params { msg[key] = value } let jsonData = try JSONSerialization.data(withJSONObject: msg) guard jsonData.count < Constants.maxMessageBytes else { throw NetworkError.messageTooLarge } return jsonData + Data([0x0A]) // newline } /// Build a response message (newline-terminated JSON). static func buildResponse(type: String, status: String, data: [String: Any]? = nil, requestId: String? = nil) throws -> Data { var msg: [String: Any] = ["type": type, "status": status] if let data = data { msg["data"] = data } if let requestId = requestId { msg["request_id"] = requestId } let jsonData = try JSONSerialization.data(withJSONObject: msg) guard jsonData.count < Constants.maxMessageBytes else { throw NetworkError.messageTooLarge } return jsonData + Data([0x0A]) } /// Parse a single protocol message from bytes. /// Matches Python: parse_message(line) static func parseMessage(_ data: Data) throws -> [String: Any] { let trimmed = data.trimmingNewlines() guard !trimmed.isEmpty else { throw NetworkError.protocolError("Empty message") } guard let obj = try JSONSerialization.jsonObject(with: trimmed) as? [String: Any] else { throw NetworkError.protocolError("Message is not a JSON object") } return obj } /// Encode bytes to base64 string. /// Matches Python: encode_binary(data) static func encodeBinary(_ data: Data) -> String { data.base64EncodedString(options: []) } /// Decode base64 string to bytes. /// Matches Python: decode_binary(data) static func decodeBinary(_ string: String) throws -> Data { guard let data = Data(base64Encoded: string, options: .ignoreUnknownCharacters) else { throw CryptoError.invalidBase64 } return data } /// Generate a new request ID (UUID string). static func newRequestId() -> String { UUID().uuidString } } // MARK: - Data Helpers private extension Data { func trimmingNewlines() -> Data { var data = self while let last = data.last, last == 0x0A || last == 0x0D { data.removeLast() } while let first = data.first, first == 0x0A || first == 0x0D { data.removeFirst() } return data } }