// _____ _
// |_ _| |_ _ _ ___ ___ _ __ __ _
// | | | ' \| '_/ -_) -_) ' \/ _` |_
// |_| |_||_|_| \___\___|_|_|_\__,_(_)
//
// 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
enum WCConnectionState: Int {
case new, connecting, serverHandshake, peerHandshake, connectionInfoSend, connectionInfoReceived, ready, disconnecting, disconnected
}
protocol WCConnectionDelegate: class {
func currentWebClientSession() -> WebClientSession?
func currentWCSession() -> WCSession
func currentMessageQueue() -> WebMessageQueue
}
@objc public class WCConnection: NSObject, NSCoding {
enum WebSocketCode: UInt16 {
case closing = 1000
}
public enum WCConnectionStopReason: Int {
case stop, delete, disable, replace, error, pause
}
var context: WebConnectionContext?
var delegate: WCConnectionDelegate
var wca: String?
private(set) var connectionStatus: WCConnectionState = .new
private var webClientConnectionQueue:DispatchQueue
private var webClientRequestEventQueue:DispatchQueue
private var webClientRequestMsgQueue:DispatchQueue
private var webClientSendQueue:DispatchQueue
private var freeDisconnect = true
private var connectionInfoRequest: WebUpdateConnectionInfoRequest?
private(set) var connectionInfoResponse: WebUpdateConnectionInfoResponse?
private var responder_sender:OpaquePointer?
private var responder_disconnect:OpaquePointer?
private var responder_client:OpaquePointer?
private var connectionWaitTimer: Timer?
private var pingInterval: UInt32 = 30
public init(delegate: WCSession) {
self.delegate = delegate
webClientConnectionQueue = DispatchQueue(label: "ch.threema.webClientConnectionQueue", attributes: [])
webClientRequestEventQueue = DispatchQueue(label: "ch.threema.webClientRequestEventQueue", attributes: [])
webClientRequestMsgQueue = DispatchQueue(label: "ch.threema.webClientRequestMsgQueue", attributes: [])
webClientSendQueue = DispatchQueue(label: "ch.threema.webClientSendQueue", attributes: [])
}
// MARK: NSCoding
required public init?(coder aDecoder: NSCoder) {
// super.init(coder:) is optional, see notes below
self.context = aDecoder.decodeObject(forKey: "context") as? WebConnectionContext
self.delegate = aDecoder.decodeObject(forKey: "delegate") as! WCConnectionDelegate
if let status = aDecoder.decodeObject(forKey: "connectionStatus") as? Int {
self.connectionStatus = WCConnectionState.init(rawValue: status)!
DDLogVerbose("Threema Web: Init from coder -> Set connection state to \(WCConnectionState.init(rawValue: status)!)")
} else {
self.connectionStatus = .disconnected
DDLogVerbose("Threema Web: Init from coder no predefined status -> Set connection state to new")
}
webClientConnectionQueue = DispatchQueue(label: "ch.threema.webClientConnectionQueue", attributes: [])
webClientRequestEventQueue = DispatchQueue(label: "ch.threema.webClientRequestEventQueue", attributes: [])
webClientRequestMsgQueue = DispatchQueue(label: "ch.threema.webClientRequestMsgQueue", attributes: [])
webClientSendQueue = DispatchQueue(label: "ch.threema.webClientSendQueue", attributes: [])
self.wca = aDecoder.decodeObject(forKey: "wca") as? String
if let free = aDecoder.decodeObject(forKey: "freeDisconnect") as? Bool {
self.freeDisconnect = free
} else {
self.freeDisconnect = true
}
self.connectionInfoRequest = aDecoder.decodeObject(forKey: "connectionInfoRequest") as? WebUpdateConnectionInfoRequest
self.connectionInfoResponse = aDecoder.decodeObject(forKey: "connectionInfoResponse") as?WebUpdateConnectionInfoResponse
self.pingInterval = UInt32(aDecoder.decodeInt32(forKey: "pingInterval"))
}
public func encode(with aCoder: NSCoder) {
// super.encodeWithCoder(aCoder) is optional, see notes below
aCoder.encode(context, forKey: "context")
aCoder.encode(delegate, forKey: "delegate")
aCoder.encode(connectionStatus.rawValue, forKey: "connectionStatus")
aCoder.encode(wca, forKey: "wca")
aCoder.encode(freeDisconnect, forKey: "freeDisconnect")
if connectionInfoRequest != nil {
aCoder.encode(connectionInfoRequest, forKey: "connectionInfoRequest")
}
if connectionInfoResponse != nil {
aCoder.encode(connectionInfoResponse, forKey: "connectionInfoResponse")
}
aCoder.encode(Int32(pingInterval), forKey: "pingInterval")
}
}
extension WCConnection {
// MARK: public functions
func connect(authToken: Data?) {
webClientConnectionQueue.async {
salty_log_change_level(UInt8(LEVEL_OFF))
salty_log_init(UInt8(LEVEL_OFF))
if self.delegate.currentWebClientSession() == nil {
ValidationLogger.shared().logString("Threema Web: Can't connect to web, webClientSession is nil")
return
}
var r_keypair: OpaquePointer? = nil
if let p = self.delegate.currentWebClientSession()!.privateKey {
let u8PtrPrivateKey: UnsafePointer = p.withUnsafeBytes {
$0.bindMemory(to: UInt8.self).baseAddress!
}
r_keypair = salty_keypair_restore(u8PtrPrivateKey)
} else {
r_keypair = salty_keypair_new()
}
let loop = salty_event_loop_new()
let remote = salty_event_loop_get_remote(loop)
let initiatorPermanentPublicKey = self.delegate.currentWebClientSession()!.initiatorPermanentPublicKey
let ippk: UnsafePointer = initiatorPermanentPublicKey!.withUnsafeBytes {
$0.bindMemory(to: UInt8.self).baseAddress!
}
var at: UnsafePointer? = nil
if authToken != nil {
at = authToken!.withUnsafeBytes {
$0.bindMemory(to: UInt8.self).baseAddress!
}
}
self.connectToWebClient(ippk: ippk, at: at, r_keypair: r_keypair!, loop: loop!, remote: remote!)
}
}
func close(close: Bool, forget: Bool, sendDisconnect: Bool, reason: WCConnectionStopReason) {
if connectionStatus != .disconnecting && self.connectionStatus != .disconnected {
self.connectionStatus = .disconnecting
DDLogVerbose("Threema Web: close -> Set connection state to \(self.connectionStatus)")
self.connectionWaitTimer?.invalidate()
self.connectionWaitTimer = nil
context?.cancelTimer()
delegate.currentWebClientSession()?.isConnecting = false
removeWCSessionFromRunning(reason: reason, forget: forget)
if sendDisconnect {
let messageData = WebUpdateConnectionDisconnectResponse.init(disconnectReason: reason.rawValue)
DDLogVerbose("Threema Web: MessagePack -> Send update/connectionDisconnect")
sendMessageToWeb(blacklisted: true, msgpack: messageData.messagePack(), true)
DispatchQueue.main.async {
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { (timer) in
self.saltyClientDisconnect(close: close)
}
}
} else {
self.connectionStatus = .disconnected
DDLogVerbose("Threema Web: close, sendDisconnect == false -> Set connection state to \(self.connectionStatus)")
if responder_disconnect != nil {
saltyClientDisconnect(close: close)
}
}
} else {
removeWCSessionFromRunning(reason: reason, forget: forget)
}
}
func sendMessageToWeb(blacklisted: Bool, msgpack: Data, _ connectionInfo: Bool = false) {
webClientSendQueue.sync {
if self.context != nil {
let chunker = try! Chunker(id: self.context!.messageCounter, data: msgpack, chunkSize: 64*1024)
var chunksToSend = [[UInt8]]()
for chunk in chunker {
if blacklisted == false {
DDLogVerbose("Threema Web: Cunkcache --> Add message")
} else {
DDLogVerbose("Threema Web: Cunkcache --> Do not add, message is blacklisted")
}
self.context!.append(chunk: blacklisted == true ? nil : chunk)
chunksToSend.append(chunk)
}
self.context!.messageCounter = self.context!.messageCounter + 1
DDLogVerbose("Threema Web: Message counter --> \(self.context!.messageCounter)")
for chunk in chunksToSend {
self.sendChunk(chunk: chunk, msgpack: msgpack, connectionInfo: connectionInfo)
}
}
}
}
func sendChunk(chunk: [UInt8], msgpack: Data?, connectionInfo: Bool) {
do {
var msgData = Data()
try msgData.pack(chunk)
if (responder_sender == nil) {
ValidationLogger.shared().logString("Threema Web: sendChunk: response_sender is nil")
return
}
if connectionStatus != .ready && !connectionInfo {
ValidationLogger.shared()?.logString("Threema Web: sendChunk status is not ready")
return
}
let success = salty_client_send_task_bytes(responder_sender, msgData.bytes, UInt32(msgData.bytes.count))
if success == UInt8(SEND_OK.rawValue) {
// msgpack is only set if message is not from chunkcache
if msgpack != nil {
delegate.currentMessageQueue().processSendFinished(finishedData: msgpack)
}
}
}
catch {
ValidationLogger.shared().logString("Threema Web: Error during create chunks")
close(close: true, forget: false, sendDisconnect: false, reason: .error)
}
}
func setWCConnectionStateToReady() {
connectionStatus = .ready
DDLogVerbose("Threema Web: setWCConnectionStateToReady -> Set connection state to \(connectionStatus)")
}
func setWCConnectionStateToConnectionInfoReceived() {
connectionStatus = .connectionInfoReceived
DDLogVerbose("Threema Web: setWCConnectionStateToConnectionInfoReceived -> Set connection state to \(connectionStatus)")
}
}
extension WCConnection {
// MARK: private functions
private func connectToWebClient(ippk: UnsafePointer, at: UnsafePointer?, r_keypair: OpaquePointer, loop: OpaquePointer, remote: OpaquePointer) {
var client_ret: salty_relayed_data_client_ret_t? = nil
let serverPermanentPublicKey = delegate.currentWebClientSession()!.serverPermanentPublicKey
let u8PtrServerPermanentPublicKey: UnsafePointer = serverPermanentPublicKey!.withUnsafeBytes {
$0.bindMemory(to: UInt8.self).baseAddress!
}
if delegate.currentWebClientSession()!.privateKey == nil {
let privateKey = salty_keypair_private_key(r_keypair)
let privateKeyData: Data? = NSData.init(bytes: privateKey, length: 32) as Data
WebClientSessionStore.shared.updateWebClientSession(session: delegate.currentWebClientSession()!, privateKey: privateKeyData!)
delegate.currentWCSession().privateKey = privateKeyData
}
if at != nil {
client_ret = salty_relayed_data_responder_new(r_keypair, remote, pingInterval, ippk, at, u8PtrServerPermanentPublicKey)
} else {
client_ret = salty_relayed_data_responder_new(r_keypair, remote, pingInterval, ippk, nil, u8PtrServerPermanentPublicKey)
}
responder_sender = client_ret!.sender_tx
responder_disconnect = client_ret!.disconnect_tx
responder_client = client_ret!.client
if client_ret!.success != 0 {
delegate.currentWebClientSession()!.isConnecting = false
let errorString = String(format:"Threema Web: salty relayed data responder error", (client_ret?.success)!)
ValidationLogger.shared().logString(errorString)
WCSessionManager.shared.removeWCSessionFromRunning(delegate.currentWCSession())
return
}
let saltyRTCHost: NSString = delegate.currentWebClientSession()!.saltyRTCHost! as NSString
let saltyRTCPort = delegate.currentWebClientSession()!.saltyRTCPort!.intValue
WebClientSessionStore.shared.updateWebClientSession(session: delegate.currentWebClientSession()!, lastConnection: Date())
let salty_client_init_ret = salty_client_init(saltyRTCHost.utf8String, UInt16(saltyRTCPort), client_ret!.client, loop, UInt16(0), nil, 0)
if salty_client_init_ret.success == UInt8(INIT_OK.rawValue) {
ValidationLogger.shared().logString("Threema Web: salty client init success")
ValidationLogger.shared().logString("Threema Web: Start EventDispatchQueue")
self.requestEventDispatchQueue(responder_event: salty_client_init_ret.event_rx, responder_receiver: client_ret!.receiver_rx)
// Connect to the SaltyRTC server, do the server and peer handshake and run the task loop.
// This call will only return once the connection has been terminated.
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)
WebClientSessionStore.shared.updateWebClientSession(session: delegate.currentWebClientSession()!, lastConnection: Date())
delegate.currentWebClientSession()!.isConnecting = false
context?.cancelTimer()
let errorString = String(format:"Threema Web: Connection ended with exit code %i", connect_success)
connectionStatus = .disconnected
DDLogVerbose("Threema Web: connectToWebClient -> Set connection state to \(connectionStatus)")
ValidationLogger.shared().logString(errorString)
salty_relayed_data_client_free(client_ret!.client);
salty_channel_sender_tx_free(client_ret!.sender_tx);
if self.freeDisconnect {
salty_channel_disconnect_tx_free(self.responder_disconnect); // only if web disconnect
}
salty_event_loop_free(loop);
responder_sender = nil
responder_disconnect = nil
responder_client = nil
freeDisconnect = true
self.connectionInfoResponse = nil
self.connectionInfoRequest = nil
} else {
delegate.currentWebClientSession()!.isConnecting = false
let errorString = String(format:"Threema Web: salty client init error", salty_client_init_ret.success)
ValidationLogger.shared().logString(errorString)
WCSessionManager.shared.removeWCSessionFromRunning(delegate.currentWCSession())
}
}
private func requestEventDispatchQueue(responder_event: OpaquePointer, responder_receiver: OpaquePointer) {
webClientRequestEventQueue.async {
let recv_event = salty_client_recv_event(responder_event, nil)
if recv_event.success == UInt8(RECV_OK.rawValue) {
let event = recv_event.event!
switch event.pointee.event_type {
case UInt8(EVENT_CONNECTING.rawValue):
ValidationLogger.shared().logString("Threema Web: EVENT_CONNECTING")
self.connectionStatus = .connecting
DDLogVerbose("Threema Web: EVENT_CONNECTING -> Set connection state to \(self.connectionStatus)")
break
case UInt8(EVENT_SERVER_HANDSHAKE_COMPLETED.rawValue):
if event.pointee.peer_connected == true {
self.connectionStatus = .serverHandshake
DDLogVerbose("Threema Web: EVENT_SERVER_HANDSHAKE_COMPLETED -> Set connection state to \(self.connectionStatus)")
} else {
self.connectionStatus = .serverHandshake
DDLogVerbose("Threema Web: EVENT_SERVER_HANDSHAKE_COMPLETED -> Set connection state to \(self.connectionStatus)")
ValidationLogger.shared().logString("Threema Web: Peer not connected")
// start timer and wait 10 seconds for peer
self.connectionWaitTimer?.invalidate()
DispatchQueue.main.async {
self.connectionWaitTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: false, block: { (timer) in
self.connectionWaitTimer?.invalidate()
self.connectionWaitTimer = nil
ValidationLogger.shared().logString("Threema Web: Error peer is not connected")
self.close(close: true, forget: false, sendDisconnect: true, reason: .stop)
})
}
}
ValidationLogger.shared().logString("Threema Web: EVENT_SERVER_HANDSHAKE_COMPLETED")
break
case UInt8(EVENT_PEER_HANDSHAKE_COMPLETED.rawValue):
self.connectionStatus = .peerHandshake
DDLogVerbose("Threema Web: EVENT_PEER_HANDSHAKE_COMPLETED -> Set connection state to \(self.connectionStatus)")
self.connectionWaitTimer?.invalidate()
self.connectionWaitTimer = nil
ValidationLogger.shared().logString("Threema Web: EVENT_PEER_HANDSHAKE_COMPLETED")
ValidationLogger.shared().logString("Threema Web: Set current session to active")
DispatchQueue.main.async {
if let webClientSession = self.delegate.currentWebClientSession() {
WebClientSessionStore.shared.updateWebClientSession(session: webClientSession, active: true)
}
}
// send connectionInfo
let tmpId = Data.init(count: 0)
let nonceData = "connectionidconnectionid".data(using: .utf8)
let id: UnsafePointer = tmpId.withUnsafeBytes {
$0.bindMemory(to: UInt8.self).baseAddress!
}
let nonce: UnsafePointer = nonceData!.withUnsafeBytes {
$0.bindMemory(to: UInt8.self).baseAddress!
}
let encrypt_decrypt_ret_t = salty_client_encrypt_with_session_keys(self.responder_client, id, 0, nonce)
if encrypt_decrypt_ret_t.success == UInt8(ENCRYPT_DECRYPT_OK.rawValue) {
let connectionId = Data.init(bytes: encrypt_decrypt_ret_t.bytes, count: encrypt_decrypt_ret_t.bytes_len)
let newContext = WebConnectionContext.init(connectionId: connectionId, delegate: self)
newContext.unchunker.delegate = self.delegate.currentWCSession()
self.connectionStatus = .connectionInfoSend
DDLogVerbose("Threema Web: EVENT_PEER_HANDSHAKE_COMPLETED -> Set connection state to \(self.connectionStatus)")
if self.context != nil {
newContext.previousConnectionContext = self.context
self.connectionInfoResponse = WebUpdateConnectionInfoResponse.init(currentId: connectionId, previousId: newContext.previousConnectionContext!.connectionId(), previousSequenceNumber: UInt32(newContext.previousConnectionContext!.incomingSequenceNumber))
newContext.messageCounter = newContext.previousConnectionContext!.messageCounter
newContext.unchunker = newContext.previousConnectionContext!.unchunker
newContext.unchunker.delegate = self.delegate.currentWCSession()
} else {
self.connectionInfoResponse = WebUpdateConnectionInfoResponse.init(currentId: connectionId, previousId: nil, previousSequenceNumber: nil)
}
self.context = newContext
DDLogVerbose("Threema Web: MessagePack -> Send update/connectionInfo")
self.sendMessageToWeb(blacklisted: true, msgpack: self.connectionInfoResponse!.messagePack(), true)
if self.connectionStatus == .connectionInfoReceived {
ValidationLogger.shared()?.logString("Threema Web: connectionInfoReceived maybeResume state: \(self.connectionStatus.rawValue)")
self.connectionStatus = .ready
DDLogVerbose("Threema Web: connectionStatus == .connectionInfoReceived -> Set connection state to \(self.connectionStatus)")
self.context!.connectionInfoRequest = self.connectionInfoRequest
self.connectionInfoRequest?.maybeResume(session: self.delegate.currentWCSession())
} else {
ValidationLogger.shared()?.logString("Threema Web: connectionInfo not received state: \(self.connectionStatus.rawValue)")
}
ValidationLogger.shared().logString("Threema Web: Start MsgDispatchQueue")
self.requestMsgDispatchQueue(responder_receiver: responder_receiver)
} else {
// stopp session because connectionid is empty
ValidationLogger.shared().logString("Threema Web: ENCRYPT_DECRYPT_ERROR")
self.close(close: true, forget: false, sendDisconnect: true, reason: .error)
}
break
case UInt8(EVENT_PEER_DISCONNECTED.rawValue):
ValidationLogger.shared().logString("Threema Web: EVENT_PEER_DISCONNECTED")
self.close(close: true, forget: false, sendDisconnect: false, reason: .stop)
break
default:
print("Threema Web: unexpected event type \(event.pointee.event_type)")
break
}
if event.pointee.event_type != UInt8(EVENT_PEER_DISCONNECTED.rawValue) {
self.requestEventDispatchQueue(responder_event: responder_event, responder_receiver: responder_receiver)
} else {
salty_channel_event_rx_free(responder_event)
}
} else {
print("Threema Web: received event error ", recv_event.success)
}
salty_client_recv_event_ret_free(recv_event)
}
}
private func requestMsgDispatchQueue(responder_receiver: OpaquePointer) {
webClientRequestMsgQueue.async {
let recv_ret = salty_client_recv_msg(responder_receiver, nil)
let success = recv_ret.success
if success == UInt8(RECV_OK.rawValue) {
let msg = recv_ret.msg!
switch msg.pointee.msg_type {
case UInt8(MSG_TASK.rawValue):
let count = JDI.ToInt(msg.pointee.msg_bytes_len)
let bytesArray = self.convert(length: count, data: msg.pointee.msg_bytes)
let chunkedData = Data(bytesArray)
do {
let unpackedData = try chunkedData.unpack()
if (self.context != nil) {
try self.context!.unchunker.addChunk(bytes: unpackedData as! Data)
self.context!.incomingSequenceNumber = self.context!.incomingSequenceNumber + 1
}
} catch {
ValidationLogger.shared().logString("Something went wrong while unchunk data: \(error)")
}
salty_client_recv_msg_ret_free(recv_ret)
break
case UInt8(MSG_CLOSE.rawValue):
ValidationLogger.shared().logString("Threema Web: MSG_CLOSE")
salty_client_recv_msg_ret_free(recv_ret)
break
default:
salty_client_recv_msg_ret_free(recv_ret)
break
}
}
if success != UInt8(RECV_STREAM_ENDED.rawValue) && success != UInt8(RECV_ERROR.rawValue) {
self.requestMsgDispatchQueue(responder_receiver: responder_receiver)
} else {
salty_channel_receiver_rx_free(responder_receiver)
}
}
}
private func removeWCSessionFromRunning(reason: WCConnectionStopReason, forget: Bool) {
if reason != .pause && reason != .replace {
ValidationLogger.shared().logString("Threema Web: Set current session by stop to inactive")
WCSessionManager.shared.removeWCSessionFromRunning(delegate.currentWCSession())
if forget, let webclientSession = delegate.currentWebClientSession() {
WebClientSessionStore.shared.deleteWebClientSession(webclientSession)
}
}
}
private func saltyClientDisconnect(close: Bool) {
let disconnectSuccess = salty_client_disconnect(self.responder_disconnect, WebSocketCode.closing.rawValue)
if disconnectSuccess == UInt8(DISCONNECT_OK.rawValue) || disconnectSuccess == UInt8(DISCONNECT_ERROR.rawValue) {
self.freeDisconnect = false
}
if close {
self.context = nil
}
self.connectionStatus = .disconnected
DDLogVerbose("Threema Web: close -> Set connection state to \(self.connectionStatus)")
}
}
extension WCConnection {
// MARK: Helper functions
private func convert(length: Int, data: UnsafePointer) -> [UInt8] {
let buffer = UnsafeBufferPointer(start: data, count: length);
return Array(buffer)
}
}
extension WCConnection: WebConnectionContextDelegate {
internal func currentWCSession() -> WCSession {
return delegate.currentWCSession()
}
}
public class JDI {
// To Int
public static func ToInt(_ x : Int8) -> Int {
return Int(x)
}
public static func ToInt(_ x : Int32) -> Int {
return Int(x)
}
public static func ToInt(_ x : Int64) -> Int {
return Int(truncatingIfNeeded: x)
}
public static func ToInt(_ x : Int) -> Int {
return x
}
public static func ToInt(_ x : UInt8) -> Int {
return Int(x)
}
public static func ToInt(_ x : UInt32) -> Int {
if MemoryLayout.size == MemoryLayout.size {
return Int(Int32(bitPattern: x)) // For 32-bit systems, non-authorized interpretation
}
return Int(x)
}
public static func ToInt(_ x : UInt64) -> Int {
return Int(truncatingIfNeeded: x)
}
public static func ToInt(_ x : UInt) -> Int {
return Int(bitPattern: x)
}
// To UInt
public static func ToUInt(_ x : Int8) -> UInt {
return UInt(bitPattern: Int(x)) // Extend sign bit, assume minus input significant
}
public static func ToUInt(_ x : Int32) -> UInt {
return UInt(truncatingIfNeeded: Int64(x)) // Extend sign bit, assume minus input significant
}
public static func ToUInt(_ x : Int64) -> UInt {
return UInt(truncatingIfNeeded: x)
}
public static func ToUInt(_ x : Int) -> UInt {
return UInt(bitPattern: x)
}
public static func ToUInt(_ x : UInt8) -> UInt {
return UInt(x)
}
public static func ToUInt(_ x : UInt32) -> UInt {
return UInt(x)
}
public static func ToUInt(_ x : UInt64) -> UInt {
return UInt(truncatingIfNeeded: x)
}
public static func ToUInt(_ x : UInt) -> UInt {
return x
}
}
extension String {
var unsafePointer: UnsafePointer {
return UnsafePointer((self as NSString).utf8String!)
}
}