mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
e28ec6b7ca
@ -1105,4 +1105,22 @@ public final class MediaBox {
|
||||
return EmptyDisposable
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public func allFileContexts() -> Signal<[(partial: String, complete: String)], NoError> {
|
||||
return Signal { subscriber in
|
||||
self.dataQueue.async {
|
||||
var result: [(partial: String, complete: String)] = []
|
||||
for (id, _) in self.fileContexts {
|
||||
let paths = self.storePathsForId(id.id)
|
||||
result.append((partial: paths.partial, complete: paths.complete))
|
||||
}
|
||||
subscriber.putNext(result)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
return EmptyDisposable
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1047,7 +1047,7 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration,
|
||||
|
||||
#if DEBUG
|
||||
//debugSaveState(basePath: basePath, name: "previous1")
|
||||
debugRestoreState(basePath: basePath, name: "previous1")
|
||||
//debugRestoreState(basePath: basePath, name: "previous1")
|
||||
#endif
|
||||
|
||||
let startTime = CFAbsoluteTimeGetCurrent()
|
||||
|
@ -73,6 +73,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
case playerEmbedding(Bool)
|
||||
case playlistPlayback(Bool)
|
||||
case videoCalls(Bool)
|
||||
case videoCallsReference(Bool)
|
||||
case videoCallsInfo(PresentationTheme, String)
|
||||
case hostInfo(PresentationTheme, String)
|
||||
case versionInfo(PresentationTheme)
|
||||
@ -89,7 +90,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return DebugControllerSection.experiments.rawValue
|
||||
case .clearTips, .reimport, .resetData, .resetDatabase, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .alternativeFolderTabs, .playerEmbedding, .playlistPlayback:
|
||||
return DebugControllerSection.experiments.rawValue
|
||||
case .videoCalls, .videoCallsInfo:
|
||||
case .videoCalls, .videoCallsReference, .videoCallsInfo:
|
||||
return DebugControllerSection.videoExperiments.rawValue
|
||||
case .hostInfo, .versionInfo:
|
||||
return DebugControllerSection.info.rawValue
|
||||
@ -150,12 +151,14 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return 25
|
||||
case .videoCalls:
|
||||
return 26
|
||||
case .videoCallsInfo:
|
||||
case .videoCallsReference:
|
||||
return 27
|
||||
case .hostInfo:
|
||||
case .videoCallsInfo:
|
||||
return 28
|
||||
case .versionInfo:
|
||||
case .hostInfo:
|
||||
return 29
|
||||
case .versionInfo:
|
||||
return 30
|
||||
}
|
||||
}
|
||||
|
||||
@ -583,6 +586,16 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
})
|
||||
}).start()
|
||||
})
|
||||
case let .videoCallsReference(value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Reference Implementation", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
|
||||
var settings = settings as? ExperimentalUISettings ?? ExperimentalUISettings.defaultSettings
|
||||
settings.videoCallsReference = value
|
||||
return settings
|
||||
})
|
||||
}).start()
|
||||
})
|
||||
case let .videoCallsInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .hostInfo(theme, string):
|
||||
@ -631,6 +644,9 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS
|
||||
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
|
||||
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
|
||||
entries.append(.videoCalls(experimentalSettings.videoCalls))
|
||||
if experimentalSettings.videoCalls {
|
||||
entries.append(.videoCallsReference(experimentalSettings.videoCallsReference))
|
||||
}
|
||||
entries.append(.videoCallsInfo(presentationData.theme, "Enables experimental transmission of electromagnetic radiation synchronized with pressure waves. Needs to be enabled on both sides."))
|
||||
|
||||
if let backupHostOverride = networkSettings?.backupHostOverride {
|
||||
|
@ -288,9 +288,9 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
self.isVideo = startWithVideo
|
||||
if self.isVideo {
|
||||
self.videoCapturer = OngoingCallVideoCapturer()
|
||||
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: .outgoingRequested, remoteVideoState: .inactive))
|
||||
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: .outgoingRequested, remoteVideoState: .active))
|
||||
} else {
|
||||
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: self.isVideoPossible ? .possible : .notAvailable, remoteVideoState: .inactive))
|
||||
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: self.isVideoPossible ? .possible : .notAvailable, remoteVideoState: .active))
|
||||
}
|
||||
|
||||
self.serializedData = serializedData
|
||||
@ -483,7 +483,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
} else {
|
||||
mappedVideoState = .notAvailable
|
||||
}
|
||||
if videoWasActive {
|
||||
if self.videoWasActive {
|
||||
mappedRemoteVideoState = .active
|
||||
} else {
|
||||
mappedRemoteVideoState = .inactive
|
||||
|
@ -117,8 +117,8 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
return OngoingCallContext.maxLayer
|
||||
}
|
||||
|
||||
public static func voipVersions(includeExperimental: Bool) -> [String] {
|
||||
return OngoingCallContext.versions(includeExperimental: includeExperimental)
|
||||
public static func voipVersions(includeExperimental: Bool, includeReference: Bool) -> [String] {
|
||||
return OngoingCallContext.versions(includeExperimental: includeExperimental, includeReference: includeReference)
|
||||
}
|
||||
|
||||
public init(accountManager: AccountManager, enableVideoCalls: Bool, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), isMediaPlaying: @escaping () -> Bool, resumeMediaPlayback: @escaping () -> Void, audioSession: ManagedAudioSession, activeAccounts: Signal<[Account], NoError>) {
|
||||
|
@ -251,7 +251,7 @@ public final class AccountContextImpl: AccountContext {
|
||||
self.experimentalUISettingsDisposable = (sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.experimentalUISettings])
|
||||
|> deliverOnMainQueue).start(next: { sharedData in
|
||||
if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings] as? ExperimentalUISettings {
|
||||
account.callSessionManager.updateVersions(versions: PresentationCallManagerImpl.voipVersions(includeExperimental: settings.videoCalls))
|
||||
account.callSessionManager.updateVersions(versions: PresentationCallManagerImpl.voipVersions(includeExperimental: settings.videoCalls, includeReference: settings.videoCallsReference))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -401,7 +401,7 @@ final class SharedApplicationContext {
|
||||
}
|
||||
}
|
||||
|
||||
let networkArguments = NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: PresentationCallManagerImpl.voipMaxLayer, voipVersions: PresentationCallManagerImpl.voipVersions(includeExperimental: false), appData: self.deviceToken.get()
|
||||
let networkArguments = NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: PresentationCallManagerImpl.voipMaxLayer, voipVersions: PresentationCallManagerImpl.voipVersions(includeExperimental: false, includeReference: false), appData: self.deviceToken.get()
|
||||
|> map { token in
|
||||
let data = buildConfig.bundleData(withAppToken: token, signatureDict: signatureDict)
|
||||
if let data = data, let jsonString = String(data: data, encoding: .utf8) {
|
||||
|
@ -10,6 +10,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
|
||||
public var knockoutWallpaper: Bool
|
||||
public var foldersTabAtBottom: Bool
|
||||
public var videoCalls: Bool
|
||||
public var videoCallsReference: Bool
|
||||
public var playerEmbedding: Bool
|
||||
public var playlistPlayback: Bool
|
||||
|
||||
@ -22,6 +23,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
|
||||
knockoutWallpaper: false,
|
||||
foldersTabAtBottom: false,
|
||||
videoCalls: false,
|
||||
videoCallsReference: true,
|
||||
playerEmbedding: false,
|
||||
playlistPlayback: false
|
||||
)
|
||||
@ -35,6 +37,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
|
||||
knockoutWallpaper: Bool,
|
||||
foldersTabAtBottom: Bool,
|
||||
videoCalls: Bool,
|
||||
videoCallsReference: Bool,
|
||||
playerEmbedding: Bool,
|
||||
playlistPlayback: Bool
|
||||
) {
|
||||
@ -45,6 +48,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
|
||||
self.knockoutWallpaper = knockoutWallpaper
|
||||
self.foldersTabAtBottom = foldersTabAtBottom
|
||||
self.videoCalls = videoCalls
|
||||
self.videoCallsReference = videoCallsReference
|
||||
self.playerEmbedding = playerEmbedding
|
||||
self.playlistPlayback = playlistPlayback
|
||||
}
|
||||
@ -57,6 +61,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
|
||||
self.knockoutWallpaper = decoder.decodeInt32ForKey("knockoutWallpaper", orElse: 0) != 0
|
||||
self.foldersTabAtBottom = decoder.decodeInt32ForKey("foldersTabAtBottom", orElse: 0) != 0
|
||||
self.videoCalls = decoder.decodeInt32ForKey("videoCalls", orElse: 0) != 0
|
||||
self.videoCallsReference = decoder.decodeInt32ForKey("videoCallsReference", orElse: 1) != 0
|
||||
self.playerEmbedding = decoder.decodeInt32ForKey("playerEmbedding", orElse: 0) != 0
|
||||
self.playlistPlayback = decoder.decodeInt32ForKey("playlistPlayback", orElse: 0) != 0
|
||||
}
|
||||
@ -69,6 +74,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
|
||||
encoder.encodeInt32(self.knockoutWallpaper ? 1 : 0, forKey: "knockoutWallpaper")
|
||||
encoder.encodeInt32(self.foldersTabAtBottom ? 1 : 0, forKey: "foldersTabAtBottom")
|
||||
encoder.encodeInt32(self.videoCalls ? 1 : 0, forKey: "videoCalls")
|
||||
encoder.encodeInt32(self.videoCallsReference ? 1 : 0, forKey: "videoCallsReference")
|
||||
encoder.encodeInt32(self.playerEmbedding ? 1 : 0, forKey: "playerEmbedding")
|
||||
encoder.encodeInt32(self.playlistPlayback ? 1 : 0, forKey: "playlistPlayback")
|
||||
}
|
||||
|
@ -248,6 +248,7 @@ private protocol OngoingCallThreadLocalContextProtocol: class {
|
||||
func nativeRequestVideo(_ capturer: OngoingCallVideoCapturer)
|
||||
func nativeAcceptVideo(_ capturer: OngoingCallVideoCapturer)
|
||||
func nativeStop(_ completion: @escaping (String?, Int64, Int64, Int64, Int64) -> Void)
|
||||
func nativeBeginTermination()
|
||||
func nativeDebugInfo() -> String
|
||||
func nativeVersion() -> String
|
||||
func nativeGetDerivedState() -> Data
|
||||
@ -270,6 +271,9 @@ extension OngoingCallThreadLocalContext: OngoingCallThreadLocalContextProtocol {
|
||||
self.stop(completion)
|
||||
}
|
||||
|
||||
func nativeBeginTermination() {
|
||||
}
|
||||
|
||||
func nativeSetIsMuted(_ value: Bool) {
|
||||
self.setIsMuted(value)
|
||||
}
|
||||
@ -336,6 +340,10 @@ extension OngoingCallThreadLocalContextWebrtc: OngoingCallThreadLocalContextProt
|
||||
self.stop(completion)
|
||||
}
|
||||
|
||||
func nativeBeginTermination() {
|
||||
self.beginTermination()
|
||||
}
|
||||
|
||||
func nativeSetIsMuted(_ value: Bool) {
|
||||
self.setIsMuted(value)
|
||||
}
|
||||
@ -458,14 +466,12 @@ public final class OngoingCallContext {
|
||||
|
||||
public static var maxLayer: Int32 {
|
||||
return OngoingCallThreadLocalContext.maxLayer()
|
||||
//return max(OngoingCallThreadLocalContext.maxLayer(), OngoingCallThreadLocalContextWebrtc.maxLayer())
|
||||
}
|
||||
|
||||
public static func versions(includeExperimental: Bool) -> [String] {
|
||||
public static func versions(includeExperimental: Bool, includeReference: Bool) -> [String] {
|
||||
var result: [String] = [OngoingCallThreadLocalContext.version()]
|
||||
if includeExperimental {
|
||||
result.append(OngoingCallThreadLocalContextWebrtc.version())
|
||||
//result.append(OngoingCallThreadLocalContextWebrtcCustom.version())
|
||||
result.append(contentsOf: OngoingCallThreadLocalContextWebrtc.versions(withIncludeReference: includeReference))
|
||||
}
|
||||
return result
|
||||
}
|
||||
@ -489,7 +495,7 @@ public final class OngoingCallContext {
|
||||
|> take(1)
|
||||
|> deliverOn(queue)).start(next: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
if version == OngoingCallThreadLocalContextWebrtc.version() {
|
||||
if OngoingCallThreadLocalContextWebrtc.versions(withIncludeReference: true).contains(version) {
|
||||
var voipProxyServer: VoipProxyServerWebrtc?
|
||||
if let proxyServer = proxyServer {
|
||||
switch proxyServer.connection {
|
||||
@ -520,7 +526,7 @@ public final class OngoingCallContext {
|
||||
))
|
||||
}
|
||||
}
|
||||
let context = OngoingCallThreadLocalContextWebrtc(queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, rtcServers: rtcServers, networkType: ongoingNetworkTypeForTypeWebrtc(initialNetworkType), dataSaving: ongoingDataSavingForTypeWebrtc(dataSaving), derivedState: derivedState.data, key: key, isOutgoing: isOutgoing, primaryConnection: callConnectionDescriptionWebrtc(connections.primary), alternativeConnections: connections.alternatives.map(callConnectionDescriptionWebrtc), maxLayer: maxLayer, allowP2P: allowP2P, logPath: logPath, sendSignalingData: { [weak callSessionManager] data in
|
||||
let context = OngoingCallThreadLocalContextWebrtc(version: version, queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, rtcServers: rtcServers, networkType: ongoingNetworkTypeForTypeWebrtc(initialNetworkType), dataSaving: ongoingDataSavingForTypeWebrtc(dataSaving), derivedState: derivedState.data, key: key, isOutgoing: isOutgoing, primaryConnection: callConnectionDescriptionWebrtc(connections.primary), alternativeConnections: connections.alternatives.map(callConnectionDescriptionWebrtc), maxLayer: maxLayer, allowP2P: allowP2P, logPath: logPath, sendSignalingData: { [weak callSessionManager] data in
|
||||
callSessionManager?.sendSignalingData(internalId: internalId, data: data)
|
||||
}, videoCapturer: video?.impl)
|
||||
|
||||
@ -631,6 +637,12 @@ public final class OngoingCallContext {
|
||||
}
|
||||
}
|
||||
|
||||
public func beginTermination() {
|
||||
self.withContext { context in
|
||||
context.nativeBeginTermination()
|
||||
}
|
||||
}
|
||||
|
||||
public func stop(callId: CallId? = nil, sendDebugLogs: Bool = false, debugLogValue: Promise<String?>) {
|
||||
let account = self.account
|
||||
let logPath = self.logPath
|
||||
|
@ -1,353 +0,0 @@
|
||||
#include "NetworkManager.h"
|
||||
|
||||
#include "p2p/base/basic_packet_socket_factory.h"
|
||||
#include "p2p/client/basic_port_allocator.h"
|
||||
#include "p2p/base/p2p_transport_channel.h"
|
||||
#include "p2p/base/basic_async_resolver_factory.h"
|
||||
#include "api/packet_socket_factory.h"
|
||||
#include "rtc_base/task_utils/to_queued_task.h"
|
||||
#include "p2p/base/ice_credentials_iterator.h"
|
||||
#include "api/jsep_ice_candidate.h"
|
||||
|
||||
extern "C" {
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/modes.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/crypto.h>
|
||||
}
|
||||
|
||||
#ifdef TGVOIP_NAMESPACE
|
||||
namespace TGVOIP_NAMESPACE {
|
||||
#endif
|
||||
|
||||
static void KDF2(unsigned char *encryptionKey, unsigned char *msgKey, size_t x, unsigned char *aesKey, unsigned char *aesIv) {
|
||||
uint8_t sA[32], sB[32];
|
||||
uint8_t buf[16 + 36];
|
||||
memcpy(buf, msgKey, 16);
|
||||
memcpy(buf + 16, encryptionKey + x, 36);
|
||||
SHA256(buf, 16 + 36, sA);
|
||||
memcpy(buf, encryptionKey + 40 + x, 36);
|
||||
memcpy(buf + 36, msgKey, 16);
|
||||
SHA256(buf, 36 + 16, sB);
|
||||
memcpy(aesKey, sA, 8);
|
||||
memcpy(aesKey + 8, sB + 8, 16);
|
||||
memcpy(aesKey + 8 + 16, sA + 24, 8);
|
||||
memcpy(aesIv, sB, 8);
|
||||
memcpy(aesIv + 8, sA + 8, 16);
|
||||
memcpy(aesIv + 8 + 16, sB + 24, 8);
|
||||
}
|
||||
|
||||
static void aesIgeEncrypt(uint8_t *in, uint8_t *out, size_t length, uint8_t *key, uint8_t *iv) {
|
||||
AES_KEY akey;
|
||||
AES_set_encrypt_key(key, 32*8, &akey);
|
||||
AES_ige_encrypt(in, out, length, &akey, iv, AES_ENCRYPT);
|
||||
}
|
||||
|
||||
static void aesIgeDecrypt(uint8_t *in, uint8_t *out, size_t length, uint8_t *key, uint8_t *iv) {
|
||||
AES_KEY akey;
|
||||
AES_set_decrypt_key(key, 32*8, &akey);
|
||||
AES_ige_encrypt(in, out, length, &akey, iv, AES_DECRYPT);
|
||||
}
|
||||
|
||||
static absl::optional<rtc::CopyOnWriteBuffer> decryptPacket(const rtc::CopyOnWriteBuffer &packet, const TgVoipEncryptionKey &encryptionKey) {
|
||||
if (packet.size() < 16 + 16) {
|
||||
return absl::nullopt;
|
||||
}
|
||||
unsigned char msgKey[16];
|
||||
memcpy(msgKey, packet.data(), 16);
|
||||
|
||||
int x = encryptionKey.isOutgoing ? 8 : 0;
|
||||
|
||||
unsigned char aesKey[32];
|
||||
unsigned char aesIv[32];
|
||||
KDF2((unsigned char *)encryptionKey.value.data(), msgKey, x, aesKey, aesIv);
|
||||
size_t decryptedSize = packet.size() - 16;
|
||||
if (decryptedSize < 0 || decryptedSize > 128 * 1024) {
|
||||
return absl::nullopt;
|
||||
}
|
||||
if (decryptedSize % 16 != 0) {
|
||||
return absl::nullopt;
|
||||
}
|
||||
rtc::Buffer decryptionBuffer(decryptedSize);
|
||||
aesIgeDecrypt(((uint8_t *)packet.data()) + 16, decryptionBuffer.begin(), decryptionBuffer.size(), aesKey, aesIv);
|
||||
|
||||
rtc::ByteBufferWriter msgKeyData;
|
||||
msgKeyData.WriteBytes((const char *)encryptionKey.value.data() + 88 + x, 32);
|
||||
msgKeyData.WriteBytes((const char *)decryptionBuffer.data(), decryptionBuffer.size());
|
||||
unsigned char msgKeyLarge[32];
|
||||
SHA256((uint8_t *)msgKeyData.Data(), msgKeyData.Length(), msgKeyLarge);
|
||||
|
||||
uint16_t innerSize;
|
||||
memcpy(&innerSize, decryptionBuffer.data(), 2);
|
||||
|
||||
unsigned char checkMsgKey[16];
|
||||
memcpy(checkMsgKey, msgKeyLarge + 8, 16);
|
||||
|
||||
if (memcmp(checkMsgKey, msgKey, 16) != 0) {
|
||||
return absl::nullopt;
|
||||
}
|
||||
|
||||
if (innerSize < 0 || innerSize > decryptionBuffer.size() - 2) {
|
||||
return absl::nullopt;
|
||||
}
|
||||
|
||||
rtc::CopyOnWriteBuffer decryptedPacket;
|
||||
decryptedPacket.AppendData((const char *)decryptionBuffer.data() + 2, innerSize);
|
||||
return decryptedPacket;
|
||||
}
|
||||
|
||||
static absl::optional<rtc::Buffer> encryptPacket(const rtc::CopyOnWriteBuffer &packet, const TgVoipEncryptionKey &encryptionKey) {
|
||||
if (packet.size() > UINT16_MAX) {
|
||||
return absl::nullopt;
|
||||
}
|
||||
|
||||
rtc::ByteBufferWriter innerData;
|
||||
uint16_t packetSize = (uint16_t)packet.size();
|
||||
innerData.WriteBytes((const char *)&packetSize, 2);
|
||||
innerData.WriteBytes((const char *)packet.data(), packet.size());
|
||||
|
||||
size_t innerPadding = 16 - innerData.Length() % 16;
|
||||
uint8_t paddingData[16];
|
||||
RAND_bytes(paddingData, (int)innerPadding);
|
||||
innerData.WriteBytes((const char *)paddingData, innerPadding);
|
||||
|
||||
if (innerData.Length() % 16 != 0) {
|
||||
assert(false);
|
||||
return absl::nullopt;
|
||||
}
|
||||
|
||||
int x = encryptionKey.isOutgoing ? 0 : 8;
|
||||
|
||||
rtc::ByteBufferWriter msgKeyData;
|
||||
msgKeyData.WriteBytes((const char *)encryptionKey.value.data() + 88 + x, 32);
|
||||
msgKeyData.WriteBytes(innerData.Data(), innerData.Length());
|
||||
unsigned char msgKeyLarge[32];
|
||||
SHA256((uint8_t *)msgKeyData.Data(), msgKeyData.Length(), msgKeyLarge);
|
||||
|
||||
unsigned char msgKey[16];
|
||||
memcpy(msgKey, msgKeyLarge + 8, 16);
|
||||
|
||||
unsigned char aesKey[32];
|
||||
unsigned char aesIv[32];
|
||||
KDF2((unsigned char *)encryptionKey.value.data(), msgKey, x, aesKey, aesIv);
|
||||
|
||||
rtc::Buffer encryptedPacket;
|
||||
encryptedPacket.AppendData((const char *)msgKey, 16);
|
||||
|
||||
rtc::Buffer encryptionBuffer(innerData.Length());
|
||||
aesIgeEncrypt((uint8_t *)innerData.Data(), encryptionBuffer.begin(), innerData.Length(), aesKey, aesIv);
|
||||
|
||||
encryptedPacket.AppendData(encryptionBuffer.begin(), encryptionBuffer.size());
|
||||
|
||||
/*rtc::CopyOnWriteBuffer testBuffer;
|
||||
testBuffer.AppendData(encryptedPacket.data(), encryptedPacket.size());
|
||||
TgVoipEncryptionKey testKey;
|
||||
testKey.value = encryptionKey.value;
|
||||
testKey.isOutgoing = !encryptionKey.isOutgoing;
|
||||
decryptPacket(testBuffer, testKey);*/
|
||||
|
||||
return encryptedPacket;
|
||||
}
|
||||
|
||||
NetworkManager::NetworkManager(
|
||||
rtc::Thread *thread,
|
||||
TgVoipEncryptionKey encryptionKey,
|
||||
bool enableP2P,
|
||||
std::vector<TgVoipRtcServer> const &rtcServers,
|
||||
std::function<void (const NetworkManager::State &)> stateUpdated,
|
||||
std::function<void (const rtc::CopyOnWriteBuffer &)> packetReceived,
|
||||
std::function<void (const std::vector<uint8_t> &)> signalingDataEmitted
|
||||
) :
|
||||
_thread(thread),
|
||||
_encryptionKey(encryptionKey),
|
||||
_stateUpdated(stateUpdated),
|
||||
_packetReceived(packetReceived),
|
||||
_signalingDataEmitted(signalingDataEmitted) {
|
||||
assert(_thread->IsCurrent());
|
||||
|
||||
_socketFactory.reset(new rtc::BasicPacketSocketFactory(_thread));
|
||||
|
||||
_networkManager = std::make_unique<rtc::BasicNetworkManager>();
|
||||
_portAllocator.reset(new cricket::BasicPortAllocator(_networkManager.get(), _socketFactory.get(), nullptr, nullptr));
|
||||
|
||||
uint32_t flags = cricket::PORTALLOCATOR_DISABLE_TCP;
|
||||
if (!enableP2P) {
|
||||
flags |= cricket::PORTALLOCATOR_DISABLE_UDP;
|
||||
flags |= cricket::PORTALLOCATOR_DISABLE_STUN;
|
||||
}
|
||||
_portAllocator->set_flags(_portAllocator->flags() | flags);
|
||||
_portAllocator->Initialize();
|
||||
|
||||
cricket::ServerAddresses stunServers;
|
||||
std::vector<cricket::RelayServerConfig> turnServers;
|
||||
|
||||
if (rtcServers.size() == 0 || rtcServers[0].host == "hlgkfjdrtjfykgulhijkljhulyo.uksouth.cloudapp.azure.com") {
|
||||
rtc::SocketAddress defaultStunAddress = rtc::SocketAddress("134.122.52.178", 3478);
|
||||
stunServers.insert(defaultStunAddress);
|
||||
|
||||
turnServers.push_back(cricket::RelayServerConfig(
|
||||
rtc::SocketAddress("134.122.52.178", 3478),
|
||||
"openrelay",
|
||||
"openrelay",
|
||||
cricket::PROTO_UDP
|
||||
));
|
||||
} else {
|
||||
for (auto &server : rtcServers) {
|
||||
if (server.isTurn) {
|
||||
turnServers.push_back(cricket::RelayServerConfig(
|
||||
rtc::SocketAddress(server.host, server.port),
|
||||
server.login,
|
||||
server.password,
|
||||
cricket::PROTO_UDP
|
||||
));
|
||||
} else {
|
||||
rtc::SocketAddress stunAddress = rtc::SocketAddress(server.host, server.port);
|
||||
stunServers.insert(stunAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_portAllocator->SetConfiguration(stunServers, turnServers, 2, webrtc::NO_PRUNE);
|
||||
|
||||
_asyncResolverFactory = std::make_unique<webrtc::BasicAsyncResolverFactory>();
|
||||
_transportChannel.reset(new cricket::P2PTransportChannel("transport", 0, _portAllocator.get(), _asyncResolverFactory.get(), nullptr));
|
||||
|
||||
cricket::IceConfig iceConfig;
|
||||
iceConfig.continual_gathering_policy = cricket::GATHER_CONTINUALLY;
|
||||
_transportChannel->SetIceConfig(iceConfig);
|
||||
|
||||
cricket::IceParameters localIceParameters(
|
||||
"gcp3",
|
||||
"zWDKozH8/3JWt8he3M/CMj5R",
|
||||
false
|
||||
);
|
||||
cricket::IceParameters remoteIceParameters(
|
||||
"acp3",
|
||||
"aWDKozH8/3JWt8he3M/CMj5R",
|
||||
false
|
||||
);
|
||||
|
||||
_transportChannel->SetIceParameters(_encryptionKey.isOutgoing ? localIceParameters : remoteIceParameters);
|
||||
_transportChannel->SetIceRole(_encryptionKey.isOutgoing ? cricket::ICEROLE_CONTROLLING : cricket::ICEROLE_CONTROLLED);
|
||||
|
||||
_transportChannel->SignalCandidateGathered.connect(this, &NetworkManager::candidateGathered);
|
||||
_transportChannel->SignalGatheringState.connect(this, &NetworkManager::candidateGatheringState);
|
||||
_transportChannel->SignalIceTransportStateChanged.connect(this, &NetworkManager::transportStateChanged);
|
||||
_transportChannel->SignalReadPacket.connect(this, &NetworkManager::transportPacketReceived);
|
||||
|
||||
_transportChannel->MaybeStartGathering();
|
||||
|
||||
_transportChannel->SetRemoteIceMode(cricket::ICEMODE_FULL);
|
||||
_transportChannel->SetRemoteIceParameters((!_encryptionKey.isOutgoing) ? localIceParameters : remoteIceParameters);
|
||||
}
|
||||
|
||||
NetworkManager::~NetworkManager() {
|
||||
assert(_thread->IsCurrent());
|
||||
|
||||
_transportChannel.reset();
|
||||
_asyncResolverFactory.reset();
|
||||
_portAllocator.reset();
|
||||
_networkManager.reset();
|
||||
_socketFactory.reset();
|
||||
}
|
||||
|
||||
void NetworkManager::receiveSignalingData(const rtc::CopyOnWriteBuffer &data) {
|
||||
rtc::ByteBufferReader reader((const char *)data.data(), data.size());
|
||||
uint32_t candidateCount = 0;
|
||||
if (!reader.ReadUInt32(&candidateCount)) {
|
||||
return;
|
||||
}
|
||||
std::vector<std::string> candidates;
|
||||
for (uint32_t i = 0; i < candidateCount; i++) {
|
||||
uint32_t candidateLength = 0;
|
||||
if (!reader.ReadUInt32(&candidateLength)) {
|
||||
return;
|
||||
}
|
||||
std::string candidate;
|
||||
if (!reader.ReadString(&candidate, candidateLength)) {
|
||||
return;
|
||||
}
|
||||
candidates.push_back(candidate);
|
||||
}
|
||||
|
||||
for (auto &serializedCandidate : candidates) {
|
||||
webrtc::JsepIceCandidate parseCandidate("", 0);
|
||||
if (parseCandidate.Initialize(serializedCandidate, nullptr)) {
|
||||
auto parsedCandidate = parseCandidate.candidate();
|
||||
_transportChannel->AddRemoteCandidate(parsedCandidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkManager::sendPacket(const rtc::CopyOnWriteBuffer &packet) {
|
||||
auto encryptedPacket = encryptPacket(packet, _encryptionKey);
|
||||
if (encryptedPacket.has_value()) {
|
||||
rtc::PacketOptions packetOptions;
|
||||
_transportChannel->SendPacket((const char *)encryptedPacket->data(), encryptedPacket->size(), packetOptions, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkManager::candidateGathered(cricket::IceTransportInternal *transport, const cricket::Candidate &candidate) {
|
||||
assert(_thread->IsCurrent());
|
||||
webrtc::JsepIceCandidate iceCandidate("", 0);
|
||||
iceCandidate.SetCandidate(candidate);
|
||||
std::string serializedCandidate;
|
||||
if (!iceCandidate.ToString(&serializedCandidate)) {
|
||||
return;
|
||||
}
|
||||
std::vector<std::string> candidates;
|
||||
candidates.push_back(serializedCandidate);
|
||||
|
||||
rtc::ByteBufferWriter writer;
|
||||
writer.WriteUInt32((uint32_t)candidates.size());
|
||||
for (auto string : candidates) {
|
||||
writer.WriteUInt32((uint32_t)string.size());
|
||||
writer.WriteString(string);
|
||||
}
|
||||
std::vector<uint8_t> data;
|
||||
data.resize(writer.Length());
|
||||
memcpy(data.data(), writer.Data(), writer.Length());
|
||||
_signalingDataEmitted(data);
|
||||
}
|
||||
|
||||
void NetworkManager::candidateGatheringState(cricket::IceTransportInternal *transport) {
|
||||
assert(_thread->IsCurrent());
|
||||
}
|
||||
|
||||
void NetworkManager::transportStateChanged(cricket::IceTransportInternal *transport) {
|
||||
assert(_thread->IsCurrent());
|
||||
|
||||
auto state = transport->GetIceTransportState();
|
||||
bool isConnected = false;
|
||||
switch (state) {
|
||||
case webrtc::IceTransportState::kConnected:
|
||||
case webrtc::IceTransportState::kCompleted:
|
||||
isConnected = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
NetworkManager::State emitState;
|
||||
emitState.isReadyToSendData = isConnected;
|
||||
_stateUpdated(emitState);
|
||||
}
|
||||
|
||||
void NetworkManager::transportReadyToSend(cricket::IceTransportInternal *transport) {
|
||||
assert(_thread->IsCurrent());
|
||||
}
|
||||
|
||||
void NetworkManager::transportPacketReceived(rtc::PacketTransportInternal *transport, const char *bytes, size_t size, const int64_t ×tamp, int unused) {
|
||||
assert(_thread->IsCurrent());
|
||||
rtc::CopyOnWriteBuffer packet;
|
||||
packet.AppendData(bytes, size);
|
||||
|
||||
auto decryptedPacket = decryptPacket(packet, _encryptionKey);
|
||||
if (decryptedPacket.has_value()) {
|
||||
_packetReceived(decryptedPacket.value());
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TGVOIP_NAMESPACE
|
||||
}
|
||||
#endif
|
@ -107,12 +107,14 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
|
||||
+ (void)setupLoggingFunction:(void (* _Nullable)(NSString * _Nullable))loggingFunction;
|
||||
+ (void)applyServerConfig:(NSString * _Nullable)data;
|
||||
+ (int32_t)maxLayer;
|
||||
+ (NSString * _Nonnull)version;
|
||||
+ (NSArray<NSString *> * _Nonnull)versionsWithIncludeReference:(bool)includeReference;
|
||||
|
||||
@property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallStateWebrtc, OngoingCallVideoStateWebrtc, OngoingCallRemoteVideoStateWebrtc);
|
||||
@property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t);
|
||||
|
||||
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy rtcServers:(NSArray<VoipRtcServerWebrtc *> * _Nonnull)rtcServers networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescriptionWebrtc * _Nonnull)primaryConnection alternativeConnections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer;
|
||||
- (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy rtcServers:(NSArray<VoipRtcServerWebrtc *> * _Nonnull)rtcServers networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescriptionWebrtc * _Nonnull)primaryConnection alternativeConnections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer;
|
||||
|
||||
- (void)beginTermination;
|
||||
- (void)stop:(void (^_Nullable)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile))completion;
|
||||
|
||||
- (bool)needRate;
|
||||
|
@ -7,6 +7,8 @@
|
||||
|
||||
#import "Instance.h"
|
||||
#import "InstanceImpl.h"
|
||||
#import "reference/InstanceImplReference.h"
|
||||
|
||||
#import "VideoCaptureInterface.h"
|
||||
|
||||
#ifndef WEBRTC_IOS
|
||||
@ -102,7 +104,26 @@
|
||||
|
||||
@end
|
||||
|
||||
@interface OngoingCallThreadLocalContextWebrtcTerminationResult : NSObject
|
||||
|
||||
@property (nonatomic, readonly) tgcalls::FinalState finalState;
|
||||
|
||||
@end
|
||||
|
||||
@implementation OngoingCallThreadLocalContextWebrtcTerminationResult
|
||||
|
||||
- (instancetype)initWithFinalState:(tgcalls::FinalState)finalState {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_finalState = finalState;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface OngoingCallThreadLocalContextWebrtc () {
|
||||
NSString *_version;
|
||||
id<OngoingCallThreadLocalContextQueueWebrtc> _queue;
|
||||
int32_t _contextId;
|
||||
|
||||
@ -113,6 +134,7 @@
|
||||
NSTimeInterval _callPacketTimeout;
|
||||
|
||||
std::unique_ptr<tgcalls::Instance> _tgVoip;
|
||||
OngoingCallThreadLocalContextWebrtcTerminationResult *_terminationResult;
|
||||
|
||||
OngoingCallStateWebrtc _state;
|
||||
OngoingCallVideoStateWebrtc _videoState;
|
||||
@ -213,16 +235,23 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
return 92;
|
||||
}
|
||||
|
||||
+ (NSString *)version {
|
||||
return @"2.7.7";
|
||||
+ (NSArray<NSString *> * _Nonnull)versionsWithIncludeReference:(bool)includeReference {
|
||||
if (includeReference) {
|
||||
return @[@"2.7.7", @"2.8.8"];
|
||||
} else {
|
||||
return @[@"2.7.7"];
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy rtcServers:(NSArray<VoipRtcServerWebrtc *> * _Nonnull)rtcServers networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescriptionWebrtc * _Nonnull)primaryConnection alternativeConnections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath sendSignalingData:(void (^)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer {
|
||||
- (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy rtcServers:(NSArray<VoipRtcServerWebrtc *> * _Nonnull)rtcServers networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescriptionWebrtc * _Nonnull)primaryConnection alternativeConnections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath sendSignalingData:(void (^)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_version = version;
|
||||
_queue = queue;
|
||||
assert([queue isCurrent]);
|
||||
|
||||
assert([[OngoingCallThreadLocalContextWebrtc versionsWithIncludeReference:true] containsObject:version]);
|
||||
|
||||
_callReceiveTimeout = 20.0;
|
||||
_callRingTimeout = 90.0;
|
||||
_callConnectTimeout = 30.0;
|
||||
@ -235,7 +264,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
_remoteVideoState = OngoingCallRemoteVideoStateActive;
|
||||
} else {
|
||||
_videoState = OngoingCallVideoStatePossible;
|
||||
_remoteVideoState = OngoingCallRemoteVideoStateInactive;
|
||||
_remoteVideoState = OngoingCallRemoteVideoStateActive;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> derivedStateValue;
|
||||
@ -302,9 +331,8 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
.maxApiLayer = [OngoingCallThreadLocalContextWebrtc maxLayer]
|
||||
};
|
||||
|
||||
std::vector<uint8_t> encryptionKeyValue;
|
||||
encryptionKeyValue.resize(key.length);
|
||||
memcpy(encryptionKeyValue.data(), key.bytes, key.length);
|
||||
auto encryptionKeyValue = std::make_shared<std::array<uint8_t, 256>>();
|
||||
memcpy(encryptionKeyValue->data(), key.bytes, key.length);
|
||||
|
||||
tgcalls::EncryptionKey encryptionKey(encryptionKeyValue, isOutgoing);
|
||||
|
||||
@ -312,8 +340,9 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
tgcalls::Register<tgcalls::InstanceImpl>();
|
||||
tgcalls::Register<tgcalls::InstanceImplReference>();
|
||||
});
|
||||
_tgVoip = tgcalls::Meta::Create("2.7.7", (tgcalls::Descriptor){
|
||||
_tgVoip = tgcalls::Meta::Create([version UTF8String], (tgcalls::Descriptor){
|
||||
.config = config,
|
||||
.persistentState = (tgcalls::PersistentState){ derivedStateValue },
|
||||
.endpoints = endpoints,
|
||||
@ -346,8 +375,16 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
}
|
||||
}];
|
||||
},
|
||||
.signalBarsUpdated = [](int value) {
|
||||
|
||||
.signalBarsUpdated = [weakSelf, queue](int value) {
|
||||
[queue dispatch:^{
|
||||
__strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf;
|
||||
if (strongSelf) {
|
||||
strongSelf->_signalBars = value;
|
||||
if (strongSelf->_signalBarsChanged) {
|
||||
strongSelf->_signalBarsChanged(value);
|
||||
}
|
||||
}
|
||||
}];
|
||||
},
|
||||
.remoteVideoIsActiveUpdated = [weakSelf, queue](bool isActive) {
|
||||
[queue dispatch:^{
|
||||
@ -396,15 +433,34 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
- (void)stopInstanceIfNeeded {
|
||||
if (!_tgVoip) {
|
||||
return;
|
||||
}
|
||||
tgcalls::FinalState finalState = _tgVoip->stop();
|
||||
_tgVoip = nil;
|
||||
_terminationResult = [[OngoingCallThreadLocalContextWebrtcTerminationResult alloc] initWithFinalState:finalState];
|
||||
}
|
||||
|
||||
- (void)beginTermination {
|
||||
[self stopInstanceIfNeeded];
|
||||
}
|
||||
|
||||
- (void)stop:(void (^)(NSString *, int64_t, int64_t, int64_t, int64_t))completion {
|
||||
if (_tgVoip) {
|
||||
tgcalls::FinalState finalState = _tgVoip->stop();
|
||||
|
||||
NSString *debugLog = [NSString stringWithUTF8String:finalState.debugLog.c_str()];
|
||||
_lastDerivedState = [[NSData alloc] initWithBytes:finalState.persistentState.value.data() length:finalState.persistentState.value.size()];
|
||||
|
||||
if (completion) {
|
||||
completion(debugLog, finalState.trafficStats.bytesSentWifi, finalState.trafficStats.bytesReceivedWifi, finalState.trafficStats.bytesSentMobile, finalState.trafficStats.bytesReceivedMobile);
|
||||
[self stopInstanceIfNeeded];
|
||||
|
||||
if (completion) {
|
||||
if (_terminationResult) {
|
||||
NSString *debugLog = [NSString stringWithUTF8String:_terminationResult.finalState.debugLog.c_str()];
|
||||
_lastDerivedState = [[NSData alloc] initWithBytes:_terminationResult.finalState.persistentState.value.data() length:_terminationResult.finalState.persistentState.value.size()];
|
||||
|
||||
if (completion) {
|
||||
completion(debugLog, _terminationResult.finalState.trafficStats.bytesSentWifi, _terminationResult.finalState.trafficStats.bytesReceivedWifi, _terminationResult.finalState.trafficStats.bytesSentMobile, _terminationResult.finalState.trafficStats.bytesReceivedMobile);
|
||||
}
|
||||
} else {
|
||||
if (completion) {
|
||||
completion(@"", 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -421,7 +477,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
}
|
||||
|
||||
- (NSString *)version {
|
||||
return @"2.7.7";
|
||||
return _version;
|
||||
}
|
||||
|
||||
- (NSData * _Nonnull)getDerivedState {
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 83c85d20ccdde154acca4b964317de1e695f95d1
|
||||
Subproject commit 8e9d3e56d43ffa4ed9ababd5fe7a4b5df8ec94d1
|
Loading…
x
Reference in New Issue
Block a user