ios_client
This commit is contained in:
38
ios_client 0.8.5/Kecalek/Utilities/Constants.swift
Normal file
38
ios_client 0.8.5/Kecalek/Utilities/Constants.swift
Normal file
@@ -0,0 +1,38 @@
|
||||
import Foundation
|
||||
|
||||
enum Constants: Sendable {
|
||||
nonisolated static let version = "0.8.5"
|
||||
nonisolated static let maxMessageBytes = 65536
|
||||
nonisolated static let maxImageBytes = 5 * 1024 * 1024 // 5 MB
|
||||
nonisolated static let maxFileBytes = 50 * 1024 * 1024 // 50 MB
|
||||
nonisolated static let imageChunkSize = 32768 // 32 KB (matches Python IMAGE_CHUNK_SIZE)
|
||||
nonisolated static let selfDeviceId = "00000000-0000-0000-0000-000000000000"
|
||||
|
||||
nonisolated static let opkReplenishThreshold = 20
|
||||
nonisolated static let opkBatchSize = 50
|
||||
nonisolated static let spkRotationDays = 7
|
||||
|
||||
nonisolated static let maxSkip = 256
|
||||
nonisolated static let maxSenderKeySkip = 256
|
||||
|
||||
nonisolated static let deviceBundleCacheTTL: TimeInterval = 300 // 5 minutes
|
||||
nonisolated static let sendReceiveTimeout: TimeInterval = 30
|
||||
nonisolated static let reconnectBaseDelay: TimeInterval = 1
|
||||
nonisolated static let reconnectMaxDelay: TimeInterval = 30
|
||||
|
||||
nonisolated static let pbkdf2Iterations: UInt32 = 600_000
|
||||
nonisolated static let ecp1Magic = Data([0x45, 0x43, 0x50, 0x31]) // "ECP1"
|
||||
|
||||
// HKDF info/salt strings matching Python
|
||||
nonisolated static let x3dhInfo = "EncryptedChat_X3DH"
|
||||
nonisolated static let rootKeyInfo = "EncryptedChat_RootKey"
|
||||
nonisolated static let selfEncryptionSalt = "self_encryption"
|
||||
nonisolated static let selfEncryptionInfo = "EncryptedChat_SelfKey"
|
||||
nonisolated static let localStorageSalt = "local_storage"
|
||||
nonisolated static let localStorageInfo = "EncryptedChat_LocalStorage"
|
||||
nonisolated static let senderKeyChainInfo = "SenderKeyChain"
|
||||
|
||||
// Server connection defaults
|
||||
nonisolated static let defaultHost = "chat.ai-tech.news"
|
||||
nonisolated static let defaultPort: UInt16 = 9999
|
||||
}
|
||||
168
ios_client 0.8.5/Kecalek/Utilities/Extensions.swift
Normal file
168
ios_client 0.8.5/Kecalek/Utilities/Extensions.swift
Normal file
@@ -0,0 +1,168 @@
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
// MARK: - Data ↔ Hex
|
||||
|
||||
extension Data {
|
||||
/// Convert data to lowercase hex string
|
||||
var hexString: String {
|
||||
map { String(format: "%02x", $0) }.joined()
|
||||
}
|
||||
|
||||
/// Initialize Data from a hex string
|
||||
init?(hexString: String) {
|
||||
let hex = hexString.lowercased()
|
||||
guard hex.count % 2 == 0 else { return nil }
|
||||
var data = Data(capacity: hex.count / 2)
|
||||
var index = hex.startIndex
|
||||
while index < hex.endIndex {
|
||||
let nextIndex = hex.index(index, offsetBy: 2)
|
||||
guard let byte = UInt8(hex[index..<nextIndex], radix: 16) else { return nil }
|
||||
data.append(byte)
|
||||
index = nextIndex
|
||||
}
|
||||
self = data
|
||||
}
|
||||
|
||||
/// Generate random bytes
|
||||
static func randomBytes(_ count: Int) -> Data {
|
||||
var data = Data(count: count)
|
||||
data.withUnsafeMutableBytes { ptr in
|
||||
_ = SecRandomCopyBytes(kSecRandomDefault, count, ptr.baseAddress!)
|
||||
}
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Data ↔ Base64 (Protocol Wire Format)
|
||||
|
||||
extension Data {
|
||||
/// Encode to standard base64 string (matching Python's base64.b64encode)
|
||||
func base64EncodedString() -> String {
|
||||
self.base64EncodedString(options: [])
|
||||
}
|
||||
|
||||
/// Decode from base64 string
|
||||
static func fromBase64(_ string: String) throws -> Data {
|
||||
// Try standard base64 first, then URL-safe
|
||||
if let data = Data(base64Encoded: string, options: .ignoreUnknownCharacters) {
|
||||
return data
|
||||
}
|
||||
throw CryptoError.invalidBase64
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UInt32 Big-Endian
|
||||
|
||||
extension UInt32 {
|
||||
var bigEndianData: Data {
|
||||
var value = self.bigEndian
|
||||
return Data(bytes: &value, count: 4)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - CryptoKit Key → Data
|
||||
|
||||
extension Curve25519.KeyAgreement.PublicKey {
|
||||
nonisolated var rawData: Data {
|
||||
Data(rawRepresentation)
|
||||
}
|
||||
}
|
||||
|
||||
extension Curve25519.KeyAgreement.PrivateKey {
|
||||
nonisolated var rawData: Data {
|
||||
Data(rawRepresentation)
|
||||
}
|
||||
}
|
||||
|
||||
extension Curve25519.Signing.PublicKey {
|
||||
nonisolated var rawData: Data {
|
||||
Data(rawRepresentation)
|
||||
}
|
||||
}
|
||||
|
||||
extension Curve25519.Signing.PrivateKey {
|
||||
nonisolated var rawData: Data {
|
||||
Data(rawRepresentation)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - String helpers
|
||||
|
||||
extension String {
|
||||
/// Trim whitespace and newlines
|
||||
var trimmed: String {
|
||||
trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Date Parsing (server sends ISO8601 with or without timezone)
|
||||
|
||||
enum DateParsing {
|
||||
private static let iso8601WithTZ: ISO8601DateFormatter = {
|
||||
let f = ISO8601DateFormatter()
|
||||
f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
||||
return f
|
||||
}()
|
||||
|
||||
private static let iso8601Basic: ISO8601DateFormatter = {
|
||||
let f = ISO8601DateFormatter()
|
||||
f.formatOptions = [.withInternetDateTime]
|
||||
return f
|
||||
}()
|
||||
|
||||
private static let noTZ: DateFormatter = {
|
||||
let f = DateFormatter()
|
||||
f.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
|
||||
f.timeZone = TimeZone(identifier: "UTC")
|
||||
f.locale = Locale(identifier: "en_US_POSIX")
|
||||
return f
|
||||
}()
|
||||
|
||||
/// Parse ISO8601 date string — handles with/without timezone, with/without fractional seconds
|
||||
static func parse(_ string: String) -> Date? {
|
||||
iso8601WithTZ.date(from: string)
|
||||
?? iso8601Basic.date(from: string)
|
||||
?? noTZ.date(from: string)
|
||||
}
|
||||
|
||||
/// Format Date to ISO8601 string (for after_ts / since_ts parameters)
|
||||
static func format(_ date: Date) -> String {
|
||||
iso8601WithTZ.string(from: date)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Dictionary merge helper
|
||||
|
||||
extension Dictionary where Key == String, Value == Any {
|
||||
nonisolated func string(for key: String) -> String? {
|
||||
self[key] as? String
|
||||
}
|
||||
|
||||
nonisolated func int(for key: String) -> Int? {
|
||||
if let i = self[key] as? Int { return i }
|
||||
if let s = self[key] as? String, let i = Int(s) { return i }
|
||||
return nil
|
||||
}
|
||||
|
||||
nonisolated func dict(for key: String) -> [String: Any]? {
|
||||
self[key] as? [String: Any]
|
||||
}
|
||||
|
||||
nonisolated func array(for key: String) -> [[String: Any]]? {
|
||||
self[key] as? [[String: Any]]
|
||||
}
|
||||
|
||||
nonisolated func data(for key: String) -> Data? {
|
||||
if let hex = self[key] as? String {
|
||||
return Data(hexString: hex)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
nonisolated func bool(for key: String) -> Bool? {
|
||||
if let b = self[key] as? Bool { return b }
|
||||
if let i = self[key] as? Int { return i != 0 }
|
||||
return nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user