WCConnection.swift 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2019-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. import Foundation
  21. import CocoaLumberjackSwift
  22. enum WCConnectionState: Int {
  23. case new, connecting, serverHandshake, peerHandshake, connectionInfoSend, connectionInfoReceived, ready, disconnecting, disconnected
  24. }
  25. protocol WCConnectionDelegate: class {
  26. func currentWebClientSession() -> WebClientSession?
  27. func currentWCSession() -> WCSession
  28. func currentMessageQueue() -> WebMessageQueue
  29. }
  30. @objc public class WCConnection: NSObject, NSCoding {
  31. enum WebSocketCode: UInt16 {
  32. case closing = 1000
  33. }
  34. public enum WCConnectionStopReason: Int {
  35. case stop, delete, disable, replace, error, pause
  36. }
  37. var context: WebConnectionContext?
  38. var delegate: WCConnectionDelegate
  39. var wca: String?
  40. private(set) var connectionStatus: WCConnectionState = .new
  41. private var webClientConnectionQueue:DispatchQueue
  42. private var webClientRequestEventQueue:DispatchQueue
  43. private var webClientRequestMsgQueue:DispatchQueue
  44. private var webClientSendQueue:DispatchQueue
  45. private var freeDisconnect = true
  46. private var connectionInfoRequest: WebUpdateConnectionInfoRequest?
  47. private(set) var connectionInfoResponse: WebUpdateConnectionInfoResponse?
  48. private var responder_sender:OpaquePointer?
  49. private var responder_disconnect:OpaquePointer?
  50. private var responder_client:OpaquePointer?
  51. private var connectionWaitTimer: Timer?
  52. private var pingInterval: UInt32 = 30
  53. public init(delegate: WCSession) {
  54. self.delegate = delegate
  55. webClientConnectionQueue = DispatchQueue(label: "ch.threema.webClientConnectionQueue", attributes: [])
  56. webClientRequestEventQueue = DispatchQueue(label: "ch.threema.webClientRequestEventQueue", attributes: [])
  57. webClientRequestMsgQueue = DispatchQueue(label: "ch.threema.webClientRequestMsgQueue", attributes: [])
  58. webClientSendQueue = DispatchQueue(label: "ch.threema.webClientSendQueue", attributes: [])
  59. }
  60. // MARK: NSCoding
  61. required public init?(coder aDecoder: NSCoder) {
  62. // super.init(coder:) is optional, see notes below
  63. self.context = aDecoder.decodeObject(forKey: "context") as? WebConnectionContext
  64. self.delegate = aDecoder.decodeObject(forKey: "delegate") as! WCConnectionDelegate
  65. if let status = aDecoder.decodeObject(forKey: "connectionStatus") as? Int {
  66. self.connectionStatus = WCConnectionState.init(rawValue: status)!
  67. DDLogVerbose("Threema Web: Init from coder -> Set connection state to \(WCConnectionState.init(rawValue: status)!)")
  68. } else {
  69. self.connectionStatus = .disconnected
  70. DDLogVerbose("Threema Web: Init from coder no predefined status -> Set connection state to new")
  71. }
  72. webClientConnectionQueue = DispatchQueue(label: "ch.threema.webClientConnectionQueue", attributes: [])
  73. webClientRequestEventQueue = DispatchQueue(label: "ch.threema.webClientRequestEventQueue", attributes: [])
  74. webClientRequestMsgQueue = DispatchQueue(label: "ch.threema.webClientRequestMsgQueue", attributes: [])
  75. webClientSendQueue = DispatchQueue(label: "ch.threema.webClientSendQueue", attributes: [])
  76. self.wca = aDecoder.decodeObject(forKey: "wca") as? String
  77. if let free = aDecoder.decodeObject(forKey: "freeDisconnect") as? Bool {
  78. self.freeDisconnect = free
  79. } else {
  80. self.freeDisconnect = true
  81. }
  82. self.connectionInfoRequest = aDecoder.decodeObject(forKey: "connectionInfoRequest") as? WebUpdateConnectionInfoRequest
  83. self.connectionInfoResponse = aDecoder.decodeObject(forKey: "connectionInfoResponse") as?WebUpdateConnectionInfoResponse
  84. self.pingInterval = UInt32(aDecoder.decodeInt32(forKey: "pingInterval"))
  85. }
  86. public func encode(with aCoder: NSCoder) {
  87. // super.encodeWithCoder(aCoder) is optional, see notes below
  88. aCoder.encode(context, forKey: "context")
  89. aCoder.encode(delegate, forKey: "delegate")
  90. aCoder.encode(connectionStatus.rawValue, forKey: "connectionStatus")
  91. aCoder.encode(wca, forKey: "wca")
  92. aCoder.encode(freeDisconnect, forKey: "freeDisconnect")
  93. if connectionInfoRequest != nil {
  94. aCoder.encode(connectionInfoRequest, forKey: "connectionInfoRequest")
  95. }
  96. if connectionInfoResponse != nil {
  97. aCoder.encode(connectionInfoResponse, forKey: "connectionInfoResponse")
  98. }
  99. aCoder.encode(Int32(pingInterval), forKey: "pingInterval")
  100. }
  101. }
  102. extension WCConnection {
  103. // MARK: public functions
  104. func connect(authToken: Data?) {
  105. webClientConnectionQueue.async {
  106. salty_log_change_level(UInt8(LEVEL_OFF))
  107. salty_log_init(UInt8(LEVEL_OFF))
  108. if self.delegate.currentWebClientSession() == nil {
  109. ValidationLogger.shared().logString("Threema Web: Can't connect to web, webClientSession is nil")
  110. return
  111. }
  112. var r_keypair: OpaquePointer? = nil
  113. if let p = self.delegate.currentWebClientSession()!.privateKey {
  114. let u8PtrPrivateKey: UnsafePointer<UInt8> = p.withUnsafeBytes {
  115. $0.bindMemory(to: UInt8.self).baseAddress!
  116. }
  117. r_keypair = salty_keypair_restore(u8PtrPrivateKey)
  118. } else {
  119. r_keypair = salty_keypair_new()
  120. }
  121. let loop = salty_event_loop_new()
  122. let remote = salty_event_loop_get_remote(loop)
  123. let initiatorPermanentPublicKey = self.delegate.currentWebClientSession()!.initiatorPermanentPublicKey
  124. let ippk: UnsafePointer<UInt8> = initiatorPermanentPublicKey!.withUnsafeBytes {
  125. $0.bindMemory(to: UInt8.self).baseAddress!
  126. }
  127. var at: UnsafePointer<UInt8>? = nil
  128. if authToken != nil {
  129. at = authToken!.withUnsafeBytes {
  130. $0.bindMemory(to: UInt8.self).baseAddress!
  131. }
  132. }
  133. self.connectToWebClient(ippk: ippk, at: at, r_keypair: r_keypair!, loop: loop!, remote: remote!)
  134. }
  135. }
  136. func close(close: Bool, forget: Bool, sendDisconnect: Bool, reason: WCConnectionStopReason) {
  137. if connectionStatus != .disconnecting && self.connectionStatus != .disconnected {
  138. self.connectionStatus = .disconnecting
  139. DDLogVerbose("Threema Web: close -> Set connection state to \(self.connectionStatus)")
  140. self.connectionWaitTimer?.invalidate()
  141. self.connectionWaitTimer = nil
  142. context?.cancelTimer()
  143. delegate.currentWebClientSession()?.isConnecting = false
  144. removeWCSessionFromRunning(reason: reason, forget: forget)
  145. if sendDisconnect {
  146. let messageData = WebUpdateConnectionDisconnectResponse.init(disconnectReason: reason.rawValue)
  147. DDLogVerbose("Threema Web: MessagePack -> Send update/connectionDisconnect")
  148. sendMessageToWeb(blacklisted: true, msgpack: messageData.messagePack(), true)
  149. DispatchQueue.main.async {
  150. Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { (timer) in
  151. self.saltyClientDisconnect(close: close)
  152. }
  153. }
  154. } else {
  155. self.connectionStatus = .disconnected
  156. DDLogVerbose("Threema Web: close, sendDisconnect == false -> Set connection state to \(self.connectionStatus)")
  157. if responder_disconnect != nil {
  158. saltyClientDisconnect(close: close)
  159. }
  160. }
  161. } else {
  162. removeWCSessionFromRunning(reason: reason, forget: forget)
  163. }
  164. }
  165. func sendMessageToWeb(blacklisted: Bool, msgpack: Data, _ connectionInfo: Bool = false) {
  166. webClientSendQueue.sync {
  167. if self.context != nil {
  168. let chunker = try! Chunker(id: self.context!.messageCounter, data: msgpack, chunkSize: 64*1024)
  169. var chunksToSend = [[UInt8]]()
  170. for chunk in chunker {
  171. if blacklisted == false {
  172. DDLogVerbose("Threema Web: Cunkcache --> Add message")
  173. } else {
  174. DDLogVerbose("Threema Web: Cunkcache --> Do not add, message is blacklisted")
  175. }
  176. self.context!.append(chunk: blacklisted == true ? nil : chunk)
  177. chunksToSend.append(chunk)
  178. }
  179. self.context!.messageCounter = self.context!.messageCounter + 1
  180. DDLogVerbose("Threema Web: Message counter --> \(self.context!.messageCounter)")
  181. for chunk in chunksToSend {
  182. self.sendChunk(chunk: chunk, msgpack: msgpack, connectionInfo: connectionInfo)
  183. }
  184. }
  185. }
  186. }
  187. func sendChunk(chunk: [UInt8], msgpack: Data?, connectionInfo: Bool) {
  188. do {
  189. var msgData = Data()
  190. try msgData.pack(chunk)
  191. if (responder_sender == nil) {
  192. ValidationLogger.shared().logString("Threema Web: sendChunk: response_sender is nil")
  193. return
  194. }
  195. if connectionStatus != .ready && !connectionInfo {
  196. ValidationLogger.shared()?.logString("Threema Web: sendChunk status is not ready")
  197. return
  198. }
  199. let success = salty_client_send_task_bytes(responder_sender, msgData.bytes, UInt32(msgData.bytes.count))
  200. if success == UInt8(SEND_OK.rawValue) {
  201. // msgpack is only set if message is not from chunkcache
  202. if msgpack != nil {
  203. delegate.currentMessageQueue().processSendFinished(finishedData: msgpack)
  204. }
  205. }
  206. }
  207. catch {
  208. ValidationLogger.shared().logString("Threema Web: Error during create chunks")
  209. close(close: true, forget: false, sendDisconnect: false, reason: .error)
  210. }
  211. }
  212. func setWCConnectionStateToReady() {
  213. connectionStatus = .ready
  214. DDLogVerbose("Threema Web: setWCConnectionStateToReady -> Set connection state to \(connectionStatus)")
  215. }
  216. func setWCConnectionStateToConnectionInfoReceived() {
  217. connectionStatus = .connectionInfoReceived
  218. DDLogVerbose("Threema Web: setWCConnectionStateToConnectionInfoReceived -> Set connection state to \(connectionStatus)")
  219. }
  220. }
  221. extension WCConnection {
  222. // MARK: private functions
  223. private func connectToWebClient(ippk: UnsafePointer<UInt8>, at: UnsafePointer<UInt8>?, r_keypair: OpaquePointer, loop: OpaquePointer, remote: OpaquePointer) {
  224. var client_ret: salty_relayed_data_client_ret_t? = nil
  225. let serverPermanentPublicKey = delegate.currentWebClientSession()!.serverPermanentPublicKey
  226. let u8PtrServerPermanentPublicKey: UnsafePointer<UInt8> = serverPermanentPublicKey!.withUnsafeBytes {
  227. $0.bindMemory(to: UInt8.self).baseAddress!
  228. }
  229. if delegate.currentWebClientSession()!.privateKey == nil {
  230. let privateKey = salty_keypair_private_key(r_keypair)
  231. let privateKeyData: Data? = NSData.init(bytes: privateKey, length: 32) as Data
  232. WebClientSessionStore.shared.updateWebClientSession(session: delegate.currentWebClientSession()!, privateKey: privateKeyData!)
  233. delegate.currentWCSession().privateKey = privateKeyData
  234. }
  235. if at != nil {
  236. client_ret = salty_relayed_data_responder_new(r_keypair, remote, pingInterval, ippk, at, u8PtrServerPermanentPublicKey)
  237. } else {
  238. client_ret = salty_relayed_data_responder_new(r_keypair, remote, pingInterval, ippk, nil, u8PtrServerPermanentPublicKey)
  239. }
  240. responder_sender = client_ret!.sender_tx
  241. responder_disconnect = client_ret!.disconnect_tx
  242. responder_client = client_ret!.client
  243. if client_ret!.success != 0 {
  244. delegate.currentWebClientSession()!.isConnecting = false
  245. let errorString = String(format:"Threema Web: salty relayed data responder error", (client_ret?.success)!)
  246. ValidationLogger.shared().logString(errorString)
  247. WCSessionManager.shared.removeWCSessionFromRunning(delegate.currentWCSession())
  248. return
  249. }
  250. let saltyRTCHost: NSString = delegate.currentWebClientSession()!.saltyRTCHost! as NSString
  251. let saltyRTCPort = delegate.currentWebClientSession()!.saltyRTCPort!.intValue
  252. WebClientSessionStore.shared.updateWebClientSession(session: delegate.currentWebClientSession()!, lastConnection: Date())
  253. let salty_client_init_ret = salty_client_init(saltyRTCHost.utf8String, UInt16(saltyRTCPort), client_ret!.client, loop, UInt16(0), nil, 0)
  254. if salty_client_init_ret.success == UInt8(INIT_OK.rawValue) {
  255. ValidationLogger.shared().logString("Threema Web: salty client init success")
  256. ValidationLogger.shared().logString("Threema Web: Start EventDispatchQueue")
  257. self.requestEventDispatchQueue(responder_event: salty_client_init_ret.event_rx, responder_receiver: client_ret!.receiver_rx)
  258. // Connect to the SaltyRTC server, do the server and peer handshake and run the task loop.
  259. // This call will only return once the connection has been terminated.
  260. let connect_success = salty_client_connect(salty_client_init_ret.handshake_future, client_ret!.client, loop, salty_client_init_ret.event_tx, client_ret!.sender_rx, client_ret!.disconnect_rx)
  261. WebClientSessionStore.shared.updateWebClientSession(session: delegate.currentWebClientSession()!, lastConnection: Date())
  262. delegate.currentWebClientSession()!.isConnecting = false
  263. context?.cancelTimer()
  264. let errorString = String(format:"Threema Web: Connection ended with exit code %i", connect_success)
  265. connectionStatus = .disconnected
  266. DDLogVerbose("Threema Web: connectToWebClient -> Set connection state to \(connectionStatus)")
  267. ValidationLogger.shared().logString(errorString)
  268. salty_relayed_data_client_free(client_ret!.client);
  269. salty_channel_sender_tx_free(client_ret!.sender_tx);
  270. if self.freeDisconnect {
  271. salty_channel_disconnect_tx_free(self.responder_disconnect); // only if web disconnect
  272. }
  273. salty_event_loop_free(loop);
  274. responder_sender = nil
  275. responder_disconnect = nil
  276. responder_client = nil
  277. freeDisconnect = true
  278. self.connectionInfoResponse = nil
  279. self.connectionInfoRequest = nil
  280. } else {
  281. delegate.currentWebClientSession()!.isConnecting = false
  282. let errorString = String(format:"Threema Web: salty client init error", salty_client_init_ret.success)
  283. ValidationLogger.shared().logString(errorString)
  284. WCSessionManager.shared.removeWCSessionFromRunning(delegate.currentWCSession())
  285. }
  286. }
  287. private func requestEventDispatchQueue(responder_event: OpaquePointer, responder_receiver: OpaquePointer) {
  288. webClientRequestEventQueue.async {
  289. let recv_event = salty_client_recv_event(responder_event, nil)
  290. if recv_event.success == UInt8(RECV_OK.rawValue) {
  291. let event = recv_event.event!
  292. switch event.pointee.event_type {
  293. case UInt8(EVENT_CONNECTING.rawValue):
  294. ValidationLogger.shared().logString("Threema Web: EVENT_CONNECTING")
  295. self.connectionStatus = .connecting
  296. DDLogVerbose("Threema Web: EVENT_CONNECTING -> Set connection state to \(self.connectionStatus)")
  297. break
  298. case UInt8(EVENT_SERVER_HANDSHAKE_COMPLETED.rawValue):
  299. if event.pointee.peer_connected == true {
  300. self.connectionStatus = .serverHandshake
  301. DDLogVerbose("Threema Web: EVENT_SERVER_HANDSHAKE_COMPLETED -> Set connection state to \(self.connectionStatus)")
  302. } else {
  303. self.connectionStatus = .serverHandshake
  304. DDLogVerbose("Threema Web: EVENT_SERVER_HANDSHAKE_COMPLETED -> Set connection state to \(self.connectionStatus)")
  305. ValidationLogger.shared().logString("Threema Web: Peer not connected")
  306. // start timer and wait 10 seconds for peer
  307. self.connectionWaitTimer?.invalidate()
  308. DispatchQueue.main.async {
  309. self.connectionWaitTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: false, block: { (timer) in
  310. self.connectionWaitTimer?.invalidate()
  311. self.connectionWaitTimer = nil
  312. ValidationLogger.shared().logString("Threema Web: Error peer is not connected")
  313. self.close(close: true, forget: false, sendDisconnect: true, reason: .stop)
  314. })
  315. }
  316. }
  317. ValidationLogger.shared().logString("Threema Web: EVENT_SERVER_HANDSHAKE_COMPLETED")
  318. break
  319. case UInt8(EVENT_PEER_HANDSHAKE_COMPLETED.rawValue):
  320. self.connectionStatus = .peerHandshake
  321. DDLogVerbose("Threema Web: EVENT_PEER_HANDSHAKE_COMPLETED -> Set connection state to \(self.connectionStatus)")
  322. self.connectionWaitTimer?.invalidate()
  323. self.connectionWaitTimer = nil
  324. ValidationLogger.shared().logString("Threema Web: EVENT_PEER_HANDSHAKE_COMPLETED")
  325. ValidationLogger.shared().logString("Threema Web: Set current session to active")
  326. DispatchQueue.main.async {
  327. if let webClientSession = self.delegate.currentWebClientSession() {
  328. WebClientSessionStore.shared.updateWebClientSession(session: webClientSession, active: true)
  329. }
  330. }
  331. // send connectionInfo
  332. let tmpId = Data.init(count: 0)
  333. let nonceData = "connectionidconnectionid".data(using: .utf8)
  334. let id: UnsafePointer<UInt8> = tmpId.withUnsafeBytes {
  335. $0.bindMemory(to: UInt8.self).baseAddress!
  336. }
  337. let nonce: UnsafePointer<UInt8> = nonceData!.withUnsafeBytes {
  338. $0.bindMemory(to: UInt8.self).baseAddress!
  339. }
  340. let encrypt_decrypt_ret_t = salty_client_encrypt_with_session_keys(self.responder_client, id, 0, nonce)
  341. if encrypt_decrypt_ret_t.success == UInt8(ENCRYPT_DECRYPT_OK.rawValue) {
  342. let connectionId = Data.init(bytes: encrypt_decrypt_ret_t.bytes, count: encrypt_decrypt_ret_t.bytes_len)
  343. let newContext = WebConnectionContext.init(connectionId: connectionId, delegate: self)
  344. newContext.unchunker.delegate = self.delegate.currentWCSession()
  345. self.connectionStatus = .connectionInfoSend
  346. DDLogVerbose("Threema Web: EVENT_PEER_HANDSHAKE_COMPLETED -> Set connection state to \(self.connectionStatus)")
  347. if self.context != nil {
  348. newContext.previousConnectionContext = self.context
  349. self.connectionInfoResponse = WebUpdateConnectionInfoResponse.init(currentId: connectionId, previousId: newContext.previousConnectionContext!.connectionId(), previousSequenceNumber: UInt32(newContext.previousConnectionContext!.incomingSequenceNumber))
  350. newContext.messageCounter = newContext.previousConnectionContext!.messageCounter
  351. newContext.unchunker = newContext.previousConnectionContext!.unchunker
  352. newContext.unchunker.delegate = self.delegate.currentWCSession()
  353. } else {
  354. self.connectionInfoResponse = WebUpdateConnectionInfoResponse.init(currentId: connectionId, previousId: nil, previousSequenceNumber: nil)
  355. }
  356. self.context = newContext
  357. DDLogVerbose("Threema Web: MessagePack -> Send update/connectionInfo")
  358. self.sendMessageToWeb(blacklisted: true, msgpack: self.connectionInfoResponse!.messagePack(), true)
  359. if self.connectionStatus == .connectionInfoReceived {
  360. ValidationLogger.shared()?.logString("Threema Web: connectionInfoReceived maybeResume state: \(self.connectionStatus.rawValue)")
  361. self.connectionStatus = .ready
  362. DDLogVerbose("Threema Web: connectionStatus == .connectionInfoReceived -> Set connection state to \(self.connectionStatus)")
  363. self.context!.connectionInfoRequest = self.connectionInfoRequest
  364. self.connectionInfoRequest?.maybeResume(session: self.delegate.currentWCSession())
  365. } else {
  366. ValidationLogger.shared()?.logString("Threema Web: connectionInfo not received state: \(self.connectionStatus.rawValue)")
  367. }
  368. ValidationLogger.shared().logString("Threema Web: Start MsgDispatchQueue")
  369. self.requestMsgDispatchQueue(responder_receiver: responder_receiver)
  370. } else {
  371. // stopp session because connectionid is empty
  372. ValidationLogger.shared().logString("Threema Web: ENCRYPT_DECRYPT_ERROR")
  373. self.close(close: true, forget: false, sendDisconnect: true, reason: .error)
  374. }
  375. break
  376. case UInt8(EVENT_PEER_DISCONNECTED.rawValue):
  377. ValidationLogger.shared().logString("Threema Web: EVENT_PEER_DISCONNECTED")
  378. self.close(close: true, forget: false, sendDisconnect: false, reason: .stop)
  379. break
  380. default:
  381. print("Threema Web: unexpected event type \(event.pointee.event_type)")
  382. break
  383. }
  384. if event.pointee.event_type != UInt8(EVENT_PEER_DISCONNECTED.rawValue) {
  385. self.requestEventDispatchQueue(responder_event: responder_event, responder_receiver: responder_receiver)
  386. } else {
  387. salty_channel_event_rx_free(responder_event)
  388. }
  389. } else {
  390. print("Threema Web: received event error ", recv_event.success)
  391. }
  392. salty_client_recv_event_ret_free(recv_event)
  393. }
  394. }
  395. private func requestMsgDispatchQueue(responder_receiver: OpaquePointer) {
  396. webClientRequestMsgQueue.async {
  397. let recv_ret = salty_client_recv_msg(responder_receiver, nil)
  398. let success = recv_ret.success
  399. if success == UInt8(RECV_OK.rawValue) {
  400. let msg = recv_ret.msg!
  401. switch msg.pointee.msg_type {
  402. case UInt8(MSG_TASK.rawValue):
  403. let count = JDI.ToInt(msg.pointee.msg_bytes_len)
  404. let bytesArray = self.convert(length: count, data: msg.pointee.msg_bytes)
  405. let chunkedData = Data(bytesArray)
  406. do {
  407. let unpackedData = try chunkedData.unpack()
  408. if (self.context != nil) {
  409. try self.context!.unchunker.addChunk(bytes: unpackedData as! Data)
  410. self.context!.incomingSequenceNumber = self.context!.incomingSequenceNumber + 1
  411. }
  412. } catch {
  413. ValidationLogger.shared().logString("Something went wrong while unchunk data: \(error)")
  414. }
  415. salty_client_recv_msg_ret_free(recv_ret)
  416. break
  417. case UInt8(MSG_CLOSE.rawValue):
  418. ValidationLogger.shared().logString("Threema Web: MSG_CLOSE")
  419. salty_client_recv_msg_ret_free(recv_ret)
  420. break
  421. default:
  422. salty_client_recv_msg_ret_free(recv_ret)
  423. break
  424. }
  425. }
  426. if success != UInt8(RECV_STREAM_ENDED.rawValue) && success != UInt8(RECV_ERROR.rawValue) {
  427. self.requestMsgDispatchQueue(responder_receiver: responder_receiver)
  428. } else {
  429. salty_channel_receiver_rx_free(responder_receiver)
  430. }
  431. }
  432. }
  433. private func removeWCSessionFromRunning(reason: WCConnectionStopReason, forget: Bool) {
  434. if reason != .pause && reason != .replace {
  435. ValidationLogger.shared().logString("Threema Web: Set current session by stop to inactive")
  436. WCSessionManager.shared.removeWCSessionFromRunning(delegate.currentWCSession())
  437. if forget, let webclientSession = delegate.currentWebClientSession() {
  438. WebClientSessionStore.shared.deleteWebClientSession(webclientSession)
  439. }
  440. }
  441. }
  442. private func saltyClientDisconnect(close: Bool) {
  443. let disconnectSuccess = salty_client_disconnect(self.responder_disconnect, WebSocketCode.closing.rawValue)
  444. if disconnectSuccess == UInt8(DISCONNECT_OK.rawValue) || disconnectSuccess == UInt8(DISCONNECT_ERROR.rawValue) {
  445. self.freeDisconnect = false
  446. }
  447. if close {
  448. self.context = nil
  449. }
  450. self.connectionStatus = .disconnected
  451. DDLogVerbose("Threema Web: close -> Set connection state to \(self.connectionStatus)")
  452. }
  453. }
  454. extension WCConnection {
  455. // MARK: Helper functions
  456. private func convert(length: Int, data: UnsafePointer<UInt8>) -> [UInt8] {
  457. let buffer = UnsafeBufferPointer(start: data, count: length);
  458. return Array(buffer)
  459. }
  460. }
  461. extension WCConnection: WebConnectionContextDelegate {
  462. internal func currentWCSession() -> WCSession {
  463. return delegate.currentWCSession()
  464. }
  465. }
  466. public class JDI {
  467. // To Int
  468. public static func ToInt(_ x : Int8) -> Int {
  469. return Int(x)
  470. }
  471. public static func ToInt(_ x : Int32) -> Int {
  472. return Int(x)
  473. }
  474. public static func ToInt(_ x : Int64) -> Int {
  475. return Int(truncatingIfNeeded: x)
  476. }
  477. public static func ToInt(_ x : Int) -> Int {
  478. return x
  479. }
  480. public static func ToInt(_ x : UInt8) -> Int {
  481. return Int(x)
  482. }
  483. public static func ToInt(_ x : UInt32) -> Int {
  484. if MemoryLayout<Int>.size == MemoryLayout<Int32>.size {
  485. return Int(Int32(bitPattern: x)) // For 32-bit systems, non-authorized interpretation
  486. }
  487. return Int(x)
  488. }
  489. public static func ToInt(_ x : UInt64) -> Int {
  490. return Int(truncatingIfNeeded: x)
  491. }
  492. public static func ToInt(_ x : UInt) -> Int {
  493. return Int(bitPattern: x)
  494. }
  495. // To UInt
  496. public static func ToUInt(_ x : Int8) -> UInt {
  497. return UInt(bitPattern: Int(x)) // Extend sign bit, assume minus input significant
  498. }
  499. public static func ToUInt(_ x : Int32) -> UInt {
  500. return UInt(truncatingIfNeeded: Int64(x)) // Extend sign bit, assume minus input significant
  501. }
  502. public static func ToUInt(_ x : Int64) -> UInt {
  503. return UInt(truncatingIfNeeded: x)
  504. }
  505. public static func ToUInt(_ x : Int) -> UInt {
  506. return UInt(bitPattern: x)
  507. }
  508. public static func ToUInt(_ x : UInt8) -> UInt {
  509. return UInt(x)
  510. }
  511. public static func ToUInt(_ x : UInt32) -> UInt {
  512. return UInt(x)
  513. }
  514. public static func ToUInt(_ x : UInt64) -> UInt {
  515. return UInt(truncatingIfNeeded: x)
  516. }
  517. public static func ToUInt(_ x : UInt) -> UInt {
  518. return x
  519. }
  520. }
  521. extension String {
  522. var unsafePointer: UnsafePointer<Int8> {
  523. return UnsafePointer((self as NSString).utf8String!)
  524. }
  525. }