Files
Kecalek_python/ios_client 0.8.5/Kecalek/Network/ProtocolHandler.swift
2026-03-14 12:43:56 +01:00

89 lines
3.2 KiB
Swift

import Foundation
/// Newline-delimited JSON protocol handler.
/// Matches Python: protocol.py build_request, build_response, parse_message, encode_binary, decode_binary
enum ProtocolHandler: Sendable {
/// Build a request message (newline-terminated JSON).
/// Matches Python: build_request(msg_type, request_id=None, **kwargs)
nonisolated 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).
nonisolated 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)
nonisolated static func parseMessage(_ data: Data) throws -> [String: Any] {
let trimmed = Self.trimmingNewlines(data)
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)
nonisolated static func encodeBinary(_ data: Data) -> String {
data.base64EncodedString(options: [])
}
/// Decode base64 string to bytes.
/// Matches Python: decode_binary(data)
nonisolated 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).
nonisolated static func newRequestId() -> String {
UUID().uuidString
}
// MARK: - Helpers
private nonisolated static func trimmingNewlines(_ data: Data) -> Data {
var result = data
while let last = result.last, last == 0x0A || last == 0x0D {
result.removeLast()
}
while let first = result.first, first == 0x0A || first == 0x0D {
result.removeFirst()
}
return result
}
}