// _____ _ // |_ _| |_ _ _ ___ ___ _ __ __ _ // | | | ' \| '_/ -_) -_) ' \/ _` |_ // |_| |_||_|_| \___\___|_|_|_\__,_(_) // // Threema iOS Client // Copyright (c) 2019-2020 Threema GmbH // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License, version 3, // as published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import Foundation import CocoaLumberjackSwift @objc public class WCSession: NSObject, NSCoding { internal var privateKey: Data? internal var webClientSession: WebClientSession? internal var messageQueue: WebMessageQueue private var connection: WCConnection? private var requestedConversations = [String]() private var requestedThumbnails = [Data]() private var lastLoadedMessageIndexes = [Data: Int]() private var requestCreateMessagesFromWeb = [String: WebAbstractMessage]() private(set) var webClientProcessQueue:DispatchQueue public init(webClientSession: WebClientSession) { self.webClientSession = webClientSession self.privateKey = webClientSession.privateKey var hash = webClientSession.initiatorPermanentPublicKeyHash if hash == nil { hash = WCSession.ccSha256(data: webClientSession.initiatorPermanentPublicKey!).hexEncodedString() WebClientSessionStore.shared.updateWebClientSession(session: webClientSession, hash: hash!) } webClientProcessQueue = DispatchQueue(label: "ch.threema.webClientProcessQueue", attributes: []) messageQueue = WebMessageQueue.init() super.init() messageQueue.delegate = self } // MARK: NSCoding required public init?(coder aDecoder: NSCoder) { // super.init(coder:) is optional, see notes below self.privateKey = aDecoder.decodeObject(forKey: "privateKey") as? Data self.connection = aDecoder.decodeObject(forKey: "connection") as? WCConnection let entityManager = EntityManager() if privateKey != nil { webClientSession = entityManager.entityFetcher.webClientSession(forPrivateKey: privateKey!) } else { webClientSession = entityManager.entityFetcher.activeWebClientSession() } self.messageQueue = aDecoder.decodeObject(forKey: "messageQueue") as! WebMessageQueue self.requestedConversations = aDecoder.decodeObject(forKey: "requestedConversations") as! [String] self.requestedThumbnails = aDecoder.decodeObject(forKey: "requestedThumbnails") as! [Data] self.lastLoadedMessageIndexes = aDecoder.decodeObject(forKey: "lastLoadedMessageIndexes") as! [Data: Int] self.requestedConversations = aDecoder.decodeObject(forKey: "requestedConversations") as! [String] self.webClientProcessQueue = DispatchQueue(label: "ch.threema.webClientProcessQueue", attributes: []) } public func encode(with aCoder: NSCoder) { // super.encodeWithCoder(aCoder) is optional, see notes below aCoder.encode(privateKey, forKey: "privateKey") aCoder.encode(connection, forKey: "connection") aCoder.encode(messageQueue, forKey: "messageQueue") aCoder.encode(requestedConversations, forKey: "requestedConversations") aCoder.encode(requestedThumbnails, forKey: "requestedThumbnails") aCoder.encode(lastLoadedMessageIndexes, forKey: "lastLoadedMessageIndexes") } } extension WCSession { // MARK: class functions class func ccSha256(data: Data) -> Data { var digest = Data(count: Int(CC_SHA256_DIGEST_LENGTH)) digest.withUnsafeMutableBytes { digestBuffer in data.withUnsafeBytes { buffer in let _ = CC_SHA256(buffer.baseAddress!, CC_LONG(buffer.count), digestBuffer.bindMemory(to: UInt8.self).baseAddress) } } return digest } } extension WCSession { // MARK: public functions public func connect(authToken: Data?) { let newConnection = WCConnection.init(delegate: self) if connection != nil { if connection!.context != nil { newConnection.context = connection!.context!.copy() as? WebConnectionContext } let oldConnection = connection ValidationLogger.shared().logString("Threema Web: Close old session") oldConnection?.close(close: false, forget: false, sendDisconnect: false, reason: .replace) } connection = newConnection connection?.connect(authToken: authToken) } public func sendMessage() { } public func receive() { } public func stop(close: Bool, forget: Bool, sendDisconnect: Bool, reason: WCConnection.WCConnectionStopReason) { connection?.close(close: close, forget: forget, sendDisconnect: sendDisconnect, reason: reason) } internal func setWCConnectionStateToReady() { connection?.setWCConnectionStateToReady() } internal func setWCConnectionStateToConnectionInfoReceived() { connection?.setWCConnectionStateToConnectionInfoReceived() } internal func connectionContext() -> WebConnectionContext? { return connection?.context } internal func connectionInfoResponse() -> WebUpdateConnectionInfoResponse? { return connection?.connectionInfoResponse } internal func sendChunk(chunk: [UInt8], msgpack: Data?, connectionInfo: Bool) { connection?.sendChunk(chunk: chunk, msgpack: msgpack, connectionInfo: connectionInfo) } internal func connectionWca() -> String? { return connection?.wca } internal func setWcaForConnection(wca: String) { connection?.wca = wca } internal func addRequestCreateMessage(requestId: String, abstractMessage: WebAbstractMessage) { requestCreateMessagesFromWeb[requestId] = abstractMessage } internal func removeRequestCreateMessage(requestId: String) { requestCreateMessagesFromWeb.removeValue(forKey: requestId) } internal func requestMessage(for requestId: String) -> WebAbstractMessage? { return requestCreateMessagesFromWeb[requestId] } } extension WCSession { // MARK: Requested lists public func requestedConversations(contains conversationId: String) -> Bool { return requestedConversations.contains(conversationId) } public func addRequestedConversation(conversationId: String) { if !requestedConversations(contains: conversationId) { requestedConversations.append(conversationId) } } public func requestedThumbnails(contains messageId: Data) -> Bool { return requestedThumbnails.contains(messageId) } public func addRequestedThumbnail(messageId: Data) { if !requestedThumbnails(contains: messageId) { requestedThumbnails.append(messageId) } } public func lastLoadedMessageIndexes(contains messageId: Data) -> Int? { return lastLoadedMessageIndexes[messageId] } public func addLastLoadedMessageIndex(messageId: Data, index: Int) { lastLoadedMessageIndexes[messageId] = index } public func clearAllRequestedLists() { requestedConversations.removeAll() requestedThumbnails.removeAll() lastLoadedMessageIndexes.removeAll() } } extension WCSession: MessageCompleteDelegate { func messageComplete(message: Data) { do { let object = try message.unpack() as! Dictionary let webMessage = WebAbstractMessage.init(dictionary: object) DDLogVerbose("Threema Web: MessagePack -> Received \(webMessage.messageType)/\(webMessage.messageSubType ?? "")") self.webClientProcessQueue.async { webMessage.getResponseMsgpack(session: self, completionHandler: { (responseMsgpack, blackListed) in if responseMsgpack != nil { self.messageQueue.enqueue(data: responseMsgpack, blackListed: blackListed) } else { print("ResponseMsgpack is nil") } }) } } catch { print("Something went wrong while unpacking data: \(error)") } } } extension WCSession: WCConnectionDelegate { internal func currentWebClientSession() -> WebClientSession? { return webClientSession } internal func currentWCSession() -> WCSession { return self } internal func currentMessageQueue() -> WebMessageQueue { return messageQueue } } extension WCSession: WebMessageQueueDelegate { internal func sendMessageToWeb(blacklisted: Bool, msgpack: Data, _ connectionInfo: Bool = false) { self.connection?.sendMessageToWeb(blacklisted: blacklisted, msgpack: msgpack, connectionInfo) } internal func connectionStatus() -> WCConnectionState? { return connection?.connectionStatus } }