diff --git a/.bazelrc b/.bazelrc index 1ffa5c2f15..55d4d3adf2 100644 --- a/.bazelrc +++ b/.bazelrc @@ -4,9 +4,6 @@ build --action_env=ZERO_AR_DATE=1 build --strategy=Genrule=local build --apple_platform_type=ios build --cxxopt='-std=c++14' -build --copt='-w' -build --swiftcopt='-Xcc' -build --swiftcopt='-w' build --spawn_strategy=local build --strategy=SwiftCompile=local build --features=debug_prefix_map_pwd_is_dot diff --git a/.gitmodules b/.gitmodules index d416d6ad33..408376ccba 100644 --- a/.gitmodules +++ b/.gitmodules @@ -17,3 +17,9 @@ [submodule "build-system/tulsi"] path = build-system/tulsi url = https://github.com/ali-fareed/tulsi.git +[submodule "third-party/depot_tools"] + path = third-party/depot_tools + url = https://chromium.googlesource.com/chromium/tools/depot_tools.git +[submodule "third-party/webrtc/webrtc-ios"] + path = third-party/webrtc/webrtc-ios +url=../webrtc-ios.git diff --git a/Telegram/BUILD b/Telegram/BUILD index f72f1025f2..61efb4a8e0 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -161,8 +161,6 @@ swift_library( "//submodules/PasswordSetupUI:PasswordSetupUIAssets", "//submodules/TelegramUI:TelegramUIResources", "//submodules/TelegramUI:TelegramUIAssets", - #"//submodules/WalletUI:WalletUIResources", - #"//submodules/WalletUI:WalletUIAssets", ], deps = [ "//submodules/TelegramUI:TelegramUI", diff --git a/buildbox/build-telegram.sh b/buildbox/build-telegram.sh index 8a98f552d6..8651822a63 100644 --- a/buildbox/build-telegram.sh +++ b/buildbox/build-telegram.sh @@ -9,6 +9,7 @@ XCODE_VERSION="11.2" GUEST_SHELL="bash" VM_BASE_NAME="macos$(echo $MACOS_VERSION | sed -e 's/\.'/_/g)_Xcode$(echo $XCODE_VERSION | sed -e 's/\.'/_/g)" +echo "Base VM: \"$VM_BASE_NAME\"" case "$(uname -s)" in Linux*) BUILD_MACHINE=linux;; @@ -128,7 +129,8 @@ fi SOURCE_DIR=$(basename "$BASE_DIR") rm -f "$BUILDBOX_DIR/transient-data/source.tar" -tar cf "$BUILDBOX_DIR/transient-data/source.tar" --exclude "$BUILDBOX_DIR" --exclude ".git" --exclude "buck-out" --exclude ".buckd" --exclude "build" "." +set -x +find . -type f -a -not -regex "\\." -a -not -regex ".*\\./git" -a -not -regex ".*\\./git/.*" -a -not -regex "\\./bazel-bin" -a -not -regex "\\./bazel-bin/.*" -a -not -regex "\\./bazel-out" -a -not -regex "\\./bazel-out/.*" -a -not -regex "\\./bazel-testlogs" -a -not -regex "\\./bazel-testlogs/.*" -a -not -regex "\\./bazel-telegram-ios" -a -not -regex "\\./bazel-telegram-ios/.*" -a -not -regex "\\./buildbox" -a -not -regex "\\./buildbox/.*" -a -not -regex "\\./buck-out" -a -not -regex "\\./buck-out/.*" -a -not -regex "\\./\\.buckd" -a -not -regex "\\./\\.buckd/.*" -a -not -regex "\\./build" -a -not -regex "\\./build/.*" -print0 | tar cf "$BUILDBOX_DIR/transient-data/source.tar" --null -T - PROCESS_ID="$$" diff --git a/buildbox/guest-build-telegram.sh b/buildbox/guest-build-telegram.sh index 0f136a3518..c76c532da7 100644 --- a/buildbox/guest-build-telegram.sh +++ b/buildbox/guest-build-telegram.sh @@ -57,7 +57,9 @@ fi mkdir "$SOURCE_PATH" -if [ "$1" != "verify" ]; then +USE_RAMDISK="0" + +if [ "$USE_RAMDISK" == "1" ]; then SIZE_IN_BLOCKS=$((12*1024*1024*1024/512)) DEV=`hdid -nomount ram://$SIZE_IN_BLOCKS` diff --git a/submodules/AccountContext/BUILD b/submodules/AccountContext/BUILD index fd5976b7f7..f3f19db373 100644 --- a/submodules/AccountContext/BUILD +++ b/submodules/AccountContext/BUILD @@ -17,7 +17,6 @@ swift_library( "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/SyncCore:SyncCore", - "//submodules/WalletCore:WalletCore", ], visibility = [ "//visibility:public", diff --git a/submodules/SettingsUI/BUILD b/submodules/SettingsUI/BUILD index fa9dc7307a..51c58151f8 100644 --- a/submodules/SettingsUI/BUILD +++ b/submodules/SettingsUI/BUILD @@ -79,7 +79,6 @@ swift_library( "//submodules/InstantPageCache:InstantPageCache", "//submodules/AppBundle:AppBundle", "//submodules/ContextUI:ContextUI", - "//submodules/WalletUI:WalletUI", "//submodules/Markdown:Markdown", "//submodules/UndoUI:UndoUI", "//submodules/DeleteChatPeerActionSheetItem:DeleteChatPeerActionSheetItem", diff --git a/submodules/TelegramCallsUI/Sources/PresentationCall.swift b/submodules/TelegramCallsUI/Sources/PresentationCall.swift index 735c1c8a73..85504d38b4 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCall.swift @@ -167,9 +167,16 @@ public final class PresentationCallImpl: PresentationCall { public let isOutgoing: Bool public let peer: Peer? + private let serializedData: String? + private let dataSaving: VoiceCallDataSaving + private let derivedState: VoipDerivedState + private let proxyServer: ProxyServerSettings? + private let currentNetworkType: NetworkType + private let updatedNetworkType: Signal + private var sessionState: CallSession? private var callContextState: OngoingCallContextState? - private var ongoingContext: OngoingCallContext + private var ongoingContext: OngoingCallContext? private var ongoingContextStateDisposable: Disposable? private var reception: Int32? private var receptionDisposable: Disposable? @@ -198,6 +205,8 @@ public final class PresentationCallImpl: PresentationCall { return self.audioOutputStatePromise.get() } + private let debugInfoValue = Promise<(String, String)>(("", "")) + private let canBeRemovedPromise = Promise(false) private var didSetCanBeRemoved = false public var canBeRemoved: Signal { @@ -233,7 +242,12 @@ public final class PresentationCallImpl: PresentationCall { self.isOutgoing = isOutgoing self.peer = peer - self.ongoingContext = OngoingCallContext(account: account, callSessionManager: self.callSessionManager, internalId: self.internalId, proxyServer: proxyServer, initialNetworkType: currentNetworkType, updatedNetworkType: updatedNetworkType, serializedData: serializedData, dataSaving: dataSaving, derivedState: derivedState) + self.serializedData = serializedData + self.dataSaving = dataSaving + self.derivedState = derivedState + self.proxyServer = proxyServer + self.currentNetworkType = currentNetworkType + self.updatedNetworkType = updatedNetworkType var didReceiveAudioOutputs = false @@ -251,28 +265,6 @@ public final class PresentationCallImpl: PresentationCall { } }) - self.ongoingContextStateDisposable = (self.ongoingContext.state - |> deliverOnMainQueue).start(next: { [weak self] contextState in - if let strongSelf = self { - if let sessionState = strongSelf.sessionState { - strongSelf.updateSessionState(sessionState: sessionState, callContextState: contextState, reception: strongSelf.reception, audioSessionControl: strongSelf.audioSessionControl) - } else { - strongSelf.callContextState = contextState - } - } - }) - - self.receptionDisposable = (self.ongoingContext.reception - |> deliverOnMainQueue).start(next: { [weak self] reception in - if let strongSelf = self { - if let sessionState = strongSelf.sessionState { - strongSelf.updateSessionState(sessionState: sessionState, callContextState: strongSelf.callContextState, reception: reception, audioSessionControl: strongSelf.audioSessionControl) - } else { - strongSelf.reception = reception - } - } - }) - self.audioSessionDisposable = audioSession.push(audioSessionType: .voiceCall, manualActivate: { [weak self] control in Queue.mainQueue().async { if let strongSelf = self { @@ -443,7 +435,7 @@ public final class PresentationCallImpl: PresentationCall { presentationState = .terminated(id, reason, self.callWasActive && (options.contains(.reportRating) || self.shouldPresentCallRating)) case let .requesting(ringing): presentationState = .requesting(ringing) - case let .active(_, _, keyVisualHash, _, _, _): + case let .active(_, _, keyVisualHash, _, _, _, _): self.callWasActive = true if let callContextState = callContextState { switch callContextState { @@ -481,11 +473,38 @@ public final class PresentationCallImpl: PresentationCall { if let _ = audioSessionControl { self.audioSessionShouldBeActive.set(true) } - case let .active(id, key, _, connections, maxLayer, allowsP2P): + case let .active(id, key, _, connections, maxLayer, version, allowsP2P): self.audioSessionShouldBeActive.set(true) if let _ = audioSessionControl, !wasActive || previousControl == nil { let logName = "\(id.id)_\(id.accessHash)" - self.ongoingContext.start(key: key, isOutgoing: sessionState.isOutgoing, connections: connections, maxLayer: maxLayer, allowP2P: allowsP2P, audioSessionActive: self.audioSessionActive.get(), logName: logName) + + let ongoingContext = OngoingCallContext(account: account, callSessionManager: self.callSessionManager, internalId: self.internalId, proxyServer: proxyServer, initialNetworkType: self.currentNetworkType, updatedNetworkType: self.updatedNetworkType, serializedData: self.serializedData, dataSaving: dataSaving, derivedState: self.derivedState, key: key, isOutgoing: sessionState.isOutgoing, connections: connections, maxLayer: maxLayer, version: version, allowP2P: allowsP2P, audioSessionActive: self.audioSessionActive.get(), logName: logName) + self.ongoingContext = ongoingContext + + self.debugInfoValue.set(ongoingContext.debugInfo()) + + self.ongoingContextStateDisposable = (ongoingContext.state + |> deliverOnMainQueue).start(next: { [weak self] contextState in + if let strongSelf = self { + if let sessionState = strongSelf.sessionState { + strongSelf.updateSessionState(sessionState: sessionState, callContextState: contextState, reception: strongSelf.reception, audioSessionControl: strongSelf.audioSessionControl) + } else { + strongSelf.callContextState = contextState + } + } + }) + + self.receptionDisposable = (ongoingContext.reception + |> deliverOnMainQueue).start(next: { [weak self] reception in + if let strongSelf = self { + if let sessionState = strongSelf.sessionState { + strongSelf.updateSessionState(sessionState: sessionState, callContextState: strongSelf.callContextState, reception: reception, audioSessionControl: strongSelf.audioSessionControl) + } else { + strongSelf.reception = reception + } + } + }) + if sessionState.isOutgoing { self.callKitIntegration?.reportOutgoingCallConnected(uuid: sessionState.id, at: Date()) } @@ -494,13 +513,13 @@ public final class PresentationCallImpl: PresentationCall { self.audioSessionShouldBeActive.set(true) if wasActive { let debugLogValue = Promise() - self.ongoingContext.stop(callId: id, sendDebugLogs: options.contains(.sendDebugLogs), debugLogValue: debugLogValue) + self.ongoingContext?.stop(callId: id, sendDebugLogs: options.contains(.sendDebugLogs), debugLogValue: debugLogValue) } default: self.audioSessionShouldBeActive.set(false) if wasActive { let debugLogValue = Promise() - self.ongoingContext.stop(debugLogValue: debugLogValue) + self.ongoingContext?.stop(debugLogValue: debugLogValue) } } if case .terminated = sessionState.state, !wasTerminated { @@ -531,9 +550,9 @@ public final class PresentationCallImpl: PresentationCall { self.statePromise.set(presentationState) self.updateTone(presentationState, callContextState: callContextState, previous: previous) } - + if !self.shouldPresentCallRating { - self.ongoingContext.needsRating { needsRating in + self.ongoingContext?.needsRating { needsRating in // self.shouldPresentCallRating = needsRating } } @@ -617,7 +636,7 @@ public final class PresentationCallImpl: PresentationCall { public func hangUp() -> Signal { let debugLogValue = Promise() self.callSessionManager.drop(internalId: self.internalId, reason: .hangUp, debugLog: debugLogValue.get()) - self.ongoingContext.stop(debugLogValue: debugLogValue) + self.ongoingContext?.stop(debugLogValue: debugLogValue) return self.hungUpPromise.get() } @@ -625,7 +644,7 @@ public final class PresentationCallImpl: PresentationCall { public func rejectBusy() { self.callSessionManager.drop(internalId: self.internalId, reason: .busy, debugLog: .single(nil)) let debugLog = Promise() - self.ongoingContext.stop(debugLogValue: debugLog) + self.ongoingContext?.stop(debugLogValue: debugLog) } public func toggleIsMuted() { @@ -635,7 +654,7 @@ public final class PresentationCallImpl: PresentationCall { public func setIsMuted(_ value: Bool) { self.isMutedValue = value self.isMutedPromise.set(self.isMutedValue) - self.ongoingContext.setIsMuted(self.isMutedValue) + self.ongoingContext?.setIsMuted(self.isMutedValue) } public func setCurrentAudioOutput(_ output: AudioSessionOutput) { @@ -656,6 +675,6 @@ public final class PresentationCallImpl: PresentationCall { } public func debugInfo() -> Signal<(String, String), NoError> { - return self.ongoingContext.debugInfo() + return self.debugInfoValue.get() } } diff --git a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift index 2e5310dec2..9619863d31 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift @@ -76,7 +76,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { } public static var voipVersions: [String] { - return [OngoingCallContext.version] + return OngoingCallContext.versions } public init(accountManager: AccountManager, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), isMediaPlaying: @escaping () -> Bool, resumeMediaPlayback: @escaping () -> Void, audioSession: ManagedAudioSession, activeAccounts: Signal<[Account], NoError>) { diff --git a/submodules/TelegramCore/Sources/CallSessionManager.swift b/submodules/TelegramCore/Sources/CallSessionManager.swift index 3149687642..b7d7ca2910 100644 --- a/submodules/TelegramCore/Sources/CallSessionManager.swift +++ b/submodules/TelegramCore/Sources/CallSessionManager.swift @@ -90,13 +90,13 @@ public struct CallId: Equatable { } enum CallSessionInternalState { - case ringing(id: Int64, accessHash: Int64, gAHash: Data, b: Data) + case ringing(id: Int64, accessHash: Int64, gAHash: Data, b: Data, versions: [String]) case accepting(id: Int64, accessHash: Int64, gAHash: Data, b: Data, disposable: Disposable) case awaitingConfirmation(id: Int64, accessHash: Int64, gAHash: Data, b: Data, config: SecretChatEncryptionConfig) case requesting(a: Data, disposable: Disposable) case requested(id: Int64, accessHash: Int64, a: Data, gA: Data, config: SecretChatEncryptionConfig, remoteConfirmationTimestamp: Int32?) case confirming(id: Int64, accessHash: Int64, key: Data, keyId: Int64, keyVisualHash: Data, disposable: Disposable) - case active(id: Int64, accessHash: Int64, beginTimestamp: Int32, key: Data, keyId: Int64, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, allowsP2P: Bool) + case active(id: Int64, accessHash: Int64, beginTimestamp: Int32, key: Data, keyId: Int64, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, allowsP2P: Bool) case dropping(Disposable) case terminated(id: Int64?, accessHash: Int64?, reason: CallSessionTerminationReason, reportRating: Bool, sendDebugLogs: Bool) } @@ -139,7 +139,7 @@ public enum CallSessionState { case ringing case accepting case requesting(ringing: Bool) - case active(id: CallId, key: Data, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, allowsP2P: Bool) + case active(id: CallId, key: Data, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, allowsP2P: Bool) case dropping case terminated(id: CallId?, reason: CallSessionTerminationReason, options: CallTerminationOptions) @@ -155,8 +155,8 @@ public enum CallSessionState { self = .requesting(ringing: true) case let .requested(_, _, _, _, _, remoteConfirmationTimestamp): self = .requesting(ringing: remoteConfirmationTimestamp != nil) - case let .active(id, accessHash, _, key, _, keyVisualHash, connections, maxLayer, allowsP2P): - self = .active(id: CallId(id: id, accessHash: accessHash), key: key, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, allowsP2P: allowsP2P) + case let .active(id, accessHash, _, key, _, keyVisualHash, connections, maxLayer, version, allowsP2P): + self = .active(id: CallId(id: id, accessHash: accessHash), key: key, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, allowsP2P: allowsP2P) case .dropping: self = .dropping case let .terminated(id, accessHash, reason, reportRating, sendDebugLogs): @@ -235,6 +235,15 @@ private final class CallSessionContext { } } +private func selectVersionOnAccept(localVersions: [String], remoteVersions: [String]) -> [String]? { + let filteredVersions = localVersions.filter(remoteVersions.contains) + if filteredVersions.isEmpty { + return nil + } else { + return [filteredVersions[0]] + } +} + private final class CallSessionManagerContext { private let queue: Queue private let postbox: Postbox @@ -254,7 +263,7 @@ private final class CallSessionManagerContext { self.postbox = postbox self.network = network self.maxLayer = maxLayer - self.versions = versions + self.versions = versions.reversed() self.addUpdates = addUpdates } @@ -338,7 +347,7 @@ private final class CallSessionManagerContext { } } - private func addIncoming(peerId: PeerId, stableId: CallSessionStableId, accessHash: Int64, timestamp: Int32, gAHash: Data) -> CallSessionInternalId? { + private func addIncoming(peerId: PeerId, stableId: CallSessionStableId, accessHash: Int64, timestamp: Int32, gAHash: Data, versions: [String]) -> CallSessionInternalId? { if self.contextIdByStableId[stableId] != nil { return nil } @@ -349,7 +358,7 @@ private final class CallSessionManagerContext { if randomStatus == 0 { let internalId = CallSessionInternalId() - let context = CallSessionContext(peerId: peerId, isOutgoing: false, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b)) + let context = CallSessionContext(peerId: peerId, isOutgoing: false, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b, versions: versions)) self.contexts[internalId] = context let queue = self.queue context.acknowledgeIncomingCallDisposable.set(self.network.request(Api.functions.phone.receivedCall(peer: .inputPhoneCall(id: stableId, accessHash: accessHash))).start(error: { [weak self] _ in @@ -374,7 +383,7 @@ private final class CallSessionManagerContext { var dropData: (CallSessionStableId, Int64, DropCallSessionReason)? var wasRinging = false switch context.state { - case let .ringing(id, accessHash, _, _): + case let .ringing(id, accessHash, _, _, _): wasRinging = true let internalReason: DropCallSessionReason switch reason { @@ -389,7 +398,7 @@ private final class CallSessionManagerContext { case let .accepting(id, accessHash, _, _, disposable): dropData = (id, accessHash, .abort) disposable.dispose() - case let .active(id, accessHash, beginTimestamp, _, _, _, _, _, _): + case let .active(id, accessHash, beginTimestamp, _, _, _, _, _, _, _): let duration = max(0, Int32(CFAbsoluteTimeGetCurrent()) - beginTimestamp) let internalReason: DropCallSessionReason switch reason { @@ -477,8 +486,13 @@ private final class CallSessionManagerContext { func accept(internalId: CallSessionInternalId) { if let context = self.contexts[internalId] { switch context.state { - case let .ringing(id, accessHash, gAHash, b): - context.state = .accepting(id: id, accessHash: accessHash, gAHash: gAHash, b: b, disposable: (acceptCallSession(postbox: self.postbox, network: self.network, stableId: id, accessHash: accessHash, b: b, maxLayer: self.maxLayer, versions: self.versions) |> deliverOn(self.queue)).start(next: { [weak self] result in + case let .ringing(id, accessHash, gAHash, b, remoteVersions): + guard var acceptVersions = selectVersionOnAccept(localVersions: self.versions, remoteVersions: remoteVersions) else { + self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil)) + return + } + acceptVersions = self.versions + context.state = .accepting(id: id, accessHash: accessHash, gAHash: gAHash, b: b, disposable: (acceptCallSession(postbox: self.postbox, network: self.network, stableId: id, accessHash: accessHash, b: b, maxLayer: self.maxLayer, versions: acceptVersions) |> deliverOn(self.queue)).start(next: { [weak self] result in if let strongSelf = self, let context = strongSelf.contexts[internalId] { if case .accepting = context.state { switch result { @@ -489,9 +503,9 @@ private final class CallSessionManagerContext { case let .waiting(config): context.state = .awaitingConfirmation(id: id, accessHash: accessHash, gAHash: gAHash, b: b, config: config) strongSelf.contextUpdated(internalId: internalId) - case let .call(config, gA, timestamp, connections, maxLayer, allowsP2P): + case let .call(config, gA, timestamp, connections, maxLayer, version, allowsP2P): if let (key, keyId, keyVisualHash) = strongSelf.makeSessionEncryptionKey(config: config, gAHash: gAHash, b: b, gA: gA) { - context.state = .active(id: id, accessHash: accessHash, beginTimestamp: timestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, allowsP2P: allowsP2P) + context.state = .active(id: id, accessHash: accessHash, beginTimestamp: timestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, allowsP2P: allowsP2P) strongSelf.contextUpdated(internalId: internalId) } else { strongSelf.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil)) @@ -515,8 +529,18 @@ private final class CallSessionManagerContext { switch call { case .phoneCallEmpty: break - case let .phoneCallAccepted(flags, id, _, _, _, _, gB, _): + case let .phoneCallAccepted(flags, id, _, _, _, _, gB, remoteProtocol): + let remoteVersions: [String] + switch remoteProtocol { + case let .phoneCallProtocol(_, _, _, versions): + remoteVersions = versions + } if let internalId = self.contextIdByStableId[id] { + guard let selectedVersions = selectVersionOnAccept(localVersions: self.versions, remoteVersions: remoteVersions) else { + self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil)) + return + } + if let context = self.contexts[internalId] { switch context.state { case let .requested(_, accessHash, a, gA, config, _): @@ -543,7 +567,7 @@ private final class CallSessionManagerContext { let keyVisualHash = MTSha256(key + gA)! - context.state = .confirming(id: id, accessHash: accessHash, key: key, keyId: keyId, keyVisualHash: keyVisualHash, disposable: (confirmCallSession(network: self.network, stableId: id, accessHash: accessHash, gA: gA, keyFingerprint: keyId, maxLayer: self.maxLayer, versions: self.versions) |> deliverOnMainQueue).start(next: { [weak self] updatedCall in + context.state = .confirming(id: id, accessHash: accessHash, key: key, keyId: keyId, keyVisualHash: keyVisualHash, disposable: (confirmCallSession(network: self.network, stableId: id, accessHash: accessHash, gA: gA, keyFingerprint: keyId, maxLayer: self.maxLayer, versions: selectedVersions) |> deliverOnMainQueue).start(next: { [weak self] updatedCall in if let strongSelf = self, let context = strongSelf.contexts[internalId], case .confirming = context.state { if let updatedCall = updatedCall { strongSelf.updateSession(updatedCall, completion: { _ in }) @@ -586,7 +610,7 @@ private final class CallSessionManagerContext { disposable.dispose() context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs) self.contextUpdated(internalId: internalId) - case let .active(id, accessHash, _, _, _, _, _, _, _): + case let .active(id, accessHash, _, _, _, _, _, _, _, _): context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs) self.contextUpdated(internalId: internalId) case let .awaitingConfirmation(id, accessHash, _, _, _): @@ -603,7 +627,7 @@ private final class CallSessionManagerContext { disposable.dispose() context.state = .terminated(id: nil, accessHash: nil, reason: parsedReason, reportRating: false, sendDebugLogs: false) self.contextUpdated(internalId: internalId) - case let .ringing(id, accessHash, _, _): + case let .ringing(id, accessHash, _, _, _): context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs) self.ringingStatesUpdated() self.contextUpdated(internalId: internalId) @@ -625,9 +649,13 @@ private final class CallSessionManagerContext { if let (key, calculatedKeyId, keyVisualHash) = self.makeSessionEncryptionKey(config: config, gAHash: gAHash, b: b, gA: gAOrB.makeData()) { if keyFingerprint == calculatedKeyId { switch callProtocol { - case let .phoneCallProtocol(_, _, maxLayer, _): - context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: calculatedKeyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, allowsP2P: allowsP2P) - self.contextUpdated(internalId: internalId) + case let .phoneCallProtocol(_, _, maxLayer, versions): + if !versions.isEmpty { + context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: calculatedKeyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], allowsP2P: allowsP2P) + self.contextUpdated(internalId: internalId) + } else { + self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil)) + } } } else { self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil)) @@ -637,18 +665,27 @@ private final class CallSessionManagerContext { } case let .confirming(id, accessHash, key, keyId, keyVisualHash, _): switch callProtocol { - case let .phoneCallProtocol(_, _, maxLayer, _): - context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, allowsP2P: allowsP2P) - self.contextUpdated(internalId: internalId) + case let .phoneCallProtocol(_, _, maxLayer, versions): + if !versions.isEmpty { + context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], allowsP2P: allowsP2P) + self.contextUpdated(internalId: internalId) + } else { + self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil)) + } } } } else { assertionFailure() } } - case let .phoneCallRequested(flags, id, accessHash, date, adminId, _, gAHash, _): + case let .phoneCallRequested(flags, id, accessHash, date, adminId, _, gAHash, requestedProtocol): + let versions: [String] + switch requestedProtocol { + case let .phoneCallProtocol(_, _, _, libraryVersions): + versions = libraryVersions + } if self.contextIdByStableId[id] == nil { - let internalId = self.addIncoming(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: adminId), stableId: id, accessHash: accessHash, timestamp: date, gAHash: gAHash.makeData()) + let internalId = self.addIncoming(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: adminId), stableId: id, accessHash: accessHash, timestamp: date, gAHash: gAHash.makeData(), versions: versions) if let internalId = internalId { var resultRingingStateValue: CallSessionRingingState? for ringingState in self.ringingStatesValue() { @@ -847,7 +884,7 @@ public final class CallSessionManager { private enum AcceptedCall { case waiting(config: SecretChatEncryptionConfig) - case call(config: SecretChatEncryptionConfig, gA: Data, timestamp: Int32, connections: CallSessionConnectionSet, maxLayer: Int32, allowsP2P: Bool) + case call(config: SecretChatEncryptionConfig, gA: Data, timestamp: Int32, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, allowsP2P: Bool) } private enum AcceptCallResult { @@ -896,8 +933,12 @@ private func acceptCallSession(postbox: Postbox, network: Network, stableId: Cal case let .phoneCall(flags, id, _, _, _, _, gAOrB, _, callProtocol, connections, startDate): if id == stableId { switch callProtocol{ - case let .phoneCallProtocol(_, _, maxLayer, _): - return .success(.call(config: config, gA: gAOrB.makeData(), timestamp: startDate, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, allowsP2P: (flags & (1 << 5)) != 0)) + case let .phoneCallProtocol(_, _, maxLayer, versions): + if !versions.isEmpty { + return .success(.call(config: config, gA: gAOrB.makeData(), timestamp: startDate, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], allowsP2P: (flags & (1 << 5)) != 0)) + } else { + return .failed + } } } else { return .failed diff --git a/submodules/TelegramCore/Sources/ManagedAnimatedEmojiUpdates.swift b/submodules/TelegramCore/Sources/ManagedAnimatedEmojiUpdates.swift index cf1c15e97c..bb16cf50fa 100644 --- a/submodules/TelegramCore/Sources/ManagedAnimatedEmojiUpdates.swift +++ b/submodules/TelegramCore/Sources/ManagedAnimatedEmojiUpdates.swift @@ -5,7 +5,7 @@ import TelegramApi import MtProtoKit func managedAnimatedEmojiUpdates(postbox: Postbox, network: Network) -> Signal { - let poll = loadedStickerPack(postbox: postbox, network: network, reference: .animatedEmoji, forceActualized: false) + let poll = loadedStickerPack(postbox: postbox, network: network, reference: .animatedEmoji, forceActualized: true) |> mapToSignal { _ -> Signal in return .complete() } diff --git a/submodules/TelegramCore/Sources/RateCall.swift b/submodules/TelegramCore/Sources/RateCall.swift index dbc28f7cf7..96967d9271 100644 --- a/submodules/TelegramCore/Sources/RateCall.swift +++ b/submodules/TelegramCore/Sources/RateCall.swift @@ -16,6 +16,8 @@ public func rateCall(account: Account, callId: CallId, starsCount: Int32, commen public func saveCallDebugLog(network: Network, callId: CallId, log: String) -> Signal { return network.request(Api.functions.phone.saveCallDebug(peer: Api.InputPhoneCall.inputPhoneCall(id: callId.id, accessHash: callId.accessHash), debug: .dataJSON(data: log))) - |> retryRequest + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } |> map { _ in } } diff --git a/submodules/TelegramCore/Sources/StickerManagement.swift b/submodules/TelegramCore/Sources/StickerManagement.swift index deaa4d27ad..e952f560ae 100644 --- a/submodules/TelegramCore/Sources/StickerManagement.swift +++ b/submodules/TelegramCore/Sources/StickerManagement.swift @@ -52,7 +52,7 @@ func updatedFeaturedStickerPacks(network: Network, postbox: Postbox) -> Signal switchToLatest } -func parsePreviewStickerSet(_ set: Api.StickerSetCovered, namespace: ItemCollectionId.Namespace) -> (StickerPackCollectionInfo, [StickerPackItem]) { +func parsePreviewStickerSet(_ set: Api.StickerSetCovered, namespace: ItemCollectionId.Namespace = Namespaces.ItemCollection.CloudStickerPacks) -> (StickerPackCollectionInfo, [StickerPackItem]) { switch set { case let .stickerSetCovered(set, cover): let info = StickerPackCollectionInfo(apiSet: set, namespace: namespace) diff --git a/submodules/TelegramUI/BUCK b/submodules/TelegramUI/BUCK index 0037548b08..68c2344e36 100644 --- a/submodules/TelegramUI/BUCK +++ b/submodules/TelegramUI/BUCK @@ -35,6 +35,7 @@ framework( "//submodules/AccountContext:AccountContext", "//submodules/LegacyComponents:LegacyComponents", "//submodules/TgVoip:TgVoip", + "//submodules/TgVoipWebrtc:TgVoipWebrtc", "//submodules/lottie-ios:Lottie", "//submodules/FFMpegBinding:FFMpegBinding", "//submodules/WebPBinding:WebPBinding", diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 585f4d3d3e..5bfe36872d 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -33,7 +33,6 @@ swift_library( "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/AccountContext:AccountContext", "//submodules/LegacyComponents:LegacyComponents", - "//submodules/TgVoip:TgVoip", "//submodules/lottie-ios:Lottie", "//submodules/FFMpegBinding:FFMpegBinding", "//submodules/WebPBinding:WebPBinding", @@ -185,8 +184,6 @@ swift_library( "//submodules/MessageReactionListUI:MessageReactionListUI", "//submodules/SegmentedControlNode:SegmentedControlNode", "//submodules/AppBundle:AppBundle", - "//submodules/WalletUI:WalletUI", - "//submodules/WalletCore:WalletCore", "//submodules/Markdown:Markdown", "//submodules/SearchPeerMembers:SearchPeerMembers", "//submodules/WidgetItems:WidgetItems", diff --git a/submodules/TelegramVoip/BUCK b/submodules/TelegramVoip/BUCK index 6d74bfe5bb..9868b8dfa1 100644 --- a/submodules/TelegramVoip/BUCK +++ b/submodules/TelegramVoip/BUCK @@ -12,6 +12,7 @@ static_library( "//submodules/Postbox:Postbox#shared", "//submodules/TelegramUIPreferences:TelegramUIPreferences", "//submodules/TgVoip:TgVoip", + "//submodules/TgVoipWebrtc:TgVoipWebrtc", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/TelegramVoip/BUILD b/submodules/TelegramVoip/BUILD index 0fa6b09a6d..028b9e5ea2 100644 --- a/submodules/TelegramVoip/BUILD +++ b/submodules/TelegramVoip/BUILD @@ -13,6 +13,7 @@ swift_library( "//submodules/Postbox:Postbox", "//submodules/TelegramUIPreferences:TelegramUIPreferences", "//submodules/TgVoip:TgVoip", + "//submodules/TgVoipWebrtc:TgVoipWebrtc", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramVoip/Sources/OngoingCallContext.swift b/submodules/TelegramVoip/Sources/OngoingCallContext.swift index 876978cd37..243429522f 100644 --- a/submodules/TelegramVoip/Sources/OngoingCallContext.swift +++ b/submodules/TelegramVoip/Sources/OngoingCallContext.swift @@ -4,12 +4,18 @@ import TelegramCore import SyncCore import Postbox import TelegramUIPreferences + import TgVoip +import TgVoipWebrtc private func callConnectionDescription(_ connection: CallSessionConnection) -> OngoingCallConnectionDescription { return OngoingCallConnectionDescription(connectionId: connection.id, ip: connection.ip, ipv6: connection.ipv6, port: connection.port, peerTag: connection.peerTag) } +private func callConnectionDescriptionWebrtc(_ connection: CallSessionConnection) -> OngoingCallConnectionDescriptionWebrtc { + return OngoingCallConnectionDescriptionWebrtc(connectionId: connection.id, ip: connection.ip, ipv6: connection.ipv6, port: connection.port, peerTag: connection.peerTag) +} + private let callLogsLimit = 20 public func callLogNameForId(id: Int64, account: Account) -> String? { @@ -68,6 +74,11 @@ private let setupLogs: Bool = { Logger.shared.log("TGVOIP", value) } }) + OngoingCallThreadLocalContextWebrtc.setupLoggingFunction({ value in + if let value = value { + Logger.shared.log("TGVOIP", value) + } + }) return true }() @@ -98,6 +109,26 @@ private final class OngoingCallThreadLocalContextQueueImpl: NSObject, OngoingCal } } +private final class OngoingCallThreadLocalContextQueueWebrtcImpl: NSObject, OngoingCallThreadLocalContextQueueWebrtc { + private let queue: Queue + + init(queue: Queue) { + self.queue = queue + + super.init() + } + + func dispatch(_ f: @escaping () -> Void) { + self.queue.async { + f() + } + } + + func isCurrent() -> Bool { + return self.queue.isCurrent() + } +} + private func ongoingNetworkTypeForType(_ type: NetworkType) -> OngoingCallNetworkType { switch type { case .none: @@ -118,6 +149,26 @@ private func ongoingNetworkTypeForType(_ type: NetworkType) -> OngoingCallNetwor } } +private func ongoingNetworkTypeForTypeWebrtc(_ type: NetworkType) -> OngoingCallNetworkTypeWebrtc { + switch type { + case .none: + return .wifi + case .wifi: + return .wifi + case let .cellular(cellular): + switch cellular { + case .edge: + return .cellularEdge + case .gprs: + return .cellularGprs + case .thirdG, .unknown: + return .cellular3g + case .lte: + return .cellularLte + } + } +} + private func ongoingDataSavingForType(_ type: VoiceCallDataSaving) -> OngoingCallDataSaving { switch type { case .never: @@ -131,6 +182,122 @@ private func ongoingDataSavingForType(_ type: VoiceCallDataSaving) -> OngoingCal } } +private func ongoingDataSavingForTypeWebrtc(_ type: VoiceCallDataSaving) -> OngoingCallDataSavingWebrtc { + switch type { + case .never: + return .never + case .cellular: + return .cellular + case .always: + return .always + default: + return .never + } +} + +private protocol OngoingCallThreadLocalContextProtocol: class { + func nativeSetNetworkType(_ type: NetworkType) + func nativeSetIsMuted(_ value: Bool) + func nativeStop(_ completion: @escaping (String?, Int64, Int64, Int64, Int64) -> Void) + func nativeDebugInfo() -> String + func nativeVersion() -> String + func nativeGetDerivedState() -> Data +} + +private final class OngoingCallThreadLocalContextHolder { + let context: OngoingCallThreadLocalContextProtocol + + init(_ context: OngoingCallThreadLocalContextProtocol) { + self.context = context + } +} + +extension OngoingCallThreadLocalContext: OngoingCallThreadLocalContextProtocol { + func nativeSetNetworkType(_ type: NetworkType) { + self.setNetworkType(ongoingNetworkTypeForType(type)) + } + + func nativeStop(_ completion: @escaping (String?, Int64, Int64, Int64, Int64) -> Void) { + self.stop(completion) + } + + func nativeSetIsMuted(_ value: Bool) { + self.setIsMuted(value) + } + + func nativeDebugInfo() -> String { + return self.debugInfo() ?? "" + } + + func nativeVersion() -> String { + return self.version() ?? "" + } + + func nativeGetDerivedState() -> Data { + return self.getDerivedState() + } +} + +extension OngoingCallThreadLocalContextWebrtc: OngoingCallThreadLocalContextProtocol { + func nativeSetNetworkType(_ type: NetworkType) { + self.setNetworkType(ongoingNetworkTypeForTypeWebrtc(type)) + } + + func nativeStop(_ completion: @escaping (String?, Int64, Int64, Int64, Int64) -> Void) { + self.stop(completion) + } + + func nativeSetIsMuted(_ value: Bool) { + self.setIsMuted(value) + } + + func nativeDebugInfo() -> String { + return self.debugInfo() ?? "" + } + + func nativeVersion() -> String { + return self.version() ?? "" + } + + func nativeGetDerivedState() -> Data { + return self.getDerivedState() + } +} + +private extension OngoingCallContextState { + init(_ state: OngoingCallState) { + switch state { + case .initializing: + self = .initializing + case .connected: + self = .connected + case .failed: + self = .failed + case .reconnecting: + self = .reconnecting + default: + self = .failed + } + } +} + +private extension OngoingCallContextState { + init(_ state: OngoingCallStateWebrtc) { + switch state { + case .initializing: + self = .initializing + case .connected: + self = .connected + case .failed: + self = .failed + case .reconnecting: + self = .reconnecting + default: + self = .failed + } + } +} + public final class OngoingCallContext { public let internalId: CallSessionInternalId @@ -138,27 +305,11 @@ public final class OngoingCallContext { private let account: Account private let callSessionManager: CallSessionManager - private var contextRef: Unmanaged? + private var contextRef: Unmanaged? - private let contextState = Promise(nil) + private let contextState = Promise(nil) public var state: Signal { return self.contextState.get() - |> map { - $0.flatMap { - switch $0 { - case .initializing: - return .initializing - case .connected: - return .connected - case .failed: - return .failed - case .reconnecting: - return .reconnecting - default: - return .failed - } - } - } } private let receptionPromise = Promise(nil) @@ -170,50 +321,87 @@ public final class OngoingCallContext { private var networkTypeDisposable: Disposable? public static var maxLayer: Int32 { - return OngoingCallThreadLocalContext.maxLayer() + return max(OngoingCallThreadLocalContext.maxLayer(), OngoingCallThreadLocalContextWebrtc.maxLayer()) } - - public static var version: String { - return OngoingCallThreadLocalContext.version()! + + public static var versions: [String] { + return [OngoingCallThreadLocalContext.version(), OngoingCallThreadLocalContextWebrtc.version()] } - - public init(account: Account, callSessionManager: CallSessionManager, internalId: CallSessionInternalId, proxyServer: ProxyServerSettings?, initialNetworkType: NetworkType, updatedNetworkType: Signal, serializedData: String?, dataSaving: VoiceCallDataSaving, derivedState: VoipDerivedState) { + + public init(account: Account, callSessionManager: CallSessionManager, internalId: CallSessionInternalId, proxyServer: ProxyServerSettings?, initialNetworkType: NetworkType, updatedNetworkType: Signal, serializedData: String?, dataSaving: VoiceCallDataSaving, derivedState: VoipDerivedState, key: Data, isOutgoing: Bool, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, allowP2P: Bool, audioSessionActive: Signal, logName: String) { let _ = setupLogs OngoingCallThreadLocalContext.applyServerConfig(serializedData) + OngoingCallThreadLocalContextWebrtc.applyServerConfig(serializedData) self.internalId = internalId self.account = account self.callSessionManager = callSessionManager let queue = self.queue - self.queue.async { - var voipProxyServer: VoipProxyServer? - if let proxyServer = proxyServer { - switch proxyServer.connection { - case let .socks5(username, password): - voipProxyServer = VoipProxyServer(host: proxyServer.host, port: proxyServer.port, username: username, password: password) - case .mtp: - break - } - } - let context = OngoingCallThreadLocalContext(queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, networkType: ongoingNetworkTypeForType(initialNetworkType), dataSaving: ongoingDataSavingForType(dataSaving), derivedState: derivedState.data) - self.contextRef = Unmanaged.passRetained(context) - context.stateChanged = { [weak self] state in - self?.contextState.set(.single(state)) - } - context.signalBarsChanged = { [weak self] signalBars in - self?.receptionPromise.set(.single(signalBars)) - } - } - - self.networkTypeDisposable = (updatedNetworkType - |> deliverOn(self.queue)).start(next: { [weak self] networkType in - self?.withContext { context in - context.setNetworkType(ongoingNetworkTypeForType(networkType)) - } - }) cleanupCallLogs(account: account) + + let logPath = logName.isEmpty ? "" : callLogsPath(account: self.account) + "/" + logName + ".log" + self.audioSessionDisposable.set((audioSessionActive + |> filter { $0 } + |> take(1) + |> deliverOn(queue)).start(next: { [weak self] _ in + if let strongSelf = self { + if version == OngoingCallThreadLocalContextWebrtc.version() { + var voipProxyServer: VoipProxyServerWebrtc? + if let proxyServer = proxyServer { + switch proxyServer.connection { + case let .socks5(username, password): + voipProxyServer = VoipProxyServerWebrtc(host: proxyServer.host, port: proxyServer.port, username: username, password: password) + case .mtp: + break + } + } + let context = OngoingCallThreadLocalContextWebrtc(queue: OngoingCallThreadLocalContextQueueWebrtcImpl(queue: queue), proxy: voipProxyServer, 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) + + strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context)) + context.stateChanged = { state in + self?.contextState.set(.single(OngoingCallContextState(state))) + } + context.signalBarsChanged = { signalBars in + self?.receptionPromise.set(.single(signalBars)) + } + + strongSelf.networkTypeDisposable = (updatedNetworkType + |> deliverOn(queue)).start(next: { networkType in + self?.withContext { context in + context.nativeSetNetworkType(networkType) + } + }) + } else { + var voipProxyServer: VoipProxyServer? + if let proxyServer = proxyServer { + switch proxyServer.connection { + case let .socks5(username, password): + voipProxyServer = VoipProxyServer(host: proxyServer.host, port: proxyServer.port, username: username, password: password) + case .mtp: + break + } + } + let context = OngoingCallThreadLocalContext(queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, networkType: ongoingNetworkTypeForType(initialNetworkType), dataSaving: ongoingDataSavingForType(dataSaving), derivedState: derivedState.data, key: key, isOutgoing: isOutgoing, primaryConnection: callConnectionDescription(connections.primary), alternativeConnections: connections.alternatives.map(callConnectionDescription), maxLayer: maxLayer, allowP2P: allowP2P, logPath: logPath) + + strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context)) + context.stateChanged = { state in + self?.contextState.set(.single(OngoingCallContextState(state))) + } + context.signalBarsChanged = { signalBars in + self?.receptionPromise.set(.single(signalBars)) + } + + strongSelf.networkTypeDisposable = (updatedNetworkType + |> deliverOn(queue)).start(next: { networkType in + self?.withContext { context in + context.nativeSetNetworkType(networkType) + } + }) + } + } + })) } deinit { @@ -226,31 +414,18 @@ public final class OngoingCallContext { self.networkTypeDisposable?.dispose() } - private func withContext(_ f: @escaping (OngoingCallThreadLocalContext) -> Void) { + private func withContext(_ f: @escaping (OngoingCallThreadLocalContextProtocol) -> Void) { self.queue.async { if let contextRef = self.contextRef { let context = contextRef.takeUnretainedValue() - f(context) + f(context.context) } } } - public func start(key: Data, isOutgoing: Bool, connections: CallSessionConnectionSet, maxLayer: Int32, allowP2P: Bool, audioSessionActive: Signal, logName: String) { - let logPath = logName.isEmpty ? "" : callLogsPath(account: self.account) + "/" + logName + ".log" - self.audioSessionDisposable.set((audioSessionActive - |> filter { $0 } - |> take(1)).start(next: { [weak self] _ in - if let strongSelf = self { - strongSelf.withContext { context in - context.start(withKey: key, isOutgoing: isOutgoing, primaryConnection: callConnectionDescription(connections.primary), alternativeConnections: connections.alternatives.map(callConnectionDescription), maxLayer: maxLayer, allowP2P: allowP2P, logPath: logPath) - } - } - })) - } - public func stop(callId: CallId? = nil, sendDebugLogs: Bool = false, debugLogValue: Promise) { self.withContext { context in - context.stop { debugLog, bytesSentWifi, bytesReceivedWifi, bytesSentMobile, bytesReceivedMobile in + context.nativeStop { debugLog, bytesSentWifi, bytesReceivedWifi, bytesSentMobile, bytesReceivedMobile in debugLogValue.set(.single(debugLog)) let delta = NetworkUsageStatsConnectionsEntry( cellular: NetworkUsageStatsDirectionsEntry( @@ -265,7 +440,7 @@ public final class OngoingCallContext { let _ = saveCallDebugLog(network: self.account.network, callId: callId, log: debugLog).start() } } - let derivedState = context.getDerivedState() + let derivedState = context.nativeGetDerivedState() let _ = updateVoipDerivedStateInteractively(postbox: self.account.postbox, { _ in return VoipDerivedState(data: derivedState) }).start() @@ -274,18 +449,16 @@ public final class OngoingCallContext { public func setIsMuted(_ value: Bool) { self.withContext { context in - context.setIsMuted(value) + context.nativeSetIsMuted(value) } } public func debugInfo() -> Signal<(String, String), NoError> { let poll = Signal<(String, String), NoError> { subscriber in self.withContext { context in - let version = context.version() - let debugInfo = context.debugInfo() - if let version = version, let debugInfo = debugInfo { - subscriber.putNext((version, debugInfo)) - } + let version = context.nativeVersion() + let debugInfo = context.nativeDebugInfo() + subscriber.putNext((version, debugInfo)) subscriber.putCompletion() } @@ -293,14 +466,5 @@ public final class OngoingCallContext { } return (poll |> then(.complete() |> delay(0.5, queue: Queue.concurrentDefaultQueue()))) |> restart } - - public func needsRating(_ completion: @escaping (Bool) -> Void) { - self.withContext { context in - let needsRating = context.needRate() - Queue.mainQueue().async { - completion(needsRating) - } - } - } } diff --git a/submodules/TgVoip/BUCK b/submodules/TgVoip/BUCK index e8d8d2596e..4b42a4b211 100644 --- a/submodules/TgVoip/BUCK +++ b/submodules/TgVoip/BUCK @@ -1,5 +1,36 @@ load("//Config:buck_rule_macros.bzl", "static_library", "glob_map", "glob_sub_map", "merge_maps") +replace_symbols = [ + "WebRtcAgc_Process", + "rtc_FatalMessage", + "WebRtcAgc_UpdateAgcThresholds", + "WebRtcAgc_Init", + "WebRtcAgc_GetAddFarendError", + "WebRtcAgc_ZeroCtrl", + "WebRtcAgc_SaturationCtrl", + "WebRtcAgc_SpeakerInactiveCtrl", + "WebRtcAgc_ProcessAnalog", + "WebRtcAgc_set_config", + "WebRtcAgc_get_config", + "WebRtcAgc_ExpCurve", + "WebRtcAgc_Create", + "WebRtcAgc_Free", + "WebRtcAgc_AddFarend", + "WebRtcAgc_VirtualMic", + "WebRtcAgc_AddMic", + "WebRtcAgc_InitDigital", + "WebRtcAgc_AddFarendToDigital", + "WebRtcAgc_CalculateGainTable", + "WebRtcAgc_InitVad", + "WebRtcAgc_ProcessVad", + "rtc", + "webrtc", +] + +shared_compiler_flags = [ + "-DTGVOIP_INCLUDE_OPUS_PACKAGE", +] + ["-D{symbol}={symbol}1".format(symbol=symbol) for symbol in replace_symbols] + static_library( name = "TgVoip", srcs = glob([ @@ -49,7 +80,7 @@ static_library( '-DWEBRTC_NS_FLOAT', '-DWEBRTC_IOS', '-DWEBRTC_HAS_NEON', - ]), + ] + shared_compiler_flags), ('.*', [ '-DTGVOIP_USE_CUSTOM_CRYPTO', '-DTGVOIP_USE_INSTALLED_OPUS', @@ -58,7 +89,7 @@ static_library( '-DTGVOIP_HAVE_TGLOG', '-DWEBRTC_NS_FLOAT', '-DWEBRTC_IOS', - ]), + ] + shared_compiler_flags), ], deps = [ "//submodules/MtProtoKit:MtProtoKit#shared", diff --git a/submodules/TgVoip/BUILD b/submodules/TgVoip/BUILD index 2bd634904e..d316e32ecc 100644 --- a/submodules/TgVoip/BUILD +++ b/submodules/TgVoip/BUILD @@ -18,6 +18,31 @@ copts_x86 = [ "-DWEBRTC_IOS", ] +replace_symbols = [ + "WebRtcAgc_Process", + "rtc_FatalMessage", + "WebRtcAgc_UpdateAgcThresholds", + "WebRtcAgc_Init", + "WebRtcAgc_GetAddFarendError", + "WebRtcAgc_ZeroCtrl", + "WebRtcAgc_SaturationCtrl", + "WebRtcAgc_SpeakerInactiveCtrl", + "WebRtcAgc_ProcessAnalog", + "WebRtcAgc_set_config", + "WebRtcAgc_get_config", + "WebRtcAgc_ExpCurve", + "WebRtcAgc_Create", + "WebRtcAgc_Free", + "WebRtcAgc_AddFarend", + "WebRtcAgc_VirtualMic", + "WebRtcAgc_AddMic", + "WebRtcAgc_InitDigital", + "WebRtcAgc_AddFarendToDigital", + "WebRtcAgc_CalculateGainTable", + "WebRtcAgc_InitVad", + "WebRtcAgc_ProcessVad", +] + objc_library( name = "TgVoip", enable_modules = True, @@ -46,8 +71,11 @@ objc_library( "-I{}/PublicHeaders/TgVoip".format(package_name()), "-I{}/libtgvoip".format(package_name()), "-I{}/libtgvoip/webrtc_dsp".format(package_name()), + "-Isubmodules/Opus/Public/opus", "-DTGVOIP_USE_INSTALLED_OPUS", - ] + select({ + "-Drtc=rtc1", + "-Dwebrtc=webrtc1", + ] + ["-D{symbol}={symbol}1".format(symbol=symbol) for symbol in replace_symbols] + select({ "@build_bazel_rules_apple//apple:ios_armv7": copts_arm, "@build_bazel_rules_apple//apple:ios_arm64": copts_arm, "@build_bazel_rules_apple//apple:ios_x86_64": copts_x86, diff --git a/submodules/TgVoip/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h b/submodules/TgVoip/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h index 674a80d80e..d7f6e16846 100644 --- a/submodules/TgVoip/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h +++ b/submodules/TgVoip/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h @@ -59,14 +59,13 @@ typedef NS_ENUM(int32_t, OngoingCallDataSaving) { + (void)setupLoggingFunction:(void (* _Nullable)(NSString * _Nullable))loggingFunction; + (void)applyServerConfig:(NSString * _Nullable)data; + (int32_t)maxLayer; -+ (NSString *)version; ++ (NSString * _Nonnull)version; @property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallState); @property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t); -- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType dataSaving:(OngoingCallDataSaving)dataSaving derivedState:(NSData * _Nonnull)derivedState; -- (void)startWithKey:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescription * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath; -- (void)stop:(void (^)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile))completion; +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType dataSaving:(OngoingCallDataSaving)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescription * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath; +- (void)stop:(void (^_Nonnull)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile))completion; - (bool)needRate; diff --git a/submodules/TgVoip/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.mm b/submodules/TgVoip/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.mm deleted file mode 100644 index 125a7b849a..0000000000 --- a/submodules/TgVoip/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.mm +++ /dev/null @@ -1,422 +0,0 @@ -#import "OngoingCallThreadLocalContext.h" - -#import "VoIPController.h" -#import "VoIPServerConfig.h" -#import "os/darwin/SetupLogging.h" - -#ifdef BUCK -#import -#else -#import -#endif - -static void TGCallAesIgeEncrypt(uint8_t *inBytes, uint8_t *outBytes, size_t length, uint8_t *key, uint8_t *iv) { - MTAesEncryptRaw(inBytes, outBytes, length, key, iv); -} - -static void TGCallAesIgeDecrypt(uint8_t *inBytes, uint8_t *outBytes, size_t length, uint8_t *key, uint8_t *iv) { - MTAesDecryptRaw(inBytes, outBytes, length, key, iv); -} - -static void TGCallSha1(uint8_t *msg, size_t length, uint8_t *output) { - MTRawSha1(msg, length, output); -} - -static void TGCallSha256(uint8_t *msg, size_t length, uint8_t *output) { - MTRawSha256(msg, length, output); -} - -static void TGCallAesCtrEncrypt(uint8_t *inOut, size_t length, uint8_t *key, uint8_t *iv, uint8_t *ecount, uint32_t *num) { - uint8_t *outData = (uint8_t *)malloc(length); - MTAesCtr *aesCtr = [[MTAesCtr alloc] initWithKey:key keyLength:32 iv:iv ecount:ecount num:*num]; - [aesCtr encryptIn:inOut out:outData len:length]; - memcpy(inOut, outData, length); - free(outData); - - [aesCtr getIv:iv]; - - memcpy(ecount, [aesCtr ecount], 16); - *num = [aesCtr num]; -} - -static void TGCallRandomBytes(uint8_t *buffer, size_t length) { - arc4random_buf(buffer, length); -} - -@implementation OngoingCallConnectionDescription - -- (instancetype _Nonnull)initWithConnectionId:(int64_t)connectionId ip:(NSString * _Nonnull)ip ipv6:(NSString * _Nonnull)ipv6 port:(int32_t)port peerTag:(NSData * _Nonnull)peerTag { - self = [super init]; - if (self != nil) { - _connectionId = connectionId; - _ip = ip; - _ipv6 = ipv6; - _port = port; - _peerTag = peerTag; - } - return self; -} - -@end - -static MTAtomic *callContexts() { - static MTAtomic *instance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - instance = [[MTAtomic alloc] initWithValue:[[NSMutableDictionary alloc] init]]; - }); - return instance; -} - -@interface OngoingCallThreadLocalContextReference : NSObject - -@property (nonatomic, weak) OngoingCallThreadLocalContext *context; -@property (nonatomic, strong, readonly) id queue; - -@end - -@implementation OngoingCallThreadLocalContextReference - -- (instancetype)initWithContext:(OngoingCallThreadLocalContext *)context queue:(id)queue { - self = [super init]; - if (self != nil) { - self.context = context; - _queue = queue; - } - return self; -} - -@end - -static int32_t nextId = 1; - -static int32_t addContext(OngoingCallThreadLocalContext *context, id queue) { - int32_t contextId = OSAtomicIncrement32(&nextId); - [callContexts() with:^id(NSMutableDictionary *dict) { - dict[@(contextId)] = [[OngoingCallThreadLocalContextReference alloc] initWithContext:context queue:queue]; - return nil; - }]; - return contextId; -} - -static void removeContext(int32_t contextId) { - [callContexts() with:^id(NSMutableDictionary *dict) { - [dict removeObjectForKey:@(contextId)]; - return nil; - }]; -} - -static void withContext(int32_t contextId, void (^f)(OngoingCallThreadLocalContext *)) { - __block OngoingCallThreadLocalContextReference *reference = nil; - [callContexts() with:^id(NSMutableDictionary *dict) { - reference = dict[@(contextId)]; - return nil; - }]; - if (reference != nil) { - [reference.queue dispatch:^{ - __strong OngoingCallThreadLocalContext *context = reference.context; - if (context != nil) { - f(context); - } - }]; - } -} - -@interface OngoingCallThreadLocalContext () { - id _queue; - int32_t _contextId; - - OngoingCallNetworkType _networkType; - NSTimeInterval _callReceiveTimeout; - NSTimeInterval _callRingTimeout; - NSTimeInterval _callConnectTimeout; - NSTimeInterval _callPacketTimeout; - int32_t _dataSavingMode; - - tgvoip::VoIPController *_controller; - - OngoingCallState _state; - int32_t _signalBars; - NSData *_lastDerivedState; -} - -- (void)controllerStateChanged:(int)state; -- (void)signalBarsChanged:(int32_t)signalBars; - -@end - -static void controllerStateCallback(tgvoip::VoIPController *controller, int state) { - int32_t contextId = (int32_t)((intptr_t)controller->implData); - withContext(contextId, ^(OngoingCallThreadLocalContext *context) { - [context controllerStateChanged:state]; - }); -} - -static void signalBarsCallback(tgvoip::VoIPController *controller, int signalBars) { - int32_t contextId = (int32_t)((intptr_t)controller->implData); - withContext(contextId, ^(OngoingCallThreadLocalContext *context) { - [context signalBarsChanged:(int32_t)signalBars]; - }); -} - -@implementation VoipProxyServer - -- (instancetype _Nonnull)initWithHost:(NSString * _Nonnull)host port:(int32_t)port username:(NSString * _Nullable)username password:(NSString * _Nullable)password { - self = [super init]; - if (self != nil) { - _host = host; - _port = port; - _username = username; - _password = password; - } - return self; -} - -@end - -static int callControllerNetworkTypeForType(OngoingCallNetworkType type) { - switch (type) { - case OngoingCallNetworkTypeWifi: - return tgvoip::NET_TYPE_WIFI; - case OngoingCallNetworkTypeCellularGprs: - return tgvoip::NET_TYPE_GPRS; - case OngoingCallNetworkTypeCellular3g: - return tgvoip::NET_TYPE_3G; - case OngoingCallNetworkTypeCellularLte: - return tgvoip::NET_TYPE_LTE; - default: - return tgvoip::NET_TYPE_WIFI; - } -} - -static int callControllerDataSavingForType(OngoingCallDataSaving type) { - switch (type) { - case OngoingCallDataSavingNever: - return tgvoip::DATA_SAVING_NEVER; - case OngoingCallDataSavingCellular: - return tgvoip::DATA_SAVING_MOBILE; - case OngoingCallDataSavingAlways: - return tgvoip::DATA_SAVING_ALWAYS; - default: - return tgvoip::DATA_SAVING_NEVER; - } -} - -@implementation OngoingCallThreadLocalContext - -+ (void)setupLoggingFunction:(void (*)(NSString *))loggingFunction { - TGVoipLoggingFunction = loggingFunction; -} - -+ (void)applyServerConfig:(NSString *)string { - if (string.length != 0) { - tgvoip::ServerConfig::GetSharedInstance()->Update(std::string(string.UTF8String)); - } -} - -+ (int32_t)maxLayer { - return tgvoip::VoIPController::GetConnectionMaxLayer(); -} - -+ (NSString *)version { - return [NSString stringWithUTF8String:tgvoip::VoIPController::GetVersion()]; -} - -- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType dataSaving:(OngoingCallDataSaving)dataSaving derivedState:(NSData * _Nonnull)derivedState { - self = [super init]; - if (self != nil) { - _queue = queue; - assert([queue isCurrent]); - _contextId = addContext(self, queue); - - _callReceiveTimeout = 20.0; - _callRingTimeout = 90.0; - _callConnectTimeout = 30.0; - _callPacketTimeout = 10.0; - _dataSavingMode = callControllerDataSavingForType(dataSaving); - _networkType = networkType; - - _controller = new tgvoip::VoIPController(); - _controller->implData = (void *)((intptr_t)_contextId); - std::vector derivedStateValue; - derivedStateValue.resize(derivedState.length); - [derivedState getBytes:derivedStateValue.data() length:derivedState.length]; - _controller->SetPersistentState(derivedStateValue); - - if (proxy != nil) { - _controller->SetProxy(tgvoip::PROXY_SOCKS5, proxy.host.UTF8String, (uint16_t)proxy.port, proxy.username.UTF8String ?: "", proxy.password.UTF8String ?: ""); - } - - auto callbacks = tgvoip::VoIPController::Callbacks(); - callbacks.connectionStateChanged = &controllerStateCallback; - callbacks.groupCallKeyReceived = NULL; - callbacks.groupCallKeySent = NULL; - callbacks.signalBarCountChanged = &signalBarsCallback; - callbacks.upgradeToGroupCallRequested = NULL; - _controller->SetCallbacks(callbacks); - - tgvoip::VoIPController::crypto.sha1 = &TGCallSha1; - tgvoip::VoIPController::crypto.sha256 = &TGCallSha256; - tgvoip::VoIPController::crypto.rand_bytes = &TGCallRandomBytes; - tgvoip::VoIPController::crypto.aes_ige_encrypt = &TGCallAesIgeEncrypt; - tgvoip::VoIPController::crypto.aes_ige_decrypt = &TGCallAesIgeDecrypt; - tgvoip::VoIPController::crypto.aes_ctr_encrypt = &TGCallAesCtrEncrypt; - - _state = OngoingCallStateInitializing; - _signalBars = -1; - } - return self; -} - -- (void)dealloc { - assert([_queue isCurrent]); - removeContext(_contextId); - if (_controller != NULL) { - [self stop:nil]; - } -} - -- (void)startWithKey:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescription * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath { - std::vector endpoints; - NSArray *connections = [@[primaryConnection] arrayByAddingObjectsFromArray:alternativeConnections]; - for (OngoingCallConnectionDescription *connection in connections) { - struct in_addr addrIpV4; - if (!inet_aton(connection.ip.UTF8String, &addrIpV4)) { - NSLog(@"CallSession: invalid ipv4 address"); - } - - struct in6_addr addrIpV6; - if (!inet_pton(AF_INET6, connection.ipv6.UTF8String, &addrIpV6)) { - NSLog(@"CallSession: invalid ipv6 address"); - } - - tgvoip::IPv4Address address(std::string(connection.ip.UTF8String)); - tgvoip::IPv6Address addressv6(std::string(connection.ipv6.UTF8String)); - unsigned char peerTag[16]; - [connection.peerTag getBytes:peerTag length:16]; - endpoints.push_back(tgvoip::Endpoint(connection.connectionId, (uint16_t)connection.port, address, addressv6, tgvoip::Endpoint::Type::UDP_RELAY, peerTag)); - } - - tgvoip::VoIPController::Config config(_callConnectTimeout, _callPacketTimeout, _dataSavingMode, false, true, true); - config.logFilePath = logPath.length > 0 ? std::string(logPath.UTF8String) : ""; - config.statsDumpFilePath = ""; - - if (_controller != nil) { - _controller->SetConfig(config); - - _controller->SetNetworkType(callControllerNetworkTypeForType(_networkType)); - _controller->SetEncryptionKey((char *)key.bytes, isOutgoing); - _controller->SetRemoteEndpoints(endpoints, allowP2P, maxLayer); - _controller->Start(); - - _controller->Connect(); - } -} - -- (void)stop:(void (^)(NSString *, int64_t, int64_t, int64_t, int64_t))completion { - if (_controller != nil) { - _controller->Stop(); - - auto debugString = _controller->GetDebugLog(); - NSString *debugLog = [NSString stringWithUTF8String:debugString.c_str()]; - - tgvoip::VoIPController::TrafficStats stats; - _controller->GetStats(&stats); - std::vector derivedStateValue = _controller->GetPersistentState(); - _lastDerivedState = [[NSData alloc] initWithBytes:derivedStateValue.data() length:derivedStateValue.size()]; - delete _controller; - _controller = NULL; - - if (completion) { - completion(debugLog, stats.bytesSentWifi, stats.bytesRecvdWifi, stats.bytesSentMobile, stats.bytesRecvdMobile); - } - } -} - -- (bool)needRate { - if (_controller != nil) { - return _controller->NeedRate(); - } else { - return false; - } -} - -- (NSString *)debugInfo { - if (_controller != nil) { - auto rawDebugString = _controller->GetDebugString(); - return [NSString stringWithUTF8String:rawDebugString.c_str()]; - } else { - return nil; - } -} - -- (NSString *)version { - if (_controller != nil) { - return [NSString stringWithUTF8String:_controller->GetVersion()]; - } else { - return nil; - } -} - -- (NSData * _Nonnull)getDerivedState { - if (_controller != nil) { - std::vector derivedStateValue = _controller->GetPersistentState(); - return [[NSData alloc] initWithBytes:derivedStateValue.data() length:derivedStateValue.size()]; - } else if (_lastDerivedState != nil) { - return _lastDerivedState; - } else { - return [NSData data]; - } -} - -- (void)controllerStateChanged:(int)state { - OngoingCallState callState = OngoingCallStateInitializing; - switch (state) { - case tgvoip::STATE_ESTABLISHED: - callState = OngoingCallStateConnected; - break; - case tgvoip::STATE_FAILED: - callState = OngoingCallStateFailed; - break; - case tgvoip::STATE_RECONNECTING: - callState = OngoingCallStateReconnecting; - break; - default: - break; - } - - if (callState != _state) { - _state = callState; - - if (_stateChanged) { - _stateChanged(callState); - } - } -} - -- (void)signalBarsChanged:(int32_t)signalBars { - if (signalBars != _signalBars) { - _signalBars = signalBars; - - if (_signalBarsChanged) { - _signalBarsChanged(signalBars); - } - } -} - -- (void)setIsMuted:(bool)isMuted { - if (_controller != nil) { - _controller->SetMicMute(isMuted); - } -} - -- (void)setNetworkType:(OngoingCallNetworkType)networkType { - if (_networkType != networkType) { - _networkType = networkType; - if (_controller != nil) { - _controller->SetNetworkType(callControllerNetworkTypeForType(networkType)); - } - } -} - -@end diff --git a/submodules/TgVoip/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoip/Sources/OngoingCallThreadLocalContext.mm index 4da89550f7..e74531bdee 100644 --- a/submodules/TgVoip/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoip/Sources/OngoingCallThreadLocalContext.mm @@ -1,10 +1,9 @@ #import "OngoingCallThreadLocalContext.h" -#import "VoIPController.h" -#import "VoIPServerConfig.h" -#import "os/darwin/SetupLogging.h" +#import "TgVoip.h" #import +#include static void TGCallAesIgeEncrypt(uint8_t *inBytes, uint8_t *outBytes, size_t length, uint8_t *key, uint8_t *iv) { MTAesEncryptRaw(inBytes, outBytes, length, key, iv); @@ -127,34 +126,19 @@ static void withContext(int32_t contextId, void (^f)(OngoingCallThreadLocalConte NSTimeInterval _callRingTimeout; NSTimeInterval _callConnectTimeout; NSTimeInterval _callPacketTimeout; - int32_t _dataSavingMode; - tgvoip::VoIPController *_controller; + TgVoip *_tgVoip; OngoingCallState _state; int32_t _signalBars; NSData *_lastDerivedState; } -- (void)controllerStateChanged:(int)state; +- (void)controllerStateChanged:(TgVoipState)state; - (void)signalBarsChanged:(int32_t)signalBars; @end -static void controllerStateCallback(tgvoip::VoIPController *controller, int state) { - int32_t contextId = (int32_t)((intptr_t)controller->implData); - withContext(contextId, ^(OngoingCallThreadLocalContext *context) { - [context controllerStateChanged:state]; - }); -} - -static void signalBarsCallback(tgvoip::VoIPController *controller, int signalBars) { - int32_t contextId = (int32_t)((intptr_t)controller->implData); - withContext(contextId, ^(OngoingCallThreadLocalContext *context) { - [context signalBarsChanged:(int32_t)signalBars]; - }); -} - @implementation VoipProxyServer - (instancetype _Nonnull)initWithHost:(NSString * _Nonnull)host port:(int32_t)port username:(NSString * _Nullable)username password:(NSString * _Nullable)password { @@ -170,55 +154,62 @@ static void signalBarsCallback(tgvoip::VoIPController *controller, int signalBar @end -static int callControllerNetworkTypeForType(OngoingCallNetworkType type) { +static TgVoipNetworkType callControllerNetworkTypeForType(OngoingCallNetworkType type) { switch (type) { - case OngoingCallNetworkTypeWifi: - return tgvoip::NET_TYPE_WIFI; - case OngoingCallNetworkTypeCellularGprs: - return tgvoip::NET_TYPE_GPRS; - case OngoingCallNetworkTypeCellular3g: - return tgvoip::NET_TYPE_3G; - case OngoingCallNetworkTypeCellularLte: - return tgvoip::NET_TYPE_LTE; - default: - return tgvoip::NET_TYPE_WIFI; + case OngoingCallNetworkTypeWifi: + return TgVoipNetworkType::WiFi; + case OngoingCallNetworkTypeCellularGprs: + return TgVoipNetworkType::Gprs; + case OngoingCallNetworkTypeCellular3g: + return TgVoipNetworkType::ThirdGeneration; + case OngoingCallNetworkTypeCellularLte: + return TgVoipNetworkType::Lte; + default: + return TgVoipNetworkType::ThirdGeneration; } } -static int callControllerDataSavingForType(OngoingCallDataSaving type) { +static TgVoipDataSaving callControllerDataSavingForType(OngoingCallDataSaving type) { switch (type) { - case OngoingCallDataSavingNever: - return tgvoip::DATA_SAVING_NEVER; - case OngoingCallDataSavingCellular: - return tgvoip::DATA_SAVING_MOBILE; - case OngoingCallDataSavingAlways: - return tgvoip::DATA_SAVING_ALWAYS; - default: - return tgvoip::DATA_SAVING_NEVER; + case OngoingCallDataSavingNever: + return TgVoipDataSaving::Never; + case OngoingCallDataSavingCellular: + return TgVoipDataSaving::Mobile; + case OngoingCallDataSavingAlways: + return TgVoipDataSaving::Always; + default: + return TgVoipDataSaving::Never; } } @implementation OngoingCallThreadLocalContext +static void (*InternalVoipLoggingFunction)(NSString *) = NULL; + + (void)setupLoggingFunction:(void (*)(NSString *))loggingFunction { - TGVoipLoggingFunction = loggingFunction; + InternalVoipLoggingFunction = loggingFunction; + TgVoip::setLoggingFunction([](std::string const &string) { + if (InternalVoipLoggingFunction) { + InternalVoipLoggingFunction([[NSString alloc] initWithUTF8String:string.c_str()]); + } + }); } + (void)applyServerConfig:(NSString *)string { if (string.length != 0) { - tgvoip::ServerConfig::GetSharedInstance()->Update(std::string(string.UTF8String)); + TgVoip::setGlobalServerConfig(std::string(string.UTF8String)); } } + (int32_t)maxLayer { - return tgvoip::VoIPController::GetConnectionMaxLayer(); + return 92; } + (NSString *)version { - return [NSString stringWithUTF8String:tgvoip::VoIPController::GetVersion()]; + return [NSString stringWithUTF8String:TgVoip::getVersion().c_str()]; } -- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType dataSaving:(OngoingCallDataSaving)dataSaving derivedState:(NSData * _Nonnull)derivedState { +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType dataSaving:(OngoingCallDataSaving)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescription * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath { self = [super init]; if (self != nil) { _queue = queue; @@ -229,37 +220,108 @@ static int callControllerDataSavingForType(OngoingCallDataSaving type) { _callRingTimeout = 90.0; _callConnectTimeout = 30.0; _callPacketTimeout = 10.0; - _dataSavingMode = callControllerDataSavingForType(dataSaving); _networkType = networkType; - _controller = new tgvoip::VoIPController(); - _controller->implData = (void *)((intptr_t)_contextId); std::vector derivedStateValue; derivedStateValue.resize(derivedState.length); [derivedState getBytes:derivedStateValue.data() length:derivedState.length]; - _controller->SetPersistentState(derivedStateValue); + std::unique_ptr proxyValue = nullptr; if (proxy != nil) { - _controller->SetProxy(tgvoip::PROXY_SOCKS5, proxy.host.UTF8String, (uint16_t)proxy.port, proxy.username.UTF8String ?: "", proxy.password.UTF8String ?: ""); + TgVoipProxy *proxyObject = new TgVoipProxy(); + proxyObject->host = proxy.host.UTF8String; + proxyObject->port = (uint16_t)proxy.port; + proxyObject->login = proxy.username.UTF8String ?: ""; + proxyObject->password = proxy.password.UTF8String ?: ""; + proxyValue = std::unique_ptr(proxyObject); } - auto callbacks = tgvoip::VoIPController::Callbacks(); - callbacks.connectionStateChanged = &controllerStateCallback; - callbacks.groupCallKeyReceived = NULL; - callbacks.groupCallKeySent = NULL; - callbacks.signalBarCountChanged = &signalBarsCallback; - callbacks.upgradeToGroupCallRequested = NULL; - _controller->SetCallbacks(callbacks); + TgVoipCrypto crypto; + crypto.sha1 = &TGCallSha1; + crypto.sha256 = &TGCallSha256; + crypto.rand_bytes = &TGCallRandomBytes; + crypto.aes_ige_encrypt = &TGCallAesIgeEncrypt; + crypto.aes_ige_decrypt = &TGCallAesIgeDecrypt; + crypto.aes_ctr_encrypt = &TGCallAesCtrEncrypt; - tgvoip::VoIPController::crypto.sha1 = &TGCallSha1; - tgvoip::VoIPController::crypto.sha256 = &TGCallSha256; - tgvoip::VoIPController::crypto.rand_bytes = &TGCallRandomBytes; - tgvoip::VoIPController::crypto.aes_ige_encrypt = &TGCallAesIgeEncrypt; - tgvoip::VoIPController::crypto.aes_ige_decrypt = &TGCallAesIgeDecrypt; - tgvoip::VoIPController::crypto.aes_ctr_encrypt = &TGCallAesCtrEncrypt; + std::vector endpoints; + NSArray *connections = [@[primaryConnection] arrayByAddingObjectsFromArray:alternativeConnections]; + for (OngoingCallConnectionDescription *connection in connections) { + unsigned char peerTag[16]; + [connection.peerTag getBytes:peerTag length:16]; + + TgVoipEndpoint endpoint; + endpoint.endpointId = connection.connectionId; + endpoint.host = { + .ipv4 = std::string(connection.ip.UTF8String), + .ipv6 = std::string(connection.ipv6.UTF8String) + }; + endpoint.port = (uint16_t)connection.port; + endpoint.type = TgVoipEndpointType::UdpRelay; + memcpy(endpoint.peerTag, peerTag, 16); + endpoints.push_back(endpoint); + } + + TgVoipConfig config = { + .initializationTimeout = _callConnectTimeout, + .receiveTimeout = _callPacketTimeout, + .dataSaving = callControllerDataSavingForType(dataSaving), + .enableP2P = allowP2P, + .enableAEC = false, + .enableNS = true, + .enableAGC = true, + .enableCallUpgrade = false, + .logPath = logPath.length == 0 ? "" : std::string(logPath.UTF8String), + .maxApiLayer = [OngoingCallThreadLocalContext maxLayer] + }; + + std::vector encryptionKeyValue; + encryptionKeyValue.resize(key.length); + memcpy(encryptionKeyValue.data(), key.bytes, key.length); + + TgVoipEncryptionKey encryptionKey = { + .value = encryptionKeyValue, + .isOutgoing = isOutgoing, + }; + + /* + TgVoipConfig const &config, + TgVoipPersistentState const &persistentState, + std::vector const &endpoints, + std::unique_ptr const &proxy, + TgVoipNetworkType initialNetworkType, + TgVoipEncryptionKey const &encryptionKey + #ifdef TGVOIP_USE_CUSTOM_CRYPTO + , + TgVoipCrypto const &crypto + */ + + _tgVoip = TgVoip::makeInstance( + config, + { derivedStateValue }, + endpoints, + proxyValue, + callControllerNetworkTypeForType(networkType), + encryptionKey, + crypto + ); _state = OngoingCallStateInitializing; _signalBars = -1; + + __weak OngoingCallThreadLocalContext *weakSelf = self; + _tgVoip->setOnStateUpdated([weakSelf](TgVoipState state) { + __strong OngoingCallThreadLocalContext *strongSelf = weakSelf; + if (strongSelf) { + [strongSelf controllerStateChanged:state]; + } + }); + _tgVoip->setOnSignalBarsUpdated([weakSelf](int signalBars) { + __strong OngoingCallThreadLocalContext *strongSelf = weakSelf; + if (strongSelf) { + [strongSelf signalBarsChanged:signalBars]; + } + }); } return self; } @@ -267,79 +329,30 @@ static int callControllerDataSavingForType(OngoingCallDataSaving type) { - (void)dealloc { assert([_queue isCurrent]); removeContext(_contextId); - if (_controller != NULL) { + if (_tgVoip != NULL) { [self stop:nil]; } } -- (void)startWithKey:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescription * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath { - std::vector endpoints; - NSArray *connections = [@[primaryConnection] arrayByAddingObjectsFromArray:alternativeConnections]; - for (OngoingCallConnectionDescription *connection in connections) { - struct in_addr addrIpV4; - if (!inet_aton(connection.ip.UTF8String, &addrIpV4)) { - NSLog(@"CallSession: invalid ipv4 address"); - } - - struct in6_addr addrIpV6; - if (!inet_pton(AF_INET6, connection.ipv6.UTF8String, &addrIpV6)) { - NSLog(@"CallSession: invalid ipv6 address"); - } - - tgvoip::IPv4Address address(std::string(connection.ip.UTF8String)); - tgvoip::IPv6Address addressv6(std::string(connection.ipv6.UTF8String)); - unsigned char peerTag[16]; - [connection.peerTag getBytes:peerTag length:16]; - endpoints.push_back(tgvoip::Endpoint(connection.connectionId, (uint16_t)connection.port, address, addressv6, tgvoip::Endpoint::Type::UDP_RELAY, peerTag)); - } - - tgvoip::VoIPController::Config config(_callConnectTimeout, _callPacketTimeout, _dataSavingMode, false, true, true); - config.logFilePath = logPath.length > 0 ? std::string(logPath.UTF8String) : ""; - config.statsDumpFilePath = ""; - - if (_controller != nil) { - _controller->SetConfig(config); - - _controller->SetNetworkType(callControllerNetworkTypeForType(_networkType)); - _controller->SetEncryptionKey((char *)key.bytes, isOutgoing); - _controller->SetRemoteEndpoints(endpoints, allowP2P, maxLayer); - _controller->Start(); - - _controller->Connect(); - } -} - - (void)stop:(void (^)(NSString *, int64_t, int64_t, int64_t, int64_t))completion { - if (_controller != nil) { - _controller->Stop(); + if (_tgVoip) { + TgVoipFinalState finalState = _tgVoip->stop(); - auto debugString = _controller->GetDebugLog(); - NSString *debugLog = [NSString stringWithUTF8String:debugString.c_str()]; + NSString *debugLog = [NSString stringWithUTF8String:finalState.debugLog.c_str()]; + _lastDerivedState = [[NSData alloc] initWithBytes:finalState.persistentState.value.data() length:finalState.persistentState.value.size()]; - tgvoip::VoIPController::TrafficStats stats; - _controller->GetStats(&stats); - std::vector derivedStateValue = _controller->GetPersistentState(); - _lastDerivedState = [[NSData alloc] initWithBytes:derivedStateValue.data() length:derivedStateValue.size()]; - delete _controller; - _controller = NULL; + delete _tgVoip; + _tgVoip = NULL; if (completion) { - completion(debugLog, stats.bytesSentWifi, stats.bytesRecvdWifi, stats.bytesSentMobile, stats.bytesRecvdMobile); + completion(debugLog, finalState.trafficStats.bytesSentWifi, finalState.trafficStats.bytesReceivedWifi, finalState.trafficStats.bytesSentMobile, finalState.trafficStats.bytesReceivedMobile); } } } - -- (bool)needRate { - if (_controller != nil) { - return _controller->NeedRate(); - } else { - return false; - } -} - (NSString *)debugInfo { - if (_controller != nil) { - auto rawDebugString = _controller->GetDebugString(); + if (_tgVoip != nil) { + auto rawDebugString = _tgVoip->getDebugInfo(); return [NSString stringWithUTF8String:rawDebugString.c_str()]; } else { return nil; @@ -347,17 +360,17 @@ static int callControllerDataSavingForType(OngoingCallDataSaving type) { } - (NSString *)version { - if (_controller != nil) { - return [NSString stringWithUTF8String:_controller->GetVersion()]; + if (_tgVoip != nil) { + return [NSString stringWithUTF8String:_tgVoip->getVersion().c_str()]; } else { return nil; } } - (NSData * _Nonnull)getDerivedState { - if (_controller != nil) { - std::vector derivedStateValue = _controller->GetPersistentState(); - return [[NSData alloc] initWithBytes:derivedStateValue.data() length:derivedStateValue.size()]; + if (_tgVoip) { + auto persistentState = _tgVoip->getPersistentState(); + return [[NSData alloc] initWithBytes:persistentState.value.data() length:persistentState.value.size()]; } else if (_lastDerivedState != nil) { return _lastDerivedState; } else { @@ -365,16 +378,16 @@ static int callControllerDataSavingForType(OngoingCallDataSaving type) { } } -- (void)controllerStateChanged:(int)state { +- (void)controllerStateChanged:(TgVoipState)state { OngoingCallState callState = OngoingCallStateInitializing; switch (state) { - case tgvoip::STATE_ESTABLISHED: + case TgVoipState::Estabilished: callState = OngoingCallStateConnected; break; - case tgvoip::STATE_FAILED: + case TgVoipState::Failed: callState = OngoingCallStateFailed; break; - case tgvoip::STATE_RECONNECTING: + case TgVoipState::Reconnecting: callState = OngoingCallStateReconnecting; break; default: @@ -401,16 +414,16 @@ static int callControllerDataSavingForType(OngoingCallDataSaving type) { } - (void)setIsMuted:(bool)isMuted { - if (_controller != nil) { - _controller->SetMicMute(isMuted); + if (_tgVoip) { + _tgVoip->setMuteMicrophone(isMuted); } } - (void)setNetworkType:(OngoingCallNetworkType)networkType { if (_networkType != networkType) { _networkType = networkType; - if (_controller != nil) { - _controller->SetNetworkType(callControllerNetworkTypeForType(networkType)); + if (_tgVoip) { + _tgVoip->setNetworkType(callControllerNetworkTypeForType(networkType)); } } } diff --git a/submodules/TgVoip/libtgvoip b/submodules/TgVoip/libtgvoip index 522550a1e9..dc4e9ec482 160000 --- a/submodules/TgVoip/libtgvoip +++ b/submodules/TgVoip/libtgvoip @@ -1 +1 @@ -Subproject commit 522550a1e975b17e9048d7a2ab2d5b97cfc2f5d4 +Subproject commit dc4e9ec48207388e41db1c2ef1cccf9899d9765f diff --git a/submodules/TgVoipWebrtc/BUCK b/submodules/TgVoipWebrtc/BUCK new file mode 100644 index 0000000000..2d095f05d1 --- /dev/null +++ b/submodules/TgVoipWebrtc/BUCK @@ -0,0 +1,45 @@ +load("//Config:buck_rule_macros.bzl", "static_library", "glob_map", "glob_sub_map", "merge_maps") + +static_library( + name = "TgVoipWebrtc", + srcs = glob([ + "Sources/**/*.m", + "Sources/**/*.mm", + "Impl/*.cpp", + ]), + has_cpp = True, + headers = merge_maps([ + glob_sub_map("PublicHeaders/", [ + "PublicHeaders/**/*.h", + ]), + glob_sub_map("Impl/", [ + "Impl/*.h", + ]), + ]), + exported_headers = glob([ + "PublicHeaders/**/*.h", + ]), + compiler_flags = [ + "-Ithird-party/webrtc/webrtc-ios/src", + "-Ithird-party/webrtc/webrtc-ios/src/third_party/abseil-cpp", + "-Ithird-party/submodules/TgVoipWebrtc/PublicHeaders", + "-DWEBRTC_IOS", + "-DWEBRTC_MAC", + "-DWEBRTC_POSIX", + ], + deps = [ + "//submodules/MtProtoKit:MtProtoKit#shared", + "//submodules/Opus:opus", + "//submodules/openssl:openssl", + "//third-party/webrtc:webrtc_lib", + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + "$SDKROOT/System/Library/Frameworks/AudioToolbox.framework", + "$SDKROOT/System/Library/Frameworks/VideoToolbox.framework", + "$SDKROOT/System/Library/Frameworks/CoreTelephony.framework", + "$SDKROOT/System/Library/Frameworks/CoreMedia.framework", + "$SDKROOT/System/Library/Frameworks/AVFoundation.framework", + ], +) diff --git a/submodules/TgVoipWebrtc/BUILD b/submodules/TgVoipWebrtc/BUILD new file mode 100644 index 0000000000..eba68a38e4 --- /dev/null +++ b/submodules/TgVoipWebrtc/BUILD @@ -0,0 +1,50 @@ + +cc_library( + name = "webrtc_lib", + srcs = ["libwebrtc.a"], +) + +objc_library( + name = "TgVoipWebrtc", + enable_modules = True, + module_name = "TgVoipWebrtc", + srcs = glob([ + "Sources/**/*.m", + "Sources/**/*.mm", + "Sources/**/*.h", + "Impl/*.h", + "Impl/*.cpp", + ]), + hdrs = glob([ + "PublicHeaders/**/*.h", + ]), + copts = [ + "-I{}/Impl".format(package_name()), + "-Ithird-party/webrtc/webrtc-ios/src", + "-Ithird-party/webrtc/webrtc-ios/src/third_party/abseil-cpp", + "-DWEBRTC_IOS", + "-DWEBRTC_MAC", + "-DWEBRTC_POSIX", + ], + includes = [ + "PublicHeaders", + ], + deps = [ + "//third-party/webrtc:webrtc_lib", + "//submodules/MtProtoKit:MtProtoKit", + "//submodules/Opus:opus", + "//submodules/openssl:openssl", + ], + sdk_frameworks = [ + "Foundation", + "UIKit", + "AudioToolbox", + "VideoToolbox", + "CoreTelephony", + "CoreMedia", + "AVFoundation", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TgVoipWebrtc/Impl/Connector.cpp b/submodules/TgVoipWebrtc/Impl/Connector.cpp new file mode 100644 index 0000000000..15576f5c2f --- /dev/null +++ b/submodules/TgVoipWebrtc/Impl/Connector.cpp @@ -0,0 +1,232 @@ +#include "Connector.h" + +#include "Endpoint.h" +#include "Layer92.h" +#include "MediaEngineWebrtc.h" +#include "Protocol10.h" + +#include "api/packet_socket_factory.h" +#include "rtc_base/task_utils/to_queued_task.h" + +#include + +const int64_t Connector::tcp_reconnect_delay = 5000; +const int64_t Connector::ping_interval_ms = 10000; +const int64_t Connector::endpoint_ping_diff_ms = 20; +const std::set Connector::multicast_types = { + message::Type::tInit, message::Type::tInitAck, message::Type::tPing +}; +const size_t Connector::PingHistory::history_length = 5; +const int64_t Connector::PingHistory::unavailable_ms = 100000; + +Connector::PingHistory::PingHistory() +: ping_sum(0) +, sent_id(0) +, sent_time(0) { + for (size_t i = 0; i < history_length; ++i) + AppendPing(unavailable_ms); +} + +void Connector::PingHistory::AppendPing(int64_t ms) { + if (history.size() >= history_length) { + ping_sum -= history.front(); + history.pop(); + } + if (history.size() < history_length) { + ping_sum += ms; + history.emplace(ms); + } +} + +void Connector::PingHistory::UpdatePing(int64_t ms) { + if (!history.empty()) { + ping_sum = ping_sum - history.back() + ms; + history.back() = ms; + } else + AppendPing(ms); +} + +void Connector::PingHistory::Ping(uint32_t id) { + sent_id = id; + sent_time = rtc::TimeMillis(); +} + +void Connector::PingHistory::Pong(uint32_t id) { + if (id != sent_id) + return; + sent_id = 0; + UpdatePing(std::min(rtc::TimeMillis() - sent_time, unavailable_ms)); + sent_time = 0; +} + +double Connector::PingHistory::Average() { + return static_cast(ping_sum) / history.size(); +} + +Connector::Connector(std::unique_ptr layer) +: active_endpoint(nullptr) +, thread(rtc::Thread::CreateWithSocketServer()) +, socket_factory(thread.get()) +, layer(std::move(layer)) +, ping_seq(0) { + pinger = webrtc::RepeatingTaskHandle::Start(thread.get(), [this]() { + Connector::UpdateActiveEndpoint(); + return webrtc::TimeDelta::ms(ping_interval_ms); + }); +} + +void Connector::Start() { + thread->Start(); + thread->Invoke(RTC_FROM_HERE, [this]() { + socket.reset(socket_factory.CreateUdpSocket( + rtc::SocketAddress(rtc::GetAnyIP(AF_INET), 0), 0, 0)); + socket->SignalReadPacket.connect(this, &Connector::RecvPacket); + socket->SignalReadyToSend.connect(this, &Connector::Ready); + }); +} + +void Connector::RecvPacket(rtc::AsyncPacketSocket *sock, const char *data, size_t len, + const rtc::SocketAddress& remote_addr, const int64_t& packet_time_us) { + for (const auto& ep : endpoints) { + auto ep_udp = dynamic_cast(ep.first); + if (ep_udp && ep_udp->address == remote_addr) { + ep_udp->RecvPacket(sock, data, len, remote_addr, packet_time_us); + break; + } + } +} + +void Connector::Ready(rtc::AsyncPacketSocket *) { + SignalMessage(message::Ready()); +} + +void Connector::AddEndpointRelayTcpObfuscated(const rtc::SocketAddress& addr, const Relay::PeerTag& peer_tag) { + thread->Invoke(RTC_FROM_HERE, [this, addr, peer_tag]() { + std::unique_ptr sock(socket_factory.CreateClientTcpSocket( + rtc::SocketAddress(rtc::GetAnyIP(AF_INET), 0), + addr, proxy_info, "", rtc::PacketSocketTcpOptions())); + AddEndpoint(std::make_unique(std::move(sock), peer_tag, layer.get())); + }); +} + +void Connector::AddEndpointRelayUdp(const rtc::SocketAddress& addr, const Relay::PeerTag& peer_tag) { + thread->Invoke(RTC_FROM_HERE, [this, addr, peer_tag]() { + assert(socket); + AddEndpoint(std::make_unique(addr, peer_tag, socket.get(), layer.get())); + }); +} + +void Connector::SetEndpointP2p(const rtc::SocketAddress& addr) { + thread->Invoke(RTC_FROM_HERE, [this, addr]() { + assert(socket); + if (auto ep = GetP2pEndpoint()) + DeleteEndpoint(ep); + AddEndpoint(std::make_unique(addr, socket.get(), layer.get())); + }); +} + +Connector::~Connector() { + thread->Invoke(RTC_FROM_HERE, [this]() { + pinger.Stop(); + active_endpoint = nullptr; + endpoints.clear(); + ping_history.clear(); + }); +} + +void Connector::RecvMessage(const message::Base& msg, EndpointBase *endpoint) { + if (msg.ID == message::tDisconnected && endpoint->type == EndpointBase::Type::RelayObfuscatedTcp) { + thread->PostDelayedTask(webrtc::ToQueuedTask([this, endpoint]() { + if (endpoints.find(endpoint) == endpoints.end()) + return; + auto final_ep = dynamic_cast(endpoint); + if (!final_ep) + return; + std::unique_ptr sock(socket_factory.CreateClientTcpSocket( + rtc::SocketAddress(rtc::GetAnyIP(AF_INET), 0), + final_ep->address, proxy_info, "", rtc::PacketSocketTcpOptions())); + final_ep->Reconnect(std::move(sock)); + }), tcp_reconnect_delay); + if (active_endpoint == endpoint) + ResetActiveEndpoint(); + return; + } + if (auto msg_ping = dynamic_cast(&msg)) { + message::Pong msg_pong; + msg_pong.id = msg_ping->id; + endpoint->SendMessage(msg_pong); + return; + } + if (auto msg_pong = dynamic_cast(&msg)) { + ping_history[endpoint].Pong(msg_pong->id); + return; + } + // fallback if no active endpoint set + if (!active_endpoint) + active_endpoint = endpoint; + SignalMessage(msg); +} + +void Connector::SendMessage(const message::Base& msg) { + if (!active_endpoint || multicast_types.find(msg.ID) != multicast_types.end()) { + for (const auto& ep : endpoints) { + ep.first->SendMessage(msg); + if (auto msg_ping = dynamic_cast(&msg)) + ping_history[ep.first].Ping(msg_ping->id); + } + return; + } + active_endpoint->SendMessage(msg); +} + +EndpointP2p *Connector::GetP2pEndpoint() const { + for (const auto& ep : endpoints) + if (auto ep_p2p = dynamic_cast(ep.first)) + return ep_p2p; + return nullptr; +} + +void Connector::AddEndpoint(std::unique_ptr endpoint) { + EndpointBase *ep = endpoint.get(); + ep->SignalMessage.connect(this, &Connector::RecvMessage); + endpoints[ep] = std::move(endpoint); + ping_history[ep] = PingHistory(); +} + +void Connector::DeleteEndpoint(EndpointBase *ep) { + // TODO: must be invoked to thread when become public + endpoints.erase(ep); + ping_history.erase(ep); +} + +void Connector::ResetActiveEndpoint() { + active_endpoint = nullptr; +} + +void Connector::UpdateActiveEndpoint() { + if (ping_history.empty()) + return; + if (ping_history.size() == 1) { + active_endpoint = ping_history.begin()->first; + return; + } + std::vector> times; + for (auto ping : ping_history) + times.emplace_back(ping.second.Average(), ping.first); + std::sort(times.begin(), times.end()); + EndpointBase *candidate = times.front().second; + if (!active_endpoint || (active_endpoint != candidate && + ping_history[active_endpoint].Average() - times.front().first > endpoint_ping_diff_ms)) + active_endpoint = candidate; + message::Ping msg; + msg.id = ++ping_seq; + SendMessage(msg); +} + +void Connector::SetProxy(rtc::ProxyType type, const rtc::SocketAddress& addr, const std::string& username, + const std::string& password) { + proxy_info.type = type; + proxy_info.address = addr; + proxy_info.username = username; + proxy_info.password = rtc::CryptString(); +} diff --git a/submodules/TgVoipWebrtc/Impl/Connector.h b/submodules/TgVoipWebrtc/Impl/Connector.h new file mode 100644 index 0000000000..4331460a27 --- /dev/null +++ b/submodules/TgVoipWebrtc/Impl/Connector.h @@ -0,0 +1,79 @@ +#ifndef DEMO_CONNECTOR_H +#define DEMO_CONNECTOR_H + + +#include "Endpoint.h" +#include "LayerBase.h" +#include "Message.h" + +#include "p2p/base/basic_packet_socket_factory.h" +#include "rtc_base/proxy_info.h" +#include "rtc_base/task_utils/repeating_task.h" +#include "rtc_base/third_party/sigslot/sigslot.h" +#include "rtc_base/thread.h" + +#include +#include + +class Connector : public sigslot::has_slots<> { +public: + explicit Connector(std::unique_ptr layer); + ~Connector() override; + void Start(); + void AddEndpointRelayTcpObfuscated(const rtc::SocketAddress& addr, const Relay::PeerTag& peer_tag); + void AddEndpointRelayUdp(const rtc::SocketAddress& addr, const Relay::PeerTag& peer_tag); + void SetEndpointP2p(const rtc::SocketAddress& addr); + sigslot::signal1 SignalMessage; + void SendMessage(const message::Base&); + void ResetActiveEndpoint(); + void SetProxy(rtc::ProxyType type, const rtc::SocketAddress& addr, const std::string& username, + const std::string& password); + +private: + class PingHistory { + public: + PingHistory(); + void Ping(uint32_t id); + void Pong(uint32_t id); + double Average(); + + private: + void AppendPing(int64_t ms); + void UpdatePing(int64_t ms); + + static const size_t history_length; + static const int64_t unavailable_ms; + + std::queue history; + int64_t ping_sum; + uint32_t sent_id; + int64_t sent_time; + }; + + static const int64_t tcp_reconnect_delay; + static const int64_t ping_interval_ms; + static const int64_t endpoint_ping_diff_ms; + static const std::set multicast_types; + + EndpointP2p *GetP2pEndpoint() const; + void AddEndpoint(std::unique_ptr); + void DeleteEndpoint(EndpointBase *ep); + void RecvPacket(rtc::AsyncPacketSocket *socket, const char *data, size_t len, + const rtc::SocketAddress& remote_addr, const int64_t& packet_time_us); + void Ready(rtc::AsyncPacketSocket *); + void RecvMessage(const message::Base&, EndpointBase *); + void UpdateActiveEndpoint(); + + rtc::ProxyInfo proxy_info; + EndpointBase *active_endpoint; + std::unique_ptr thread; + rtc::BasicPacketSocketFactory socket_factory; + std::unique_ptr socket; + std::unique_ptr layer; + std::map> endpoints; + std::map ping_history; + webrtc::RepeatingTaskHandle pinger; + uint32_t ping_seq; +}; + +#endif //DEMO_CONNECTOR_H diff --git a/submodules/TgVoipWebrtc/Impl/Controller.cpp b/submodules/TgVoipWebrtc/Impl/Controller.cpp new file mode 100644 index 0000000000..b00c97819b --- /dev/null +++ b/submodules/TgVoipWebrtc/Impl/Controller.cpp @@ -0,0 +1,225 @@ +#include "Controller.h" + +#include "Layer92.h" + +#include "modules/rtp_rtcp/source/rtp_utility.h" +#include "rtc_base/time_utils.cc" +#include "rtc_base/message_handler.h" + +#include + +std::map Controller::network_params = { + {message::NetworkType::nGprs, {6, 8, 6, 120, false, false, false}}, + {message::NetworkType::nEdge, {6, 16, 6, 120, false, false, false}}, + {message::NetworkType::n3gOrAbove, {6, 32, 16, 60, false, false, false}}, +}; +MediaEngineWebrtc::NetworkParams Controller::default_network_params = {6, 32, 16, 30, false, false, false}; +MediaEngineWebrtc::NetworkParams Controller::datasaving_network_params = {6, 8, 6, 120, false, false, true}; + +Controller::Controller(bool is_outgoing, const EncryptionKey& encryption_key, size_t init_timeout, size_t reconnect_timeout) +: thread(rtc::Thread::Create()) +, connector(std::make_unique(std::make_unique(encryption_key, is_outgoing))) +, state(State::Starting) +, is_outgoing(is_outgoing) +, last_recv_time(rtc::TimeMillis()) +, last_send_time(rtc::TimeMillis()) +, init_timeout(init_timeout * 1000) +, reconnect_timeout(reconnect_timeout * 1000) +, local_datasaving(false) +, final_datasaving(false) +, local_network_type(message::NetworkType::nUnknown) +, final_network_type(message::NetworkType::nUnknown) +{ + connector->SignalMessage.connect(this, &Controller::NewMessage); + thread->Start(); +} + +Controller::~Controller() { + thread->Invoke(RTC_FROM_HERE, [this]() { + media = nullptr; +#ifdef TGVOIP_PREPROCESSED_OUTPUT + preproc = nullptr; +#endif + connector = nullptr; + }); +} + +void Controller::AddEndpoint(const rtc::SocketAddress& address, const Relay::PeerTag &peer_tag, + Controller::EndpointType type) { + if (type == EndpointType::UDP) + connector->AddEndpointRelayUdp(address, peer_tag); + else if (type == EndpointType::TCP) + connector->AddEndpointRelayTcpObfuscated(address, peer_tag); + else if (type == EndpointType::P2P) + connector->SetEndpointP2p(address); +} + +void Controller::Start() { + last_recv_time = rtc::TimeMillis(); + connector->Start(); +} + +void Controller::NewMessage(const message::Base& msg) { + if (msg.ID == message::tReady && state == State::Starting) { + state = State::WaitInit; + SignalNewState(state); + StartRepeating([this]() { + message::Init msg; + msg.minVer = ProtocolBase::minimal_version; + msg.ver = ProtocolBase::actual_version; + connector->SendMessage(msg); + if (rtc::TimeMillis() - last_recv_time > init_timeout) + SetFail(); + return webrtc::TimeDelta::seconds(1); + }); + } else if ((msg.ID == message::tInit || msg.ID == message::tInitAck) && state == State::WaitInit) { + state = State::WaitInitAck; + SignalNewState(state); + StartRepeating([this]() { + message::InitAck msg; + // TODO: version matching + msg.minVer = ProtocolBase::minimal_version; + msg.ver = ProtocolBase::actual_version; + connector->SendMessage(msg); + if (rtc::TimeMillis() - last_recv_time > init_timeout) + SetFail(); + return webrtc::TimeDelta::seconds(1); + }); + } else if ((msg.ID == message::tInitAck || msg.ID == message::tRtpStream) && state == State::WaitInitAck) { + state = State::Established; + SignalNewState(state); + thread->PostTask(RTC_FROM_HERE, [this]() { +#ifdef TGVOIP_PREPROCESSED_OUTPUT + preproc = std::make_unique(not is_outgoing, false, true); + preproc->Play.connect(this, &Controller::Preprocessed); +#endif + media = std::make_unique(is_outgoing); + media->Record.connect(this, &Controller::Record); + media->Play.connect(this, &Controller::Play); + media->Send.connect(this, &Controller::SendRtp); + }); + StartRepeating([this]() { + if (state == State::Established && rtc::TimeMillis() - last_recv_time > 1000) { + connector->ResetActiveEndpoint(); + state = State::Reconnecting; + SignalNewState(state); + } else if (state == State::Reconnecting && rtc::TimeMillis() - last_recv_time > reconnect_timeout) + SetFail(); + return webrtc::TimeDelta::seconds(1); + }); + } if ((msg.ID == message::tRtpStream) && (state == State::Established || state == State::Reconnecting)) { + const auto msg_rtp = *dynamic_cast(&msg); + thread->PostTask(RTC_FROM_HERE, [this, msg_rtp]() { + if (media) { + media->Receive(msg_rtp.data); + UpdateNetworkParams(msg_rtp); + } + }); + if (!webrtc::RtpUtility::RtpHeaderParser(msg_rtp.data.data(), msg_rtp.data.size()).RTCP()) { + last_recv_time = rtc::TimeMillis(); + if (state == State::Reconnecting) { + state = State::Established; + SignalNewState(state); + } + } + } else if (msg.ID == message::tBufferOverflow || + msg.ID == message::tPacketIncorrect || + msg.ID == message::tWrongProtocol) { + SetFail(); + } +} + +template +void Controller::StartRepeating(Closure&& closure) { + StopRepeating(); + repeatable = webrtc::RepeatingTaskHandle::Start(thread.get(), std::forward(closure)); +} + +void Controller::StopRepeating() { + thread->Invoke(RTC_FROM_HERE, [this]() { + repeatable.Stop(); + }); +} + +void Controller::SetFail() { + thread->PostTask(RTC_FROM_HERE, [this]() { + media = nullptr; +#ifdef TGVOIP_PREPROCESSED_OUTPUT + preproc = nullptr; +#endif + }); + if (state != State::Failed) { + state = State::Failed; + SignalNewState(state); + } + StopRepeating(); +} + +void Controller::Play(const int16_t *data, size_t size) { + SignalPlay(data, size); +} + +void Controller::Record(int16_t *data, size_t size) { + SignalRecord(data, size); + last_send_time = rtc::TimeMillis(); +} + +#ifdef TGVOIP_PREPROCESSED_OUTPUT +void Controller::Preprocessed(const int16_t *data, size_t size) { + if (rtc::TimeMillis() - last_send_time < 100) + SignalPreprocessed(data, size); +} +#endif + +void Controller::SendRtp(rtc::CopyOnWriteBuffer packet) { +#ifdef TGVOIP_PREPROCESSED_OUTPUT + thread->PostTask(RTC_FROM_HERE, [this, packet]() { + if (preproc) + preproc->Receive(packet); + }); +#endif + message::RtpStream msg; + msg.data = packet; + msg.network_type = local_network_type; + msg.data_saving = local_datasaving; + connector->SendMessage(msg); +} + +void Controller::UpdateNetworkParams(const message::RtpStream& rtp) { + bool new_datasaving = local_datasaving || rtp.data_saving; + if (!new_datasaving) { + final_datasaving = false; + message::NetworkType new_network_type = std::min(local_network_type, rtp.network_type); + if (new_network_type != final_network_type) { + final_network_type = new_network_type; + auto it = network_params.find(rtp.network_type); + if (it == network_params.end()) + media->SetNetworkParams(default_network_params); + else + media->SetNetworkParams(it->second); + } + } else if (new_datasaving != final_datasaving) { + final_datasaving = true; + media->SetNetworkParams(datasaving_network_params); + } +} + +void Controller::SetNetworkType(message::NetworkType network_type) { + local_network_type = network_type; +} + +void Controller::SetDataSaving(bool data_saving) { + local_datasaving = data_saving; +} + +void Controller::SetMute(bool mute) { + thread->Invoke(RTC_FROM_HERE, [this, mute]() { + if (media) + media->SetMute(mute); + }); +} + +void Controller::SetProxy(rtc::ProxyType type, const rtc::SocketAddress& addr, const std::string& username, + const std::string& password) { + connector->SetProxy(type, addr, username, password); +} diff --git a/submodules/TgVoipWebrtc/Impl/Controller.h b/submodules/TgVoipWebrtc/Impl/Controller.h new file mode 100644 index 0000000000..ebd1a7d27c --- /dev/null +++ b/submodules/TgVoipWebrtc/Impl/Controller.h @@ -0,0 +1,84 @@ +#ifndef DEMO_CONTROLLER_H +#define DEMO_CONTROLLER_H + + +#include "Connector.h" +#include "MediaEngineWebrtc.h" +#include "Layer92.h" + +#include "rtc_base/copy_on_write_buffer.h" +#include "rtc_base/socket_address.h" +#include "rtc_base/task_utils/repeating_task.h" +#include "rtc_base/third_party/sigslot/sigslot.h" + +class Controller : public sigslot::has_slots<> { +public: + enum EndpointType { + UDP, + TCP, + P2P, + }; + + enum State { + Starting, + WaitInit, + WaitInitAck, + Established, + Failed, + Reconnecting, + }; + + explicit Controller(bool is_outgoing, const EncryptionKey& encryption_key, size_t init_timeout, size_t reconnect_timeout); + ~Controller() override; + void AddEndpoint(const rtc::SocketAddress& address, const Relay::PeerTag& peer_tag, EndpointType type); + void Start(); + void SetNetworkType(message::NetworkType network_type); + void SetDataSaving(bool data_saving); + void SetMute(bool mute); + void SetProxy(rtc::ProxyType type, const rtc::SocketAddress& addr, const std::string& username, + const std::string& password); + + static std::map network_params; + static MediaEngineWebrtc::NetworkParams default_network_params; + static MediaEngineWebrtc::NetworkParams datasaving_network_params; + sigslot::signal2 SignalRecord; +#ifdef TGVOIP_PREPROCESSED_OUTPUT + sigslot::signal2 SignalPreprocessed; +#endif + sigslot::signal2 SignalPlay; + sigslot::signal1 SignalNewState; + +private: + std::unique_ptr thread; + std::unique_ptr connector; + std::unique_ptr media; +#ifdef TGVOIP_PREPROCESSED_OUTPUT + std::unique_ptr preproc; +#endif + State state; + webrtc::RepeatingTaskHandle repeatable; + int64_t last_recv_time; + int64_t last_send_time; + const bool is_outgoing; + const size_t init_timeout; + const size_t reconnect_timeout; + bool local_datasaving; + bool final_datasaving; + message::NetworkType local_network_type; + message::NetworkType final_network_type; + + template void StartRepeating(Closure&& closure); + void StopRepeating(); + void NewMessage(const message::Base& msg); + void SetFail(); + void Play(const int16_t *data, size_t size); + void Record(int16_t *data, size_t size); +#ifdef TGVOIP_PREPROCESSED_OUTPUT + void Preprocessed(const int16_t *data, size_t size); +#endif + void SendRtp(rtc::CopyOnWriteBuffer packet); + void UpdateNetworkParams(const message::RtpStream& rtp); +}; + + +#endif //DEMO_CONTROLLER_H diff --git a/submodules/TgVoipWebrtc/Impl/Endpoint.cpp b/submodules/TgVoipWebrtc/Impl/Endpoint.cpp new file mode 100644 index 0000000000..4e26c0d941 --- /dev/null +++ b/submodules/TgVoipWebrtc/Impl/Endpoint.cpp @@ -0,0 +1,204 @@ +#include "Endpoint.h" + +#include "rtc_base/buffer.h" +#include "rtc_base/byte_buffer.h" + +#include +#include + +EndpointBase::EndpointBase(LayerBase *layer, Type type) +: type(type) +, layer(layer) +, in_buffer(std::make_unique(nullptr, 0)) +, in_remains(0) {} + +void EndpointBase::RecvPacket(rtc::AsyncPacketSocket *, const char *data, size_t len, + const rtc::SocketAddress& remote_addr, const int64_t& packet_time_us) { + if (in_buffer && in_buffer->Length() > 0) { + rtc::Buffer tmp(in_buffer->Data(), in_buffer->Length() + len); + memcpy(tmp.data() + in_buffer->Length(), data, len); + in_buffer = std::make_unique(reinterpret_cast(tmp.data()), tmp.size()); + } else + in_buffer = std::make_unique(data, len); +} + +EndpointUdp::EndpointUdp(const rtc::SocketAddress& addr, rtc::AsyncPacketSocket *socket, LayerBase *layer, Type type) +: EndpointBase(layer, type) +, address(addr) +, socket(socket) {} + +void EndpointUdp::SendPacket(const uint8_t *data, size_t size) { + socket->SendTo(data, size, address, packet_options); +} + +EndpointTcp::EndpointTcp(std::unique_ptr socket, LayerBase *layer, Type type) +: EndpointBase(layer, type) +, address(socket->GetRemoteAddress()) +, socket(nullptr) { + Reconnect(std::move(socket)); +} + +void EndpointTcp::Reconnect(std::unique_ptr socket_) { + socket = std::move(socket_); + socket->SignalReadPacket.connect(dynamic_cast(this), &EndpointTcp::RecvPacket); + socket->SignalReadyToSend.connect(this, &EndpointTcp::Ready); + socket->SignalClose.connect(this, &EndpointTcp::Close); +} + +void EndpointTcp::SendPacket(const uint8_t *data, size_t size) { + socket->Send(data, size, packet_options); +} + +Relay::Relay(const PeerTag& peer_tag_) : peer_tag() { + memcpy(peer_tag, peer_tag_, sizeof(PeerTag)); +} + +bool Relay::CheckPacket(rtc::ByteBufferReader *packet) { + if (packet->Length() >= 16 && memcmp(peer_tag, packet->Data(), 16) == 0) { + packet->Consume(16); + return true; + } + return false; +} + +const rtc::Buffer& Relay::PreparePacket(const rtc::Buffer& packet) { + buffer.Clear(); + if (!packet.empty()) { + buffer.AppendData(peer_tag, 16); + buffer.AppendData(packet); + } + return buffer; +} + +EndpointRelayObfuscatedTcp::EndpointRelayObfuscatedTcp(std::unique_ptr socket, + const PeerTag& peer_tag, LayerBase *layer) +: Relay(peer_tag) +, EndpointTcp(std::move(socket), layer, Type::RelayObfuscatedTcp) +, recvState() +, sendState() {} + +void EndpointRelayObfuscatedTcp::Ready(rtc::AsyncPacketSocket *) { + unsigned char buf[64]; + layer->GenerateTCPO2States(buf, &recvState, &sendState); + EndpointTcp::SendPacket(buf, 64); + SignalMessage(message::Connected(), this); +} + +void EndpointRelayObfuscatedTcp::Close(rtc::AsyncPacketSocket *, int) { + SignalMessage(message::Disconnected(), this); +} + +void EndpointRelayObfuscatedTcp::RecvPacket(rtc::AsyncPacketSocket *socket, const char *data, size_t packet_len, + const rtc::SocketAddress& remote_addr, const int64_t& packet_time_us) { + EndpointBase::RecvPacket(socket, data, packet_len, remote_addr, packet_time_us); + do { + if (in_remains > in_buffer->Length()) + break; + if (in_remains > 0 && CheckPacket(in_buffer.get())) { + auto msg = layer->DecodePacket(*in_buffer); + if (msg) + SignalMessage(*msg, this); + } + + unsigned char len1; + size_t packetLen = 0; + if (!in_buffer->ReadUInt8(&len1)) + break; + layer->EncryptForTCPO2(&len1, 1, &recvState); + if (len1 < 0x7F) { + packetLen = (size_t) len1 * 4; + } else { + unsigned char len2[3]; + if (!in_buffer->ReadBytes(reinterpret_cast(&len2), 3)) { + SignalMessage(message::PacketIncorrect(), this); + return; + } + layer->EncryptForTCPO2(len2, 3, &recvState); + packetLen = ((size_t) len2[0] | ((size_t) len2[1] << 8) | ((size_t) len2[2] << 16)) * 4; + } + + in_remains = packetLen; + if (packetLen > in_buffer->Length()) { + in_remains = packetLen; + break; + } + } while (true); +} + +void EndpointRelayObfuscatedTcp::SendMessage(const message::Base& msg_base) { + if (socket->GetState() == rtc::AsyncPacketSocket::State::STATE_CLOSED) + return; + const rtc::Buffer& out = PreparePacket(layer->EncodePacket(&msg_base)); + if (!out.empty()) + SendPacket(out.data(), out.size()); +} + +void EndpointRelayObfuscatedTcp::SendPacket(const uint8_t *data, size_t size) { + rtc::ByteBufferWriter out; + size_t len = size / 4; + if (len < 0x7F) { + out.WriteUInt8(len); + } else { + out.WriteUInt8(0x7F); + out.WriteUInt8(len & 0xFF); + out.WriteUInt8((len >> 8) & 0xFF); + out.WriteUInt8((len >> 16) & 0xFF); + } + out.WriteBytes(reinterpret_cast(data), size); + layer->EncryptForTCPO2(reinterpret_cast(out.ReserveWriteBuffer(0)), + out.Length(), &sendState); + EndpointTcp::SendPacket(reinterpret_cast(out.Data()), out.Length()); +} + +EndpointRelayUdp::EndpointRelayUdp(const rtc::SocketAddress& addr, const PeerTag& peer_tag, + rtc::AsyncPacketSocket *socket, LayerBase *layer) + : Relay(peer_tag) + , EndpointUdp(addr, socket, layer, Type::RelayUdp) {} + +void EndpointRelayUdp::SendMessage(const message::Base& msg_base) { + const rtc::Buffer& out = PreparePacket(layer->EncodePacket(&msg_base)); + if (!out.empty()) + SendPacket(out.data(), out.size()); +} + +void EndpointRelayUdp::RecvPacket(rtc::AsyncPacketSocket *sock, const char *data, size_t len, + const rtc::SocketAddress& remote_addr, const int64_t& packet_time_us) { + bool glued; + bool processed = false; + do { + EndpointBase::RecvPacket(sock, data, len, remote_addr, packet_time_us); + glued = in_buffer->Length() > len; + std::unique_ptr msg; + while (CheckPacket(in_buffer.get()) && (msg = layer->DecodePacket(*in_buffer))) { + processed = true; + SignalMessage(*msg, this); + } + if (!processed) + in_buffer = std::make_unique(nullptr, 0); + } while (!processed && glued); +} + +EndpointP2p::EndpointP2p(const rtc::SocketAddress& addr, rtc::AsyncPacketSocket *socket, LayerBase *layer) +: EndpointUdp(addr, socket, layer, Type::P2p) {} + +void EndpointP2p::SendMessage(const message::Base& msg_base) { + rtc::Buffer out = layer->EncodePacket(&msg_base); + if (!out.empty()) + SendPacket(out.data(), out.size()); +} + +void EndpointP2p::RecvPacket(rtc::AsyncPacketSocket *sock, const char *data, size_t len, + const rtc::SocketAddress& remote_addr, const int64_t& packet_time_us) { + bool glued; + bool processed = false; + do { + EndpointBase::RecvPacket(sock, data, len, remote_addr, packet_time_us); + glued = in_buffer->Length() > len; + while (auto msg = layer->DecodePacket(*in_buffer)) { + processed = true; + SignalMessage(*msg, this); + } + if (!processed) + in_buffer = std::make_unique(nullptr, 0); + } while (!processed && glued); +} diff --git a/submodules/TgVoipWebrtc/Impl/Endpoint.h b/submodules/TgVoipWebrtc/Impl/Endpoint.h new file mode 100644 index 0000000000..3d6593aeec --- /dev/null +++ b/submodules/TgVoipWebrtc/Impl/Endpoint.h @@ -0,0 +1,126 @@ +#ifndef DEMO_ENDPOINT_H +#define DEMO_ENDPOINT_H + + +#include "LayerBase.h" +#include "Message.h" + +#include "rtc_base/async_packet_socket.h" +#include "rtc_base/buffer_queue.h" +#include "rtc_base/socket_address.h" + +class Connector; + +class EndpointBase : public sigslot::has_slots<> { +public: + enum Type { + Unknown, + RelayUdp, + RelayObfuscatedTcp, + P2p, + }; + + EndpointBase(const EndpointBase&) = delete; + virtual void SendMessage(const message::Base&) = 0; + sigslot::signal2 SignalMessage; + const Type type; + +protected: + explicit EndpointBase(LayerBase *layer, Type type); + virtual void RecvPacket(rtc::AsyncPacketSocket *socket, const char *data, size_t len, + const rtc::SocketAddress& remote_addr, const int64_t& packet_time_us); + virtual void SendPacket(const uint8_t *data, size_t size) = 0; + + LayerBase *layer; + rtc::PacketOptions packet_options; + std::unique_ptr in_buffer; + size_t in_remains; +}; + +class Relay { +public: + typedef unsigned char PeerTag[16]; + + virtual ~Relay() = default; + +protected: + explicit Relay(const PeerTag& peer_tag); + bool CheckPacket(rtc::ByteBufferReader *packet); + const rtc::Buffer& PreparePacket(const rtc::Buffer& packet); + + PeerTag peer_tag; // how to initialize it in initializer list? + +private: + rtc::Buffer buffer; +}; + +class EndpointUdp : public EndpointBase { +public: + const rtc::SocketAddress address; + +protected: + friend class Connector; +// friend void Connector::RecvPacket(rtc::AsyncPacketSocket *, const char *, size_t, +// const rtc::SocketAddress&, const int64_t&); + + EndpointUdp(const rtc::SocketAddress& addr, rtc::AsyncPacketSocket *socket, LayerBase *layer, Type type); + void SendPacket(const uint8_t *data, size_t size) override; + + rtc::AsyncPacketSocket *socket; +}; + +class EndpointTcp : public EndpointBase { +public: + const rtc::SocketAddress address; + + void Reconnect(std::unique_ptr socket_); + +protected: + explicit EndpointTcp(std::unique_ptr socket, LayerBase *layer, Type type); + void SendPacket(const uint8_t *data, size_t size) override; + + virtual void Ready(rtc::AsyncPacketSocket *) = 0; + virtual void Close(rtc::AsyncPacketSocket *, int) = 0; + + std::unique_ptr socket; +}; + +class EndpointRelayObfuscatedTcp final : public Relay, public EndpointTcp { +public: + EndpointRelayObfuscatedTcp(std::unique_ptr socket, const PeerTag& peer_tag, + LayerBase *layer); + void SendMessage(const message::Base&) override; + +private: + void RecvPacket(rtc::AsyncPacketSocket *socket, const char *data, size_t len, + const rtc::SocketAddress& remote_addr, const int64_t& packet_time_us) override; + void Ready(rtc::AsyncPacketSocket *) override; + void Close(rtc::AsyncPacketSocket *, int) override; + void SendPacket(const uint8_t *data, size_t size) override; + + TCPO2State recvState; + TCPO2State sendState; +}; + +class EndpointRelayUdp final : public Relay, public EndpointUdp { +public: + EndpointRelayUdp(const rtc::SocketAddress& addr, const PeerTag& peer_tag, + rtc::AsyncPacketSocket *socket, LayerBase *layer); + void SendMessage(const message::Base&) override; + +private: + void RecvPacket(rtc::AsyncPacketSocket *socket, const char *data, size_t len, + const rtc::SocketAddress& remote_addr, const int64_t& packet_time_us) override; +}; + +class EndpointP2p final : public EndpointUdp { +public: + EndpointP2p(const rtc::SocketAddress& addr, rtc::AsyncPacketSocket *socket, LayerBase *layer); + void SendMessage(const message::Base&) override; + +private: + void RecvPacket(rtc::AsyncPacketSocket *socket, const char *data, size_t len, + const rtc::SocketAddress& remote_addr, const int64_t& packet_time_us) override; +}; + +#endif //DEMO_ENDPOINT_H diff --git a/submodules/TgVoipWebrtc/Impl/Layer92.cpp b/submodules/TgVoipWebrtc/Impl/Layer92.cpp new file mode 100644 index 0000000000..8bea191d6b --- /dev/null +++ b/submodules/TgVoipWebrtc/Impl/Layer92.cpp @@ -0,0 +1,238 @@ +#include "Layer92.h" + +#include "Message.h" + +#include "rtc_base/byte_buffer.h" +#include "rtc_base/byte_order.h" + +#include +#include + +namespace { + +#define TLID_UDP_REFLECTOR_SELF_INFO 0xc01572c7 +#define TLID_UDP_REFLECTOR_PEER_INFO 0x27D9371C + +#ifdef _MSC_VER +#define MSC_STACK_FALLBACK(a, b) (b) +#else +#define MSC_STACK_FALLBACK(a, b) (a) +#endif + +} + +Layer92::Layer92(const EncryptionKey& encryptionKey_, bool isOutgoing) +: LayerBase(92) +, encryptionKey() +, isOutgoing(isOutgoing) { + memcpy(encryptionKey, encryptionKey_, sizeof(encryptionKey)); +} + +void Layer92::EncryptForTCPO2(unsigned char *buffer, size_t len, TCPO2State *state) { + crypto.aes_ctr_encrypt(buffer, len, state->key, state->iv, state->ecount, &state->num); +} + +void Layer92::GenerateTCPO2States(unsigned char* buffer, TCPO2State* recvState, TCPO2State* sendState) { + memset(recvState, 0, sizeof(TCPO2State)); + memset(sendState, 0, sizeof(TCPO2State)); + unsigned char nonce[64]; + uint32_t *first = reinterpret_cast(nonce), *second = first + 1; + uint32_t first1 = 0x44414548U, first2 = 0x54534f50U, first3 = 0x20544547U, first4 = 0x20544547U, first5 = 0xeeeeeeeeU; + uint32_t second1 = 0; + do { + crypto.rand_bytes(nonce, sizeof(nonce)); + } while (*first == first1 || *first == first2 || *first == first3 || *first == first4 || *first == first5 || + *second == second1 || *reinterpret_cast(nonce) == 0xef); + + // prepare encryption key/iv + memcpy(sendState->key, nonce + 8, 32); + memcpy(sendState->iv, nonce + 8 + 32, 16); + + // prepare decryption key/iv + char reversed[48]; + memcpy(reversed, nonce + 8, sizeof(reversed)); + std::reverse(reversed, reversed + sizeof(reversed)); + memcpy(recvState->key, reversed, 32); + memcpy(recvState->iv, reversed + 32, 16); + + // write protocol identifier + *reinterpret_cast(nonce + 56) = 0xefefefefU; + memcpy(buffer, nonce, 56); + EncryptForTCPO2(nonce, sizeof(nonce), sendState); + memcpy(buffer + 56, nonce + 56, 8); +} + +std::unique_ptr Layer92::DecodeRelayPacket(rtc::ByteBufferReader& in) { + if (in.Length() < 12 + 4 + 16) + return nullptr; + if (*reinterpret_cast(in.Data()) != 0xFFFFFFFFFFFFFFFFLL) + return nullptr; + if (*reinterpret_cast(in.Data() + 8) != 0xFFFFFFFF) + return nullptr; + + // relay special request response + in.Consume(12); + uint32_t tlid; + if (!in.ReadUInt32(&tlid)) + return nullptr; + + if (tlid == TLID_UDP_REFLECTOR_SELF_INFO) { + if (in.Length() < 32) + return nullptr; + + auto msg = std::make_unique(); + in.ReadUInt32(&msg->date); + in.ReadUInt64(&msg->query_id); + in6_addr myIP{}; + in.ReadBytes(reinterpret_cast(&myIP), 16); + uint32_t myPort; // int32_t in src; why not uint16_t? + in.ReadUInt32(&myPort); + msg->my_addr = rtc::SocketAddress(rtc::IPAddress(myIP), myPort); + return msg; + } + if (tlid == TLID_UDP_REFLECTOR_PEER_INFO) { + if (in.Length() < 16) + return nullptr; + auto msg = std::make_unique(); + uint32_t myAddr; + uint32_t myPort; + uint32_t peerAddr; + uint32_t peerPort; + in.ReadUInt32(&myAddr); + in.ReadUInt32(&myPort); + in.ReadUInt32(&peerAddr); + in.ReadUInt32(&peerPort); + msg->my_addr = rtc::SocketAddress(myAddr, myPort); + msg->peer_addr = rtc::SocketAddress(peerAddr, peerPort); + return msg; + } + return nullptr; +} + +void Layer92::KDF2(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); + crypto.sha256(buf, 16 + 36, sA); + memcpy(buf, encryptionKey + 40 + x, 36); + memcpy(buf + 36, msgKey, 16); + crypto.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); +} + +std::unique_ptr Layer92::DecodeProtocolPacket(rtc::ByteBufferReader& in) { + unsigned char msgKey[16]; + memcpy(msgKey, in.Data(), 16); + + unsigned char decrypted[1500]; + unsigned char aesKey[32], aesIv[32]; + KDF2(msgKey, isOutgoing ? 8 : 0, aesKey, aesIv); + size_t decryptedLen = in.Length() - 16; + if (decryptedLen > sizeof(decrypted)) + return nullptr; + if (decryptedLen % 16 != 0) + return nullptr; // wrong decrypted length + + in.Consume(16); + crypto.aes_ige_decrypt((uint8_t *)in.Data(), decrypted, decryptedLen, aesKey, aesIv); + in.Consume(decryptedLen); + + rtc::ByteBufferWriter buf; + size_t x = isOutgoing ? 8 : 0; + buf.WriteBytes((char *)encryptionKey + 88 + x, 32); + buf.WriteBytes((char *)decrypted, decryptedLen); + unsigned char msgKeyLarge[32]; + crypto.sha256((uint8_t *)buf.Data(), buf.Length(), msgKeyLarge); + if (memcmp(msgKey, msgKeyLarge + 8, 16) != 0) + return nullptr; // packet has wrong hash + + uint16_t innerLen; + memcpy(&innerLen, decrypted, 2); + if (innerLen > decryptedLen) + return nullptr; // packet has wrong inner length +// if (decryptedLen - innerLen < 16) +// return nullptr; // packet has too little padding + return protocol->ReadProtocolPacket(decrypted + 2, innerLen); +} + +std::unique_ptr Layer92::DecodePacket(rtc::ByteBufferReader& in) { + auto msg = DecodeRelayPacket(in); + if (msg) + return msg; + return DecodeProtocolPacket(in); +} + +rtc::Buffer Layer92::EncodePacket(const message::Base *msg_base) { + auto buf = EncodeRelayPacket(msg_base); + if (!buf.empty()) + return buf; + return EncodeProtocolPacket(msg_base); +} + +rtc::Buffer Layer92::EncodeRelayPacket(const message::Base *msg_base) { + if (msg_base->ID == message::tRelayPing) { + const auto *msg = dynamic_cast(msg_base); + if (!msg) + return rtc::Buffer(); + unsigned char buf[16]; + memset(buf, 0xFF, 16); + return rtc::Buffer(buf, 16); + } + if (msg_base->ID == message::tGetPeerInfo) { + const auto *msg = dynamic_cast(msg_base); + if (!msg) + return rtc::Buffer(); + rtc::ByteBufferWriter out; + out.WriteUInt32(-1); + out.WriteUInt32(-1); + out.WriteUInt32(-1); + out.WriteUInt32(-1); + int64_t id; + crypto.rand_bytes(reinterpret_cast(&id), 8); + out.WriteUInt64(id); + return rtc::Buffer(out.Data(), out.Length()); + } + return rtc::Buffer(); +} + +rtc::Buffer Layer92::EncodeProtocolPacket(const message::Base *msg_base) { + rtc::Buffer internal = protocol->WriteProtocolPacket(msg_base); + if (internal.empty()) + return rtc::Buffer(); + + rtc::ByteBufferWriter out; + rtc::ByteBufferWriter inner; + uint16_t len = internal.size(); + inner.WriteBytes((char *)&len, 2); // for backward compatibility + inner.WriteBytes((char *)internal.data(), internal.size()); + + size_t padLen = 16 - inner.Length() % 16; +// if (padLen < 16) +// padLen += 16; + uint8_t padding[32]; + crypto.rand_bytes(padding, padLen); + inner.WriteBytes((char *)padding, padLen); + assert(inner.Length() % 16 == 0); + + unsigned char key[32], iv[32], msgKey[16]; + rtc::ByteBufferWriter buf; + size_t x = isOutgoing ? 0 : 8; + buf.WriteBytes((char *)encryptionKey + 88 + x, 32); + buf.WriteBytes(inner.Data(), inner.Length()); + unsigned char msgKeyLarge[32]; + crypto.sha256((uint8_t *)buf.Data(), buf.Length(), msgKeyLarge); + memcpy(msgKey, msgKeyLarge + 8, 16); + KDF2(msgKey, isOutgoing ? 0 : 8, key, iv); + out.WriteBytes((char *)msgKey, 16); + + unsigned char aesOut[MSC_STACK_FALLBACK(inner.Length(), 1500)]; + crypto.aes_ige_encrypt((uint8_t *)inner.Data(), aesOut, inner.Length(), key, iv); + out.WriteBytes((char *)aesOut, inner.Length()); + return rtc::Buffer(out.Data(), out.Length()); +} diff --git a/submodules/TgVoipWebrtc/Impl/Layer92.h b/submodules/TgVoipWebrtc/Impl/Layer92.h new file mode 100644 index 0000000000..f751a95b12 --- /dev/null +++ b/submodules/TgVoipWebrtc/Impl/Layer92.h @@ -0,0 +1,49 @@ +#ifndef DEMO_LAYER92_H +#define DEMO_LAYER92_H + + +#include "LayerBase.h" +#include "Message.h" +#include "Protocol10.h" + +#include "rtc_base/byte_buffer.h" + +#include +#include + +struct CryptoFunctions { + void (*rand_bytes)(uint8_t* buffer, size_t length); + void (*sha1)(uint8_t* msg, size_t length, uint8_t* output); + void (*sha256)(uint8_t* msg, size_t length, uint8_t* output); + void (*aes_ige_encrypt)(uint8_t* in, uint8_t* out, size_t length, uint8_t* key, uint8_t* iv); + void (*aes_ige_decrypt)(uint8_t* in, uint8_t* out, size_t length, uint8_t* key, uint8_t* iv); + void (*aes_ctr_encrypt)(uint8_t* inout, size_t length, uint8_t* key, uint8_t* iv, uint8_t* ecount, uint32_t* num); + void (*aes_cbc_encrypt)(uint8_t* in, uint8_t* out, size_t length, uint8_t* key, uint8_t* iv); + void (*aes_cbc_decrypt)(uint8_t* in, uint8_t* out, size_t length, uint8_t* key, uint8_t* iv); +}; + +typedef unsigned char EncryptionKey[256]; + +class Layer92 : public LayerBase { +public: + static CryptoFunctions crypto; + + explicit Layer92(const EncryptionKey& encryptionKey, bool isOutgoing); + void EncryptForTCPO2(unsigned char *buffer, size_t len, TCPO2State *state) override; + void GenerateTCPO2States(unsigned char *buffer, TCPO2State *recvState, TCPO2State *sendState) override; + std::unique_ptr DecodePacket(rtc::ByteBufferReader& in) override; + rtc::Buffer EncodePacket(const message::Base *msg_base) override; + +private: + void KDF2(unsigned char* msgKey, size_t x, unsigned char *aesKey, unsigned char *aesIv); + std::unique_ptr DecodeRelayPacket(rtc::ByteBufferReader& in); + std::unique_ptr DecodeProtocolPacket(rtc::ByteBufferReader& in); + rtc::Buffer EncodeRelayPacket(const message::Base *msg_base); + rtc::Buffer EncodeProtocolPacket(const message::Base *msg_base); + + EncryptionKey encryptionKey; + bool isOutgoing; +}; + + +#endif //DEMO_LAYER92_H diff --git a/submodules/TgVoipWebrtc/Impl/LayerBase.cpp b/submodules/TgVoipWebrtc/Impl/LayerBase.cpp new file mode 100644 index 0000000000..9e6dbab457 --- /dev/null +++ b/submodules/TgVoipWebrtc/Impl/LayerBase.cpp @@ -0,0 +1,17 @@ +#include "LayerBase.h" + +#include "Layer92.h" + +bool LayerBase::ChangeProtocol(uint32_t protocol_version) { + if (protocol && protocol->version == protocol_version) + return true; + auto new_protocol = ProtocolBase::CreateProtocol(protocol_version); + if (!new_protocol) + return false; + protocol = std::move(new_protocol); + return true; +} + +LayerBase::LayerBase(uint32_t version) +: version(version) +, protocol(ProtocolBase::CreateProtocol(ProtocolBase::actual_version)) {} diff --git a/submodules/TgVoipWebrtc/Impl/LayerBase.h b/submodules/TgVoipWebrtc/Impl/LayerBase.h new file mode 100644 index 0000000000..5d2db961d4 --- /dev/null +++ b/submodules/TgVoipWebrtc/Impl/LayerBase.h @@ -0,0 +1,38 @@ +#ifndef DEMO_LAYERBASE_H +#define DEMO_LAYERBASE_H + + +#include "ProtocolBase.h" + +#include "rtc_base/buffer.h" +#include "rtc_base/byte_buffer.h" + +#include +#include + +struct TCPO2State { + unsigned char key[32]; + unsigned char iv[16]; + unsigned char ecount[16]; + uint32_t num; +}; + +class LayerBase { +public: + bool ChangeProtocol(uint32_t protocol_version); + + virtual ~LayerBase() = default; + virtual void EncryptForTCPO2(unsigned char *buffer, size_t len, TCPO2State *state) = 0; + virtual void GenerateTCPO2States(unsigned char* buffer, TCPO2State* recvState, TCPO2State* sendState) = 0; + virtual std::unique_ptr DecodePacket(rtc::ByteBufferReader& in) = 0; + virtual rtc::Buffer EncodePacket(const message::Base *msg_base) = 0; + + const uint32_t version; + +protected: + explicit LayerBase(uint32_t version); + + std::unique_ptr protocol; +}; + +#endif //DEMO_LAYERBASE_H diff --git a/submodules/TgVoipWebrtc/Impl/MediaEngineBase.h b/submodules/TgVoipWebrtc/Impl/MediaEngineBase.h new file mode 100644 index 0000000000..d85aa8c785 --- /dev/null +++ b/submodules/TgVoipWebrtc/Impl/MediaEngineBase.h @@ -0,0 +1,21 @@ +#ifndef DEMO_MEDIAENGINEBASE_H +#define DEMO_MEDIAENGINEBASE_H + + +#include "rtc_base/copy_on_write_buffer.h" +#include "rtc_base/third_party/sigslot/sigslot.h" + +#include + +class MediaEngineBase { +public: + MediaEngineBase() = default; + virtual ~MediaEngineBase() = default; + + sigslot::signal1 Send; + virtual void Receive(rtc::CopyOnWriteBuffer) = 0; + sigslot::signal2 Play; + sigslot::signal2 Record; +}; + +#endif //DEMO_MEDIAENGINEBASE_H diff --git a/submodules/TgVoipWebrtc/Impl/MediaEngineWebrtc.cpp b/submodules/TgVoipWebrtc/Impl/MediaEngineWebrtc.cpp new file mode 100644 index 0000000000..4b6651c9a3 --- /dev/null +++ b/submodules/TgVoipWebrtc/Impl/MediaEngineWebrtc.cpp @@ -0,0 +1,204 @@ +#include "MediaEngineWebrtc.h" + +#include "api/audio_codecs/audio_decoder_factory_template.h" +#include "api/audio_codecs/audio_encoder_factory_template.h" +#include "api/audio_codecs/opus/audio_decoder_opus.h" +#include "api/audio_codecs/opus/audio_encoder_opus.h" +#include "api/rtp_parameters.h" +#include "api/task_queue/default_task_queue_factory.h" +#include "media/base/codec.h" +#include "media/base/media_constants.h" +#include "media/engine/webrtc_media_engine.h" +#include "modules/audio_device/include/audio_device_default.h" +#include "rtc_base/task_utils/repeating_task.h" +#include "system_wrappers/include/field_trial.h" + +#if WEBRTC_ENABLE_PROTOBUF +#include "modules/audio_coding/audio_network_adaptor/config.pb.h" +#endif + +namespace { +const size_t frame_samples = 480; +const uint8_t channels = 1; +const uint8_t sample_bytes = 2; +const uint32_t clockrate = 48000; +const uint16_t sdp_payload = 111; +const char* sdp_name = "opus"; +const uint8_t sdp_channels = 2; +const uint32_t sdp_bitrate = 0; +const uint32_t caller_ssrc = 1; +const uint32_t called_ssrc = 2; +const int extension_sequence = 1; +} + +MediaEngineWebrtc::MediaEngineWebrtc(bool outgoing, bool send, bool recv) +: ssrc_send(outgoing ? caller_ssrc : called_ssrc) +, ssrc_recv(outgoing ? called_ssrc : caller_ssrc) +, event_log(std::make_unique()) +, task_queue_factory(webrtc::CreateDefaultTaskQueueFactory()) +, data_sender(*this) { + webrtc::field_trial::InitFieldTrialsFromString( + "WebRTC-Audio-SendSideBwe/Enabled/" + "WebRTC-Audio-Allocation/min:6kbps,max:32kbps/" + "WebRTC-Audio-OpusMinPacketLossRate/Enabled-1/" +// "WebRTC-Audio-OpusPlcUsePrevDecodedSamples/Enabled/" +// "WebRTC-Audio-NewOpusPacketLossRateOptimization/Enabled-1-20-1.0/" +// "WebRTC-SendSideBwe-WithOverhead/Enabled/" +// "WebRTC-Bwe-SeparateAudioPackets/enabled:true,packet_threshold:15,time_threshold:1000ms/" +// "WebRTC-Audio-AlrProbing/Disabled/" + ); + cricket::MediaEngineDependencies media_deps; + media_deps.task_queue_factory = task_queue_factory.get(); +#ifdef TGVOIP_USE_CALLBACK_AUDIO_IO + media_deps.adm = new rtc::RefCountedObject>(); +#endif + media_deps.audio_encoder_factory = webrtc::CreateAudioEncoderFactory(); + media_deps.audio_decoder_factory = webrtc::CreateAudioDecoderFactory(); + media_deps.audio_processing = webrtc::AudioProcessingBuilder().Create(); + media_engine = cricket::CreateMediaEngine(std::move(media_deps)); + media_engine->Init(); + webrtc::Call::Config call_config(event_log.get()); + call_config.task_queue_factory = task_queue_factory.get(); + call_config.trials = &field_trials; + call_config.audio_state = media_engine->voice().GetAudioState(); + call.reset(webrtc::Call::Create(call_config)); +#ifdef TGVOIP_USE_CALLBACK_AUDIO_IO + audio_processor = std::make_unique(call_config.audio_state->audio_transport(), + task_queue_factory.get(), *this, send, recv); +#endif + voice_channel.reset(media_engine->voice().CreateMediaChannel( + call.get(), cricket::MediaConfig(), cricket::AudioOptions(), webrtc::CryptoOptions::NoGcm())); + if (send) { + voice_channel->AddSendStream(cricket::StreamParams::CreateLegacy(ssrc_send)); + SetNetworkParams({6, 32, 6, 120, false, false, false}); + SetMute(false); + voice_channel->SetInterface(&data_sender, webrtc::MediaTransportConfig()); + voice_channel->OnReadyToSend(true); + voice_channel->SetSend(true); + } + if (recv) { + cricket::AudioRecvParameters recv_parameters; + recv_parameters.codecs.emplace_back(sdp_payload, sdp_name, clockrate, sdp_bitrate, sdp_channels); + recv_parameters.extensions.emplace_back(webrtc::RtpExtension::kTransportSequenceNumberUri, extension_sequence); + recv_parameters.rtcp.reduced_size = true; + recv_parameters.rtcp.remote_estimate = true; + voice_channel->AddRecvStream(cricket::StreamParams::CreateLegacy(ssrc_recv)); + voice_channel->SetRecvParameters(recv_parameters); + voice_channel->SetPlayout(true); + } +} + +MediaEngineWebrtc::~MediaEngineWebrtc() = default; + +void MediaEngineWebrtc::Receive(rtc::CopyOnWriteBuffer packet) { + if (voice_channel) + voice_channel->OnPacketReceived(packet, -1); +} + +void MediaEngineWebrtc::OnSentPacket(const rtc::SentPacket& sent_packet) { + call->OnSentPacket(sent_packet); +} + +void MediaEngineWebrtc::SetNetworkParams(const MediaEngineWebrtc::NetworkParams& params) { + cricket::AudioCodec opus_codec(sdp_payload, sdp_name, clockrate, sdp_bitrate, sdp_channels); + opus_codec.AddFeedbackParam(cricket::FeedbackParam(cricket::kRtcpFbParamTransportCc)); + opus_codec.SetParam(cricket::kCodecParamMinBitrate, params.min_bitrate_kbps); + opus_codec.SetParam(cricket::kCodecParamStartBitrate, params.start_bitrate_kbps); + opus_codec.SetParam(cricket::kCodecParamMaxBitrate, params.max_bitrate_kbps); + opus_codec.SetParam(cricket::kCodecParamUseInbandFec, 1); + opus_codec.SetParam(cricket::kCodecParamPTime, params.ptime_ms); +// opus_codec.SetParam(cricket::kCodecParamUseDtx, "1"); +// opus_codec.SetParam(cricket::kCodecParamMaxAverageBitrate, 6); + std::string config_string; +#if WEBRTC_ENABLE_PROTOBUF + webrtc::audio_network_adaptor::config::ControllerManager cont_conf; +// cont_conf.add_controllers()->mutable_bitrate_controller(); + config_string = cont_conf.SerializeAsString(); +#endif + cricket::AudioSendParameters send_parameters; + if (!config_string.empty()) { + send_parameters.options.audio_network_adaptor_config = config_string; + send_parameters.options.audio_network_adaptor = true; + } + send_parameters.codecs.push_back(opus_codec); + send_parameters.extensions.emplace_back(webrtc::RtpExtension::kTransportSequenceNumberUri, extension_sequence); + send_parameters.options.echo_cancellation = params.echo_cancellation; +// send_parameters.options.experimental_ns = false; + send_parameters.options.noise_suppression = params.noise_suppression; + send_parameters.options.auto_gain_control = params.auto_gain_control; + send_parameters.options.highpass_filter = false; + send_parameters.options.typing_detection = false; +// send_parameters.max_bandwidth_bps = 16000; + send_parameters.rtcp.reduced_size = true; + send_parameters.rtcp.remote_estimate = true; + voice_channel->SetSendParameters(send_parameters); +} + +void MediaEngineWebrtc::SetMute(bool mute) { + voice_channel->SetAudioSend(ssrc_send, !mute, nullptr, &audio_source); +} + +bool MediaEngineWebrtc::Sender::SendPacket(rtc::CopyOnWriteBuffer *packet, const rtc::PacketOptions& options) { + engine.Send(*packet); + rtc::SentPacket sent_packet(options.packet_id, rtc::TimeMillis(), options.info_signaled_after_sent); + engine.OnSentPacket(sent_packet); + return true; +} + +bool MediaEngineWebrtc::Sender::SendRtcp(rtc::CopyOnWriteBuffer *packet, const rtc::PacketOptions& options) { + engine.Send(*packet); + rtc::SentPacket sent_packet(options.packet_id, rtc::TimeMillis(), options.info_signaled_after_sent); + engine.OnSentPacket(sent_packet); + return true; +} + +int MediaEngineWebrtc::Sender::SetOption(cricket::MediaChannel::NetworkInterface::SocketType, rtc::Socket::Option, int) { + return -1; // in general, the result is not important yet +} + +MediaEngineWebrtc::Sender::Sender(MediaEngineWebrtc& engine) : engine(engine) {} + +MediaEngineWebrtc::AudioProcessor::AudioProcessor(webrtc::AudioTransport *transport_, + webrtc::TaskQueueFactory *task_queue_factory, MediaEngineBase& engine_, bool send_, bool recv_) + : send(send_) + , recv(recv_) + , transport(transport_) + , delay_us(frame_samples * 1000000 / clockrate) + , buf_send(nullptr) + , buf_recv(nullptr) + , engine(engine_) + , task_queue_send(std::make_unique(task_queue_factory->CreateTaskQueue( + "AudioProcessorSend", webrtc::TaskQueueFactory::Priority::NORMAL))) + , task_queue_recv(std::make_unique(task_queue_factory->CreateTaskQueue( + "AudioProcessorRecv", webrtc::TaskQueueFactory::Priority::NORMAL))) { + if (send) { + buf_send = new int16_t[frame_samples * channels]; + webrtc::RepeatingTaskHandle::Start(task_queue_send->Get(), [this]() { + static uint32_t new_mic_level = 0; + memset(buf_send, 0, frame_samples * channels * sample_bytes); + engine.Record(buf_send, frame_samples * channels); + transport->RecordedDataIsAvailable(buf_send, frame_samples, sample_bytes, channels, clockrate, + 0, 0, 0, false, new_mic_level); + return webrtc::TimeDelta::us(delay_us); + }); + } + if (recv) { + buf_recv = new int16_t[frame_samples * channels]; + webrtc::RepeatingTaskHandle::Start(task_queue_recv->Get(), [this]() { + static int64_t elapsed_time_ms = -1; + static int64_t ntp_time_ms = -1; + size_t samples_out = 0; + transport->NeedMorePlayData(frame_samples, sample_bytes, channels, clockrate, buf_recv, + samples_out, &elapsed_time_ms, &ntp_time_ms); + engine.Play(buf_recv, samples_out * channels); + return webrtc::TimeDelta::us(delay_us); + }); + } +} + +MediaEngineWebrtc::AudioProcessor::~AudioProcessor() { + task_queue_send = nullptr; + task_queue_recv = nullptr; + delete[] buf_send; + delete[] buf_recv; +} diff --git a/submodules/TgVoipWebrtc/Impl/MediaEngineWebrtc.h b/submodules/TgVoipWebrtc/Impl/MediaEngineWebrtc.h new file mode 100644 index 0000000000..f6332d9f83 --- /dev/null +++ b/submodules/TgVoipWebrtc/Impl/MediaEngineWebrtc.h @@ -0,0 +1,78 @@ +#ifndef DEMO_MEDIAENGINEWEBRTC_H +#define DEMO_MEDIAENGINEWEBRTC_H + + +#include "MediaEngineBase.h" + +#include "api/transport/field_trial_based_config.h" +#include "call/call.h" +#include "media/base/media_engine.h" +#include "pc/rtp_sender.h" +#include "rtc_base/task_queue.h" + +#include + +class MediaEngineWebrtc : public MediaEngineBase { +public: + struct NetworkParams { + uint8_t min_bitrate_kbps; + uint8_t max_bitrate_kbps; + uint8_t start_bitrate_kbps; + uint8_t ptime_ms; + bool echo_cancellation; + bool auto_gain_control; + bool noise_suppression; + }; + + explicit MediaEngineWebrtc(bool outgoing, bool send = true, bool recv = true); + ~MediaEngineWebrtc() override; + void Receive(rtc::CopyOnWriteBuffer) override; + void OnSentPacket(const rtc::SentPacket& sent_packet); + void SetNetworkParams(const NetworkParams& params); + void SetMute(bool mute); + +private: + class Sender final : public cricket::MediaChannel::NetworkInterface { + public: + explicit Sender(MediaEngineWebrtc&); + bool SendPacket(rtc::CopyOnWriteBuffer *packet, const rtc::PacketOptions& options) override; + bool SendRtcp(rtc::CopyOnWriteBuffer *packet, const rtc::PacketOptions& options) override; + int SetOption(SocketType type, rtc::Socket::Option opt, int option) override; + private: + MediaEngineWebrtc& engine; + }; + + class AudioProcessor { + public: + AudioProcessor(webrtc::AudioTransport *transport, webrtc::TaskQueueFactory *task_queue_factory, + MediaEngineBase& engine, bool send, bool recv); + ~AudioProcessor(); + private: + bool send; + bool recv; + webrtc::AudioTransport *transport; + size_t delay_us; + int16_t *buf_send; + int16_t *buf_recv; + MediaEngineBase& engine; + std::unique_ptr task_queue_send; + std::unique_ptr task_queue_recv; + }; + + const uint32_t ssrc_send; + const uint32_t ssrc_recv; + std::unique_ptr call; + std::unique_ptr media_engine; + std::unique_ptr event_log; + std::unique_ptr task_queue_factory; + webrtc::FieldTrialBasedConfig field_trials; + webrtc::LocalAudioSinkAdapter audio_source; + Sender data_sender; + std::unique_ptr voice_channel; +#ifdef TGVOIP_USE_CALLBACK_AUDIO_IO + std::unique_ptr audio_processor; +#endif +}; + + +#endif //DEMO_MEDIAENGINEWEBRTC_H diff --git a/submodules/TgVoipWebrtc/Impl/Message.h b/submodules/TgVoipWebrtc/Impl/Message.h new file mode 100644 index 0000000000..9d9f62e4ca --- /dev/null +++ b/submodules/TgVoipWebrtc/Impl/Message.h @@ -0,0 +1,134 @@ +#ifndef DEMO_MESSAGE_H +#define DEMO_MESSAGE_H + +#include "rtc_base/copy_on_write_buffer.h" +#include "rtc_base/socket_address.h" + +namespace message { + +enum Type { + tUnknown, + tReady, + tConnected, + tDisconnected, + tRelayPing, + tRelayPong, + tGetPeerInfo, + tPeerInfo, + tSelfIPv6, + tSelfLocalIP, + tInit, + tInitAck, + tPing, + tPong, + tBufferOverflow, + tPacketIncorrect, + tWrongProtocol, + tRtpStream, +}; + +enum NetworkType { + nGprs, + nEdge, + n3gOrAbove, + nHighSpeed, + nUnknown, +}; + +struct Base { + virtual ~Base() = default; + explicit Base(Type ID) : ID(ID) {} + const Type ID; +}; + +struct Unknown : Base { + Unknown() : Base(Type::tUnknown) {} +}; + +struct Ready : Base { + Ready() : Base(Type::tReady) {} +}; + +struct Connected : Base { + Connected() : Base(Type::tConnected) {} +}; + +struct Disconnected : Base { + Disconnected() : Base(Type::tDisconnected) {} +}; + +struct RelayPing : Base { + RelayPing() : Base(Type::tRelayPing) {} +}; + +struct RelayPong : Base { + RelayPong() : Base(Type::tRelayPong) {} + uint32_t date{}; // int32_t in src + uint64_t query_id{}; //int64_t in src + rtc::SocketAddress my_addr; +}; + +struct GetPeerInfo : Base { + GetPeerInfo() : Base(Type::tGetPeerInfo) {} +}; + +struct PeerInfo : Base { + PeerInfo() : Base(Type::tPeerInfo) {} + rtc::SocketAddress my_addr; + rtc::SocketAddress peer_addr; +}; + +struct SelfIPv6 : Base { + SelfIPv6() : Base(Type::tSelfIPv6) {} + rtc::SocketAddress my_addr; +}; + +struct SelfLocalIP : Base { + SelfLocalIP() : Base(Type::tSelfLocalIP) {} +}; + +struct Init : Base { + Init() : Base(Type::tInit) {} + uint32_t ver{}; + uint32_t minVer{}; + uint32_t flags{}; +}; + +struct InitAck : Base { + InitAck() : Base(Type::tInitAck) {} + uint32_t ver{}; + uint32_t minVer{}; +}; + +struct Ping : Base { + Ping() : Base(Type::tPing) {} + uint32_t id{}; +}; + +struct Pong : Base { + Pong() : Base(Type::tPong) {} + uint32_t id{}; +}; + +struct BufferOverflow : Base { + BufferOverflow() : Base(Type::tBufferOverflow) {} +}; + +struct PacketIncorrect : Base { + PacketIncorrect() : Base(Type::tPacketIncorrect) {} +}; + +struct WrongProtocol : Base { + WrongProtocol() : Base(Type::tWrongProtocol) {} +}; + +struct RtpStream : Base { + RtpStream() : Base(Type::tRtpStream) {} + bool data_saving{false}; + NetworkType network_type{NetworkType::nUnknown}; + rtc::CopyOnWriteBuffer data; +}; + +} + +#endif //DEMO_MESSAGE_H diff --git a/submodules/TgVoipWebrtc/Impl/Protocol10.cpp b/submodules/TgVoipWebrtc/Impl/Protocol10.cpp new file mode 100644 index 0000000000..05d47da952 --- /dev/null +++ b/submodules/TgVoipWebrtc/Impl/Protocol10.cpp @@ -0,0 +1,159 @@ +#include "Protocol10.h" + +#include "rtc_base/byte_buffer.h" + +#include +#include + +const std::map Protocol10::decoders = { + {Protocol10::PacketType::tInit, Protocol10::InitDecode}, // back compatibility + {Protocol10::PacketType::tInitAck, Protocol10::InitAckDecode}, // back compatibility + {Protocol10::PacketType::tRtpStream, Protocol10::RtpStreamDecode}, + {Protocol10::PacketType::tPing, Protocol10::PingDecode}, + {Protocol10::PacketType::tPong, Protocol10::PongDecode}, +}; + +const std::map Protocol10::encoders = { + {message::tInit, Protocol10::InitEncode}, + {message::tInitAck, Protocol10::InitAckEncode}, + {message::tRtpStream, Protocol10::RtpStreamEncode}, + {message::tPing, Protocol10::PingEncode}, + {message::tPong, Protocol10::PongEncode}, +}; + +Protocol10::Protocol10() : ProtocolBase(10) {} + +std::unique_ptr Protocol10::ReadProtocolPacket(const uint8_t *buffer, size_t size) { + uint8_t type = buffer[0]; + auto deserializer = decoders.find(type); + if (deserializer == decoders.end()) + return nullptr; + return deserializer->second(buffer + 1, size - 1); +} + +rtc::Buffer Protocol10::WriteProtocolPacket(const message::Base *msg) { + auto serializer = encoders.find(msg->ID); + if (serializer == encoders.end()) + return rtc::Buffer(); + return serializer->second(msg); +} + +rtc::Buffer Protocol10::InitEncode(const message::Base *msg_base) { + const auto *msg = dynamic_cast(msg_base); + if (!msg) + return rtc::Buffer(); + rtc::ByteBufferWriter out; + out.WriteUInt8(PacketType::tInit); + out.Resize(14); + out.WriteUInt32(rtc::NetworkToHost32(msg->ver)); + out.WriteUInt32(rtc::NetworkToHost32(msg->minVer)); + out.WriteUInt32(rtc::NetworkToHost32(msg->flags)); + return rtc::Buffer(out.Data(), out.Length()); +} + +std::unique_ptr Protocol10::InitDecode(const uint8_t *buffer, size_t size) { + rtc::ByteBufferReader in(reinterpret_cast(buffer), size); + uint32_t ackId = 0, pseq = 0, acks = 0; + unsigned char pflags = 0; + in.ReadUInt32(&ackId); + in.ReadUInt32(&pseq); + in.ReadUInt32(&acks); + in.ReadUInt8(&pflags); + auto msg = std::make_unique(); + in.ReadUInt32(&msg->ver); + in.ReadUInt32(&msg->minVer); + in.ReadUInt32(&msg->flags); + msg->ver = rtc::HostToNetwork32(msg->ver); + msg->minVer = rtc::HostToNetwork32(msg->minVer); + msg->flags = rtc::HostToNetwork32(msg->flags); + if (ProtocolBase::IsSupported(msg->ver)) + return msg; + // TODO: support matching of lower supported versions + return std::make_unique(); +} + +rtc::Buffer Protocol10::InitAckEncode(const message::Base *msg_base) { + const auto *msg = dynamic_cast(msg_base); + if (!msg) + return rtc::Buffer(); + rtc::ByteBufferWriter out; + out.WriteUInt8(PacketType::tInitAck); + out.Resize(14); + out.WriteUInt32(rtc::NetworkToHost32(msg->ver)); + out.WriteUInt32(rtc::NetworkToHost32(msg->minVer)); + return rtc::Buffer(out.Data(), out.Length()); +} + +std::unique_ptr Protocol10::InitAckDecode(const uint8_t *buffer, size_t size) { + rtc::ByteBufferReader in(reinterpret_cast(buffer), size); + uint32_t ackId = 0, pseq = 0, acks = 0; + unsigned char pflags = 0; + in.ReadUInt32(&ackId); + in.ReadUInt32(&pseq); + in.ReadUInt32(&acks); + in.ReadUInt8(&pflags); + auto msg = std::make_unique(); + in.ReadUInt32(&msg->ver); + in.ReadUInt32(&msg->minVer); + msg->ver = rtc::HostToNetwork32(msg->ver); + msg->minVer = rtc::HostToNetwork32(msg->minVer); + if (ProtocolBase::IsSupported(msg->ver)) + return msg; + // TODO: support matching of lower supported versions + return std::make_unique(); +} + +rtc::Buffer Protocol10::RtpStreamEncode(const message::Base *msg_base) { + const auto *msg = dynamic_cast(msg_base); + if (!msg) + return rtc::Buffer(); + rtc::ByteBufferWriter out; + out.WriteUInt8(PacketType::tRtpStream); + uint8_t meta = (msg->network_type & 0b111) | (msg->data_saving << 3); + out.WriteUInt8(meta); + out.WriteBytes(reinterpret_cast(msg->data.data()), msg->data.size()); + return rtc::Buffer(out.Data(), out.Length()); +} + +std::unique_ptr Protocol10::RtpStreamDecode(const uint8_t *buffer, size_t size) { + auto msg = std::make_unique(); + uint8_t meta = buffer[0]; + msg->network_type = (message::NetworkType) (meta & 0b111); + msg->data_saving = (meta >> 3) & 0b1; + msg->data = rtc::CopyOnWriteBuffer(buffer + 1, size - 1); + return msg; +} + +rtc::Buffer Protocol10::PingEncode(const message::Base *msg_base) { + const auto *msg = dynamic_cast(msg_base); + if (!msg) + return rtc::Buffer(); + rtc::ByteBufferWriter out; + out.WriteUInt8(PacketType::tPing); + out.WriteUInt32(msg->id); + return rtc::Buffer(out.Data(), out.Length()); +} + +std::unique_ptr Protocol10::PingDecode(const uint8_t *buffer, size_t size) { + rtc::ByteBufferReader in(reinterpret_cast(buffer), size); + auto msg = std::make_unique(); + in.ReadUInt32(&msg->id); + return msg; +} + +rtc::Buffer Protocol10::PongEncode(const message::Base *msg_base) { + const auto *msg = dynamic_cast(msg_base); + if (!msg) + return rtc::Buffer(); + rtc::ByteBufferWriter out; + out.WriteUInt8(PacketType::tPong); + out.WriteUInt32(msg->id); + return rtc::Buffer(out.Data(), out.Length()); +} + +std::unique_ptr Protocol10::PongDecode(const uint8_t *buffer, size_t size) { + rtc::ByteBufferReader in(reinterpret_cast(buffer), size); + auto msg = std::make_unique(); + in.ReadUInt32(&msg->id); + return msg; +} diff --git a/submodules/TgVoipWebrtc/Impl/Protocol10.h b/submodules/TgVoipWebrtc/Impl/Protocol10.h new file mode 100644 index 0000000000..6ba7a6e4de --- /dev/null +++ b/submodules/TgVoipWebrtc/Impl/Protocol10.h @@ -0,0 +1,44 @@ +#ifndef DEMO_PROTOCOL10_H +#define DEMO_PROTOCOL10_H + + +#include "Message.h" +#include "ProtocolBase.h" + +#include + +class Protocol10 : public ProtocolBase { +public: + enum PacketType { + tInit = 1, + tInitAck, + tRtpStream, + tPing, + tPong, + }; + + Protocol10(); + std::unique_ptr ReadProtocolPacket(const uint8_t *buffer, size_t size) override; + rtc::Buffer WriteProtocolPacket(const message::Base *msg) override; + +private: + typedef std::function(const uint8_t *, size_t)> Deserializer; + typedef std::function Serializer; + + static const std::map decoders; + static const std::map encoders; + + static rtc::Buffer InitEncode(const message::Base *msg_base); + static std::unique_ptr InitDecode(const uint8_t *buffer, size_t size); + static rtc::Buffer InitAckEncode(const message::Base *msg_base); + static std::unique_ptr InitAckDecode(const uint8_t *buffer, size_t size); + static rtc::Buffer RtpStreamEncode(const message::Base *msg_base); + static std::unique_ptr RtpStreamDecode(const uint8_t *buffer, size_t size); + static rtc::Buffer PingEncode(const message::Base *msg_base); + static std::unique_ptr PingDecode(const uint8_t *buffer, size_t size); + static rtc::Buffer PongEncode(const message::Base *msg_base); + static std::unique_ptr PongDecode(const uint8_t *buffer, size_t size); +}; + + +#endif //DEMO_PROTOCOL10_H diff --git a/submodules/TgVoipWebrtc/Impl/ProtocolBase.cpp b/submodules/TgVoipWebrtc/Impl/ProtocolBase.cpp new file mode 100644 index 0000000000..38476d4d4b --- /dev/null +++ b/submodules/TgVoipWebrtc/Impl/ProtocolBase.cpp @@ -0,0 +1,23 @@ +#include "ProtocolBase.h" + +#include "Protocol10.h" + +const std::map ProtocolBase::constructors = { + {10, std::make_unique}, +}; + +const uint32_t ProtocolBase::actual_version = 10; +const uint32_t ProtocolBase::minimal_version = 10; + +std::unique_ptr ProtocolBase::CreateProtocol(uint32_t version) { + auto protocol = constructors.find(version); + if (protocol == constructors.end()) + return nullptr; + return protocol->second(); +} + +bool ProtocolBase::IsSupported(uint32_t version) { + return constructors.find(version) != constructors.end(); +} + +ProtocolBase::ProtocolBase(uint32_t version) : version(version) {} diff --git a/submodules/TgVoipWebrtc/Impl/ProtocolBase.h b/submodules/TgVoipWebrtc/Impl/ProtocolBase.h new file mode 100644 index 0000000000..e15610b0f6 --- /dev/null +++ b/submodules/TgVoipWebrtc/Impl/ProtocolBase.h @@ -0,0 +1,33 @@ +#ifndef DEMO_PROTOCOLBASE_H +#define DEMO_PROTOCOLBASE_H + + +#include "Message.h" + +#include +#include +#include + +class ProtocolBase { +public: + static const uint32_t actual_version; + static const uint32_t minimal_version; + static std::unique_ptr CreateProtocol(uint32_t version); + static bool IsSupported(uint32_t version); + + virtual ~ProtocolBase() = default; + virtual std::unique_ptr ReadProtocolPacket(const uint8_t *buffer, size_t size) = 0; + virtual rtc::Buffer WriteProtocolPacket(const message::Base *msg) = 0; + + const uint32_t version; + +protected: + explicit ProtocolBase(uint32_t version); + +private: + typedef std::function()> Constructor; + static const std::map constructors; +}; + + +#endif //DEMO_PROTOCOLBASE_H diff --git a/submodules/TgVoipWebrtc/Impl/TgVoip.cpp b/submodules/TgVoipWebrtc/Impl/TgVoip.cpp new file mode 100644 index 0000000000..466fc5291a --- /dev/null +++ b/submodules/TgVoipWebrtc/Impl/TgVoip.cpp @@ -0,0 +1,406 @@ +#include + +#include "TgVoip.h" + +#include "Controller.h" +#include "Layer92.h" +#include "Message.h" + +#include +#include + +#ifndef TGVOIP_USE_CUSTOM_CRYPTO +extern "C" { +#include +#include +#include +#include +#include +} + +void tgvoip_openssl_aes_ige_encrypt(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); +} + +void tgvoip_openssl_aes_ige_decrypt(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); +} + +void tgvoip_openssl_rand_bytes(uint8_t* buffer, size_t len){ + RAND_bytes(buffer, len); +} + +void tgvoip_openssl_sha1(uint8_t* msg, size_t len, uint8_t* output){ + SHA1(msg, len, output); +} + +void tgvoip_openssl_sha256(uint8_t* msg, size_t len, uint8_t* output){ + SHA256(msg, len, output); +} + +void tgvoip_openssl_aes_ctr_encrypt(uint8_t* inout, size_t length, uint8_t* key, uint8_t* iv, uint8_t* ecount, uint32_t* num){ + AES_KEY akey; + AES_set_encrypt_key(key, 32*8, &akey); + CRYPTO_ctr128_encrypt(inout, inout, length, &akey, iv, ecount, num, (block128_f) AES_encrypt); +} + +void tgvoip_openssl_aes_cbc_encrypt(uint8_t* in, uint8_t* out, size_t length, uint8_t* key, uint8_t* iv){ + AES_KEY akey; + AES_set_encrypt_key(key, 256, &akey); + AES_cbc_encrypt(in, out, length, &akey, iv, AES_ENCRYPT); +} + +void tgvoip_openssl_aes_cbc_decrypt(uint8_t* in, uint8_t* out, size_t length, uint8_t* key, uint8_t* iv){ + AES_KEY akey; + AES_set_decrypt_key(key, 256, &akey); + AES_cbc_encrypt(in, out, length, &akey, iv, AES_DECRYPT); +} + +const char * openssl_version() { + return SSLeay_version(SSLEAY_VERSION); +} + +CryptoFunctions Layer92::crypto={ + tgvoip_openssl_rand_bytes, + tgvoip_openssl_sha1, + tgvoip_openssl_sha256, + tgvoip_openssl_aes_ige_encrypt, + tgvoip_openssl_aes_ige_decrypt, + tgvoip_openssl_aes_ctr_encrypt, + tgvoip_openssl_aes_cbc_encrypt, + tgvoip_openssl_aes_cbc_decrypt +}; +#endif + + +#ifdef TGVOIP_NAMESPACE +namespace TGVOIP_NAMESPACE { +#endif + +class TgVoipImpl : public TgVoip, public sigslot::has_slots<> { +public: + TgVoipImpl( + std::vector const &endpoints, + TgVoipPersistentState const &persistentState, + std::unique_ptr const &proxy, + TgVoipConfig const &config, + TgVoipEncryptionKey const &encryptionKey, + TgVoipNetworkType initialNetworkType +#ifdef TGVOIP_USE_CUSTOM_CRYPTO + , + TgVoipCrypto const &crypto +#endif +#ifdef TGVOIP_USE_CALLBACK_AUDIO_IO + , + TgVoipAudioDataCallbacks const &audioDataCallbacks +#endif + ) { +#ifdef TGVOIP_USE_CUSTOM_CRYPTO + tgvoip::VoIPController::crypto.sha1 = crypto.sha1; + tgvoip::VoIPController::crypto.sha256 = crypto.sha256; + tgvoip::VoIPController::crypto.rand_bytes = crypto.rand_bytes; + tgvoip::VoIPController::crypto.aes_ige_encrypt = crypto.aes_ige_encrypt; + tgvoip::VoIPController::crypto.aes_ige_decrypt = crypto.aes_ige_decrypt; + tgvoip::VoIPController::crypto.aes_ctr_encrypt = crypto.aes_ctr_encrypt; +#endif + +// std::cerr << "OpenSSL version: " << openssl_version() << std::endl; // to verify because of WebRTC BoringSSL + + EncryptionKey encryptionKeyValue; + memcpy(encryptionKeyValue, encryptionKey.value.data(), 256); + controller_ = new Controller(encryptionKey.isOutgoing, encryptionKeyValue, 5, 3); + +#ifdef TGVOIP_USE_CALLBACK_AUDIO_IO + audioCallbacks = audioDataCallbacks; + controller_->SignalRecord.connect(this, &TgVoipImpl::record); + controller_->SignalPlay.connect(this, &TgVoipImpl::play); +#ifdef TGVOIP_PREPROCESSED_OUTPUT + controller_->SignalPreprocessed.connect(this, &TgVoipImpl::preprocessed); +#endif +#endif + + if (proxy != nullptr) { + controller_->SetProxy(rtc::ProxyType::PROXY_SOCKS5, rtc::SocketAddress(proxy->host, proxy->port), + proxy->login, proxy->password); + } + + controller_->SignalNewState.connect(this, &TgVoipImpl::controllerStateCallback); + controller_->Start(); + + for (const auto &endpoint : endpoints) { + rtc::SocketAddress addr(endpoint.host.ipv4, endpoint.port); + Controller::EndpointType type; + switch (endpoint.type) { + case TgVoipEndpointType::UdpRelay: + type = Controller::EndpointType::UDP; + break; + case TgVoipEndpointType::Lan: + case TgVoipEndpointType::Inet: + type = Controller::EndpointType::P2P; + break; + case TgVoipEndpointType::TcpRelay: + type = Controller::EndpointType::TCP; + break; + default: + type = Controller::EndpointType::UDP; + break; + } + controller_->AddEndpoint(addr, endpoint.peerTag, type); + } + + setNetworkType(initialNetworkType); + + switch (config.dataSaving) { + case TgVoipDataSaving::Mobile: + controller_->SetDataSaving(true); + break; + case TgVoipDataSaving::Always: + controller_->SetDataSaving(true); + break; + default: + controller_->SetDataSaving(false); + break; + } + } + + ~TgVoipImpl() override { + stop(); + } + + void setOnStateUpdated(std::function onStateUpdated) override { + std::lock_guard lock(m_onStateUpdated); + onStateUpdated_ = onStateUpdated; + } + + void setOnSignalBarsUpdated(std::function onSignalBarsUpdated) override { + std::lock_guard lock(m_onSignalBarsUpdated); + onSignalBarsUpdated_ = onSignalBarsUpdated; + } + + void setNetworkType(TgVoipNetworkType networkType) override { + message::NetworkType mappedType; + + switch (networkType) { + case TgVoipNetworkType::Unknown: + mappedType = message::NetworkType::nUnknown; + break; + case TgVoipNetworkType::Gprs: + mappedType = message::NetworkType::nGprs; + break; + case TgVoipNetworkType::Edge: + mappedType = message::NetworkType::nEdge; + break; + case TgVoipNetworkType::ThirdGeneration: + mappedType = message::NetworkType::n3gOrAbove; + break; + case TgVoipNetworkType::Hspa: + mappedType = message::NetworkType::n3gOrAbove; + break; + case TgVoipNetworkType::Lte: + mappedType = message::NetworkType::n3gOrAbove; + break; + case TgVoipNetworkType::WiFi: + mappedType = message::NetworkType::nHighSpeed; + break; + case TgVoipNetworkType::Ethernet: + mappedType = message::NetworkType::nHighSpeed; + break; + case TgVoipNetworkType::OtherHighSpeed: + mappedType = message::NetworkType::nHighSpeed; + break; + case TgVoipNetworkType::OtherLowSpeed: + mappedType = message::NetworkType::nEdge; + break; + case TgVoipNetworkType::OtherMobile: + mappedType = message::NetworkType::n3gOrAbove; + break; + case TgVoipNetworkType::Dialup: + mappedType = message::NetworkType::nGprs; + break; + default: + mappedType = message::NetworkType::nUnknown; + break; + } + + controller_->SetNetworkType(mappedType); + } + + void setMuteMicrophone(bool muteMicrophone) override { + controller_->SetMute(muteMicrophone); + } + + void setAudioOutputGainControlEnabled(bool enabled) override { + } + + void setEchoCancellationStrength(int strength) override { + } + + std::string getLastError() override { + return ""; // TODO: not implemented + } + + std::string getDebugInfo() override { + return ""; // TODO: not implemented + } + + int64_t getPreferredRelayId() override { + return 0; // we don't have endpoint ids + } + + TgVoipTrafficStats getTrafficStats() override { + return TgVoipTrafficStats{}; // TODO: not implemented + } + + TgVoipPersistentState getPersistentState() override { + return TgVoipPersistentState{}; // we dont't have such information + } + + TgVoipFinalState stop() override { + TgVoipFinalState finalState = { + }; + + delete controller_; + controller_ = nullptr; + + return finalState; + } + + void controllerStateCallback(Controller::State state) { + if (onStateUpdated_) { + TgVoipState mappedState; + switch (state) { + case Controller::State::WaitInit: + mappedState = TgVoipState::WaitInit; + break; + case Controller::State::WaitInitAck: + mappedState = TgVoipState::WaitInitAck; + break; + case Controller::State::Established: + mappedState = TgVoipState::Estabilished; + break; + case Controller::State::Failed: + mappedState = TgVoipState::Failed; + break; + case Controller::State::Reconnecting: + mappedState = TgVoipState::Reconnecting; + break; + default: + mappedState = TgVoipState::Estabilished; + break; + } + + onStateUpdated_(mappedState); + } + } + +private: +#ifdef TGVOIP_USE_CALLBACK_AUDIO_IO + TgVoipAudioDataCallbacks audioCallbacks; + + void play(const int16_t *data, size_t size) { + if (!audioCallbacks.output) + return; + int16_t buf[size]; + memcpy(buf, data, size * 2); + audioCallbacks.output(buf, size); + } + + void record(int16_t *data, size_t size) { + if (audioCallbacks.input) + audioCallbacks.input(data, size); + } + + void preprocessed(const int16_t *data, size_t size) { + if (!audioCallbacks.preprocessed) + return; + int16_t buf[size]; + memcpy(buf, data, size * 2); + audioCallbacks.preprocessed(buf, size); + } +#endif + +private: + Controller *controller_; + std::function onStateUpdated_; + std::function onSignalBarsUpdated_; + std::mutex m_onStateUpdated, m_onSignalBarsUpdated; +}; + +std::function globalLoggingFunction; + +void __tgvoip_call_tglog(const char *format, ...){ + va_list vaArgs; + va_start(vaArgs, format); + + va_list vaCopy; + va_copy(vaCopy, vaArgs); + const int length = std::vsnprintf(nullptr, 0, format, vaCopy); + va_end(vaCopy); + + std::vector zc(length + 1); + std::vsnprintf(zc.data(), zc.size(), format, vaArgs); + va_end(vaArgs); + + if (globalLoggingFunction != nullptr) { + globalLoggingFunction(std::string(zc.data(), zc.size())); + } +} + +void TgVoip::setLoggingFunction(std::function loggingFunction) { + globalLoggingFunction = loggingFunction; +} + +void TgVoip::setGlobalServerConfig(const std::string &serverConfig) { +} + +int TgVoip::getConnectionMaxLayer() { + return 92; // TODO: retrieve from LayerBase +} + +std::string TgVoip::getVersion() { + return ""; // TODO: version not known while not released +} + +TgVoip *TgVoip::makeInstance( + TgVoipConfig const &config, + TgVoipPersistentState const &persistentState, + std::vector const &endpoints, + std::unique_ptr const &proxy, + TgVoipNetworkType initialNetworkType, + TgVoipEncryptionKey const &encryptionKey +#ifdef TGVOIP_USE_CUSTOM_CRYPTO +, + TgVoipCrypto const &crypto +#endif +#ifdef TGVOIP_USE_CALLBACK_AUDIO_IO + , + TgVoipAudioDataCallbacks const &audioDataCallbacks +#endif +) { + return new TgVoipImpl( + endpoints, + persistentState, + proxy, + config, + encryptionKey, + initialNetworkType +#ifdef TGVOIP_USE_CUSTOM_CRYPTO + , + crypto +#endif +#ifdef TGVOIP_USE_CALLBACK_AUDIO_IO + , + audioDataCallbacks +#endif + ); +} + +TgVoip::~TgVoip() = default; + +#ifdef TGVOIP_NAMESPACE +} +#endif diff --git a/submodules/TgVoipWebrtc/Impl/TgVoip.h b/submodules/TgVoipWebrtc/Impl/TgVoip.h new file mode 100644 index 0000000000..a0408040af --- /dev/null +++ b/submodules/TgVoipWebrtc/Impl/TgVoip.h @@ -0,0 +1,179 @@ +#ifndef __TGVOIP_H +#define __TGVOIP_H + +#define TGVOIP_NAMESPACE tgvoip_webrtc + +#include +#include +#include +#include + +#ifdef TGVOIP_NAMESPACE +namespace TGVOIP_NAMESPACE { +#endif + +struct TgVoipProxy { + std::string host; + uint16_t port; + std::string login; + std::string password; +}; + +enum class TgVoipEndpointType { + Inet, + Lan, + UdpRelay, + TcpRelay +}; + +struct TgVoipEdpointHost { + std::string ipv4; + std::string ipv6; +}; + +struct TgVoipEndpoint { + int64_t endpointId; + TgVoipEdpointHost host; + uint16_t port; + TgVoipEndpointType type; + unsigned char peerTag[16]; +}; + +enum class TgVoipNetworkType { + Unknown, + Gprs, + Edge, + ThirdGeneration, + Hspa, + Lte, + WiFi, + Ethernet, + OtherHighSpeed, + OtherLowSpeed, + OtherMobile, + Dialup +}; + +enum class TgVoipDataSaving { + Never, + Mobile, + Always +}; + +struct TgVoipPersistentState { + std::vector value; +}; + +#ifdef TGVOIP_USE_CUSTOM_CRYPTO +struct TgVoipCrypto { + void (*rand_bytes)(uint8_t* buffer, size_t length); + void (*sha1)(uint8_t* msg, size_t length, uint8_t* output); + void (*sha256)(uint8_t* msg, size_t length, uint8_t* output); + void (*aes_ige_encrypt)(uint8_t* in, uint8_t* out, size_t length, uint8_t* key, uint8_t* iv); + void (*aes_ige_decrypt)(uint8_t* in, uint8_t* out, size_t length, uint8_t* key, uint8_t* iv); + void (*aes_ctr_encrypt)(uint8_t* inout, size_t length, uint8_t* key, uint8_t* iv, uint8_t* ecount, uint32_t* num); + void (*aes_cbc_encrypt)(uint8_t* in, uint8_t* out, size_t length, uint8_t* key, uint8_t* iv); + void (*aes_cbc_decrypt)(uint8_t* in, uint8_t* out, size_t length, uint8_t* key, uint8_t* iv); +}; +#endif + +struct TgVoipConfig { + double initializationTimeout; + double receiveTimeout; + TgVoipDataSaving dataSaving; + bool enableP2P; + bool enableAEC; + bool enableNS; + bool enableAGC; + bool enableCallUpgrade; +#ifndef _WIN32 + std::string logPath; +#else + std::wstring logPath; +#endif + int maxApiLayer; +}; + +struct TgVoipEncryptionKey { + std::vector value; + bool isOutgoing; +}; + +enum class TgVoipState { + WaitInit, + WaitInitAck, + Estabilished, + Failed, + Reconnecting +}; + +struct TgVoipTrafficStats { + uint64_t bytesSentWifi; + uint64_t bytesReceivedWifi; + uint64_t bytesSentMobile; + uint64_t bytesReceivedMobile; +}; + +struct TgVoipFinalState { + TgVoipPersistentState persistentState; + std::string debugLog; + TgVoipTrafficStats trafficStats; + bool isRatingSuggested; +}; + +struct TgVoipAudioDataCallbacks { + std::function input; + std::function output; + std::function preprocessed; +}; + +class TgVoip { +protected: + TgVoip() = default; + +public: + static void setLoggingFunction(std::function loggingFunction); + static void setGlobalServerConfig(std::string const &serverConfig); + static int getConnectionMaxLayer(); + static std::string getVersion(); + static TgVoip *makeInstance( + TgVoipConfig const &config, + TgVoipPersistentState const &persistentState, + std::vector const &endpoints, + std::unique_ptr const &proxy, + TgVoipNetworkType initialNetworkType, + TgVoipEncryptionKey const &encryptionKey +#ifdef TGVOIP_USE_CUSTOM_CRYPTO + , + TgVoipCrypto const &crypto +#endif +#ifdef TGVOIP_USE_CALLBACK_AUDIO_IO + , + TgVoipAudioDataCallbacks const &audioDataCallbacks +#endif + ); + + virtual ~TgVoip(); + + virtual void setNetworkType(TgVoipNetworkType networkType) = 0; + virtual void setMuteMicrophone(bool muteMicrophone) = 0; + virtual void setAudioOutputGainControlEnabled(bool enabled) = 0; + virtual void setEchoCancellationStrength(int strength) = 0; + + virtual std::string getLastError() = 0; + virtual std::string getDebugInfo() = 0; + virtual int64_t getPreferredRelayId() = 0; + virtual TgVoipTrafficStats getTrafficStats() = 0; + virtual TgVoipPersistentState getPersistentState() = 0; + + virtual void setOnStateUpdated(std::function onStateUpdated) = 0; + virtual void setOnSignalBarsUpdated(std::function onSignalBarsUpdated) = 0; + + virtual TgVoipFinalState stop() = 0; +}; + +#ifdef TGVOIP_NAMESPACE +} +#endif + +#endif diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h new file mode 100644 index 0000000000..15c752dfc0 --- /dev/null +++ b/submodules/TgVoipWebrtc/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h @@ -0,0 +1,81 @@ +#ifndef OngoingCallContext_h +#define OngoingCallContext_h + +#import + +@interface OngoingCallConnectionDescriptionWebrtc : NSObject + +@property (nonatomic, readonly) int64_t connectionId; +@property (nonatomic, strong, readonly) NSString * _Nonnull ip; +@property (nonatomic, strong, readonly) NSString * _Nonnull ipv6; +@property (nonatomic, readonly) int32_t port; +@property (nonatomic, strong, readonly) NSData * _Nonnull peerTag; + +- (instancetype _Nonnull)initWithConnectionId:(int64_t)connectionId ip:(NSString * _Nonnull)ip ipv6:(NSString * _Nonnull)ipv6 port:(int32_t)port peerTag:(NSData * _Nonnull)peerTag; + +@end + +typedef NS_ENUM(int32_t, OngoingCallStateWebrtc) { + OngoingCallStateInitializing, + OngoingCallStateConnected, + OngoingCallStateFailed, + OngoingCallStateReconnecting +}; + +typedef NS_ENUM(int32_t, OngoingCallNetworkTypeWebrtc) { + OngoingCallNetworkTypeWifi, + OngoingCallNetworkTypeCellularGprs, + OngoingCallNetworkTypeCellularEdge, + OngoingCallNetworkTypeCellular3g, + OngoingCallNetworkTypeCellularLte +}; + +typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) { + OngoingCallDataSavingNever, + OngoingCallDataSavingCellular, + OngoingCallDataSavingAlways +}; + +@protocol OngoingCallThreadLocalContextQueueWebrtc + +- (void)dispatch:(void (^ _Nonnull)())f; +- (bool)isCurrent; + +@end + +@interface VoipProxyServerWebrtc : NSObject + +@property (nonatomic, strong, readonly) NSString * _Nonnull host; +@property (nonatomic, readonly) int32_t port; +@property (nonatomic, strong, readonly) NSString * _Nullable username; +@property (nonatomic, strong, readonly) NSString * _Nullable password; + +- (instancetype _Nonnull)initWithHost:(NSString * _Nonnull)host port:(int32_t)port username:(NSString * _Nullable)username password:(NSString * _Nullable)password; + +@end + +@interface OngoingCallThreadLocalContextWebrtc : NSObject + ++ (void)setupLoggingFunction:(void (* _Nullable)(NSString * _Nullable))loggingFunction; ++ (void)applyServerConfig:(NSString * _Nullable)data; ++ (int32_t)maxLayer; ++ (NSString * _Nonnull)version; + +@property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallStateWebrtc); +@property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t); + +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescriptionWebrtc * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath; +- (void)stop:(void (^_Nullable)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile))completion; + +- (bool)needRate; + +- (NSString * _Nullable)debugInfo; +- (NSString * _Nullable)version; +- (NSData * _Nonnull)getDerivedState; + +- (void)setIsMuted:(bool)isMuted; +- (void)setNetworkType:(OngoingCallNetworkTypeWebrtc)networkType; + +@end + +#endif diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm new file mode 100644 index 0000000000..85ab31235d --- /dev/null +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -0,0 +1,326 @@ +#import + +#import "TgVoip.h" + +using namespace TGVOIP_NAMESPACE; + +@implementation OngoingCallConnectionDescriptionWebrtc + +- (instancetype _Nonnull)initWithConnectionId:(int64_t)connectionId ip:(NSString * _Nonnull)ip ipv6:(NSString * _Nonnull)ipv6 port:(int32_t)port peerTag:(NSData * _Nonnull)peerTag { + self = [super init]; + if (self != nil) { + _connectionId = connectionId; + _ip = ip; + _ipv6 = ipv6; + _port = port; + _peerTag = peerTag; + } + return self; +} + +@end + +@interface OngoingCallThreadLocalContextWebrtc () { + id _queue; + int32_t _contextId; + + OngoingCallNetworkTypeWebrtc _networkType; + NSTimeInterval _callReceiveTimeout; + NSTimeInterval _callRingTimeout; + NSTimeInterval _callConnectTimeout; + NSTimeInterval _callPacketTimeout; + + TgVoip *_tgVoip; + + OngoingCallStateWebrtc _state; + int32_t _signalBars; + NSData *_lastDerivedState; +} + +- (void)controllerStateChanged:(TgVoipState)state; +- (void)signalBarsChanged:(int32_t)signalBars; + +@end + +@implementation VoipProxyServerWebrtc + +- (instancetype _Nonnull)initWithHost:(NSString * _Nonnull)host port:(int32_t)port username:(NSString * _Nullable)username password:(NSString * _Nullable)password { + self = [super init]; + if (self != nil) { + _host = host; + _port = port; + _username = username; + _password = password; + } + return self; +} + +@end + +static TgVoipNetworkType callControllerNetworkTypeForType(OngoingCallNetworkTypeWebrtc type) { + switch (type) { + case OngoingCallNetworkTypeWifi: + return TgVoipNetworkType::WiFi; + case OngoingCallNetworkTypeCellularGprs: + return TgVoipNetworkType::Gprs; + case OngoingCallNetworkTypeCellular3g: + return TgVoipNetworkType::ThirdGeneration; + case OngoingCallNetworkTypeCellularLte: + return TgVoipNetworkType::Lte; + default: + return TgVoipNetworkType::ThirdGeneration; + } +} + +static TgVoipDataSaving callControllerDataSavingForType(OngoingCallDataSavingWebrtc type) { + switch (type) { + case OngoingCallDataSavingNever: + return TgVoipDataSaving::Never; + case OngoingCallDataSavingCellular: + return TgVoipDataSaving::Mobile; + case OngoingCallDataSavingAlways: + return TgVoipDataSaving::Always; + default: + return TgVoipDataSaving::Never; + } +} + +@implementation OngoingCallThreadLocalContextWebrtc + +static void (*InternalVoipLoggingFunction)(NSString *) = NULL; + ++ (void)setupLoggingFunction:(void (*)(NSString *))loggingFunction { + InternalVoipLoggingFunction = loggingFunction; + TgVoip::setLoggingFunction([](std::string const &string) { + if (InternalVoipLoggingFunction) { + InternalVoipLoggingFunction([[NSString alloc] initWithUTF8String:string.c_str()]); + } + }); +} + ++ (void)applyServerConfig:(NSString *)string { + if (string.length != 0) { + TgVoip::setGlobalServerConfig(std::string(string.UTF8String)); + } +} + ++ (int32_t)maxLayer { + return 92; +} + ++ (NSString *)version { + return @"2.7.7"; +} + +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescriptionWebrtc * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath { + self = [super init]; + if (self != nil) { + _queue = queue; + assert([queue isCurrent]); + + _callReceiveTimeout = 20.0; + _callRingTimeout = 90.0; + _callConnectTimeout = 30.0; + _callPacketTimeout = 10.0; + _networkType = networkType; + + std::vector derivedStateValue; + derivedStateValue.resize(derivedState.length); + [derivedState getBytes:derivedStateValue.data() length:derivedState.length]; + + std::unique_ptr proxyValue = nullptr; + if (proxy != nil) { + TgVoipProxy *proxyObject = new TgVoipProxy(); + proxyObject->host = proxy.host.UTF8String; + proxyObject->port = (uint16_t)proxy.port; + proxyObject->login = proxy.username.UTF8String ?: ""; + proxyObject->password = proxy.password.UTF8String ?: ""; + proxyValue = std::unique_ptr(proxyObject); + } + + /*TgVoipCrypto crypto; + crypto.sha1 = &TGCallSha1; + crypto.sha256 = &TGCallSha256; + crypto.rand_bytes = &TGCallRandomBytes; + crypto.aes_ige_encrypt = &TGCallAesIgeEncrypt; + crypto.aes_ige_decrypt = &TGCallAesIgeDecrypt; + crypto.aes_ctr_encrypt = &TGCallAesCtrEncrypt;*/ + + std::vector endpoints; + NSArray *connections = [@[primaryConnection] arrayByAddingObjectsFromArray:alternativeConnections]; + for (OngoingCallConnectionDescriptionWebrtc *connection in connections) { + unsigned char peerTag[16]; + [connection.peerTag getBytes:peerTag length:16]; + + TgVoipEndpoint endpoint; + endpoint.endpointId = connection.connectionId; + endpoint.host = { + .ipv4 = std::string(connection.ip.UTF8String), + .ipv6 = std::string(connection.ipv6.UTF8String) + }; + endpoint.port = (uint16_t)connection.port; + endpoint.type = TgVoipEndpointType::UdpRelay; + memcpy(endpoint.peerTag, peerTag, 16); + endpoints.push_back(endpoint); + } + + TgVoipConfig config = { + .initializationTimeout = _callConnectTimeout, + .receiveTimeout = _callPacketTimeout, + .dataSaving = callControllerDataSavingForType(dataSaving), + .enableP2P = allowP2P, + .enableAEC = false, + .enableNS = true, + .enableAGC = true, + .enableCallUpgrade = false, + .logPath = logPath.length == 0 ? "" : std::string(logPath.UTF8String), + .maxApiLayer = [OngoingCallThreadLocalContextWebrtc maxLayer] + }; + + std::vector encryptionKeyValue; + encryptionKeyValue.resize(key.length); + memcpy(encryptionKeyValue.data(), key.bytes, key.length); + + TgVoipEncryptionKey encryptionKey = { + .value = encryptionKeyValue, + .isOutgoing = isOutgoing, + }; + + _tgVoip = TgVoip::makeInstance( + config, + { derivedStateValue }, + endpoints, + proxyValue, + callControllerNetworkTypeForType(networkType), + encryptionKey + ); + + _state = OngoingCallStateInitializing; + _signalBars = -1; + + __weak OngoingCallThreadLocalContextWebrtc *weakSelf = self; + _tgVoip->setOnStateUpdated([weakSelf](TgVoipState state) { + __strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf; + if (strongSelf) { + [strongSelf controllerStateChanged:state]; + } + }); + _tgVoip->setOnSignalBarsUpdated([weakSelf](int signalBars) { + __strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf; + if (strongSelf) { + [strongSelf signalBarsChanged:signalBars]; + } + }); + } + return self; +} + +- (void)dealloc { + assert([_queue isCurrent]); + if (_tgVoip != NULL) { + [self stop:nil]; + } +} + +- (bool)needRate { + return false; +} + +- (void)stop:(void (^)(NSString *, int64_t, int64_t, int64_t, int64_t))completion { + if (_tgVoip) { + TgVoipFinalState finalState = _tgVoip->stop(); + + NSString *debugLog = [NSString stringWithUTF8String:finalState.debugLog.c_str()]; + _lastDerivedState = [[NSData alloc] initWithBytes:finalState.persistentState.value.data() length:finalState.persistentState.value.size()]; + + delete _tgVoip; + _tgVoip = NULL; + + if (completion) { + completion(debugLog, finalState.trafficStats.bytesSentWifi, finalState.trafficStats.bytesReceivedWifi, finalState.trafficStats.bytesSentMobile, finalState.trafficStats.bytesReceivedMobile); + } + } +} + +- (NSString *)debugInfo { + if (_tgVoip != nil) { + NSString *version = [self version]; + return [NSString stringWithFormat:@"WebRTC, Version: %@", version]; + //auto rawDebugString = _tgVoip->getDebugInfo(); + //return [NSString stringWithUTF8String:rawDebugString.c_str()]; + } else { + return nil; + } +} + +- (NSString *)version { + if (_tgVoip != nil) { + return @"2.7.7";//[NSString stringWithUTF8String:_tgVoip->getVersion().c_str()]; + } else { + return nil; + } +} + +- (NSData * _Nonnull)getDerivedState { + if (_tgVoip) { + auto persistentState = _tgVoip->getPersistentState(); + return [[NSData alloc] initWithBytes:persistentState.value.data() length:persistentState.value.size()]; + } else if (_lastDerivedState != nil) { + return _lastDerivedState; + } else { + return [NSData data]; + } +} + +- (void)controllerStateChanged:(TgVoipState)state { + OngoingCallStateWebrtc callState = OngoingCallStateInitializing; + switch (state) { + case TgVoipState::Estabilished: + callState = OngoingCallStateConnected; + break; + case TgVoipState::Failed: + callState = OngoingCallStateFailed; + break; + case TgVoipState::Reconnecting: + callState = OngoingCallStateReconnecting; + break; + default: + break; + } + + if (callState != _state) { + _state = callState; + + if (_stateChanged) { + _stateChanged(callState); + } + } +} + +- (void)signalBarsChanged:(int32_t)signalBars { + if (signalBars != _signalBars) { + _signalBars = signalBars; + + if (_signalBarsChanged) { + _signalBarsChanged(signalBars); + } + } +} + +- (void)setIsMuted:(bool)isMuted { + if (_tgVoip) { + _tgVoip->setMuteMicrophone(isMuted); + } +} + +- (void)setNetworkType:(OngoingCallNetworkTypeWebrtc)networkType { + if (_networkType != networkType) { + _networkType = networkType; + if (_tgVoip) { + _tgVoip->setNetworkType(callControllerNetworkTypeForType(networkType)); + } + } +} + +@end + diff --git a/submodules/WalletUI/BUILD b/submodules/WalletUI/BUILD index a5159bc357..6c42bedab0 100644 --- a/submodules/WalletUI/BUILD +++ b/submodules/WalletUI/BUILD @@ -39,8 +39,6 @@ swift_library( "//submodules/UrlEscaping:UrlEscaping", "//submodules/LocalAuth:LocalAuth", "//submodules/ScreenCaptureDetection:ScreenCaptureDetection", - "//submodules/WalletUrl:WalletUrl", - "//submodules/WalletCore:WalletCore", "//submodules/ActivityIndicator:ActivityIndicator", "//submodules/ProgressNavigationButtonNode:ProgressNavigationButtonNode", "//submodules/Markdown:Markdown", diff --git a/submodules/openssl/BUCK b/submodules/openssl/BUCK index dc53d924ab..5d87cd095d 100644 --- a/submodules/openssl/BUCK +++ b/submodules/openssl/BUCK @@ -132,13 +132,28 @@ genrule( srcs = [ "pack-openssl.sh", ], - bash = "sh $SRCDIR/pack-openssl.sh $OUT $(location :openssl_build_arm64)/out/include " + " ".join(["$(location :openssl_build_" + arch + ")/out/lib/libcrypto.a" for arch in archs]), + bash = "sh $SRCDIR/pack-openssl.sh $OUT $(location :openssl_build_arm64)/out/include " + + " ".join(["$(location :openssl_build_" + arch + ")/out/lib/libcrypto.a" for arch in archs]), out = "openssl", visibility = [ "PUBLIC", ] ) +genrule( + name = "openssl_libssl_merged", + srcs = [ + "pack-libssl.sh", + ], + bash = "sh $SRCDIR/pack-libssl.sh $OUT $(location :openssl_build_arm64)/out/include " + + " ".join(["$(location :openssl_build_" + arch + ")/out/lib/libssl.a" for arch in archs]), + out = "libssl", + visibility = [ + "PUBLIC", + ] +) + + openssl_header_targets = gen_header_targets(openssl_header_paths, "openssl_header_", "", "openssl_build_arm64", "out/include") apple_library( @@ -150,11 +165,15 @@ apple_library( exported_headers = openssl_header_targets, linker_flags = [ "-L$(location :openssl_build_merged)/lib", + "-L$(location :openssl_libssl_merged)/lib", "-lcrypto", + "-lssl", ], exported_linker_flags = [ "-L$(location :openssl_build_merged)/lib", + "-L$(location :openssl_libssl_merged)/lib", "-lcrypto", + "-lssl", ], deps = [":openssl_build_" + arch for arch in ["arm64"]], ) diff --git a/submodules/openssl/BUILD b/submodules/openssl/BUILD index 955faeca8d..2770f12648 100644 --- a/submodules/openssl/BUILD +++ b/submodules/openssl/BUILD @@ -107,6 +107,7 @@ openssl_headers = [ openssl_libs = [ "libcrypto.a", + "libssl.a", ] genrule( @@ -119,6 +120,7 @@ genrule( ], cmd_bash = """ + if [ "$(TARGET_CPU)" == "ios_armv7" ]; then BUILD_ARCH="armv7" elif [ "$(TARGET_CPU)" == "ios_arm64" ]; then diff --git a/submodules/openssl/pack-libssl.sh b/submodules/openssl/pack-libssl.sh new file mode 100644 index 0000000000..9e00937932 --- /dev/null +++ b/submodules/openssl/pack-libssl.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -e +set -x + +OUT_DIR="$1" +INCLUDE_DIR="$2" +shift +shift +LIBS="$@" + +mkdir -p "$OUT_DIR/lib" + +/usr/bin/lipo $LIBS -output "$OUT_DIR/lib/libssl.a" -create + diff --git a/third-party/BUCK b/third-party/BUCK index e69de29bb2..c2e30f374b 100644 --- a/third-party/BUCK +++ b/third-party/BUCK @@ -0,0 +1,8 @@ + +filegroup( + name = "depot_tools_sources", + srcs = glob([ + "depot_tools/**/*" + ]), + visibility = ["PUBLIC"], +) diff --git a/third-party/BUILD b/third-party/BUILD index e69de29bb2..44e0b20555 100644 --- a/third-party/BUILD +++ b/third-party/BUILD @@ -0,0 +1,8 @@ + +filegroup( + name = "depot_tools_sources", + srcs = glob([ + "depot_tools/**/*" + ]), + visibility = ["//visibility:public"], +) diff --git a/third-party/depot_tools b/third-party/depot_tools new file mode 160000 index 0000000000..ae7f4c5111 --- /dev/null +++ b/third-party/depot_tools @@ -0,0 +1 @@ +Subproject commit ae7f4c51114152e95cf1e9b6d2f0a4936fb7c5d6 diff --git a/third-party/webrtc/BUCK b/third-party/webrtc/BUCK new file mode 100644 index 0000000000..4d62e6de3d --- /dev/null +++ b/third-party/webrtc/BUCK @@ -0,0 +1,65 @@ +load("//Config:buck_rule_macros.bzl", "static_library") + +webrtc_lib_flags = [ + "-lwebrtc" +] + +genrule( + name = "webrtc_build", + srcs = [ + "build-webrtc-buck.sh", + "webrtc-ios", + "patch.sh", + ], + bash = +""" + set -x + echo "SRCDIR=$SRCDIR" + + OUT_DIR="ios" + + BUILD_ARCH=arm64 + + BUILD_DIR="$SRCDIR/$BUILD_ARCH" + rm -rf "$BUILD_DIR" + mkdir -p "$BUILD_DIR" + + mkdir -p "$BUILD_DIR/webrtc-ios" + cp -R "$SRCDIR/webrtc-ios/.git" "$BUILD_DIR/webrtc-ios/.git" + cp -R "$SRCDIR/webrtc-ios/src" "$BUILD_DIR/webrtc-ios/src" + + DEPOT_TOOLS_PATH="$(location //third-party:depot_tools_sources)" + + rm -rf "$BUILD_DIR/depot_tools" + cp -R "$DEPOT_TOOLS_PATH" "$BUILD_DIR/" + + cp "$SRCDIR/patch.sh" "$BUILD_DIR/" + + rm -rf "$BUILD_DIR/openssl" + cp -R "$(location //submodules/openssl:openssl_build_merged)" "$BUILD_DIR/openssl/" + cp -R "$(location //submodules/openssl:openssl_libssl_merged)" "$BUILD_DIR/libssl/" + + sh $SRCDIR/build-webrtc-buck.sh "$BUILD_DIR" $BUILD_ARCH + + mkdir -p "$OUT" + cp "$BUILD_DIR/webrtc-ios/src/out/$OUT_DIR/obj/libwebrtc.a" "$OUT/" +""", + out = "libwebrtc", + visibility = ["PUBLIC"] +) + +apple_library( + name = "webrtc_lib", + visibility = [ + "PUBLIC", + ], + linker_flags = [ + "-L$(location :webrtc_build)", + ] + webrtc_lib_flags, + exported_linker_flags = [ + "-L$(location :webrtc_build)", + ] + webrtc_lib_flags, + deps = [ + ":webrtc_build" + ], +) diff --git a/third-party/webrtc/BUILD b/third-party/webrtc/BUILD new file mode 100644 index 0000000000..fd2b2e8807 --- /dev/null +++ b/third-party/webrtc/BUILD @@ -0,0 +1,78 @@ +webrtc_libs = [ + "libwebrtc.a", +] + +filegroup( + name = "webrtc_sources", + srcs = glob([ + "webrtc-ios/**/*" + ]), +) + +genrule( + name = "webrtc_build", + srcs = [ + "build-webrtc-bazel.sh", + "patch.sh", + ":webrtc_sources", + "//third-party:depot_tools_sources", + "//submodules/openssl:openssl_include", + "//submodules/openssl:libcrypto.a", + "//submodules/openssl:libssl.a", + ], + cmd_bash = + """ + OUT_DIR="ios" + if [ "$(TARGET_CPU)" == "ios_armv7" ]; then + BUILD_ARCH="armv7" + elif [ "$(TARGET_CPU)" == "ios_arm64" ]; then + BUILD_ARCH="arm64" + elif [ "$(TARGET_CPU)" == "ios_x86_64" ]; then + BUILD_ARCH="x64" + OUT_DIR="ios_sim" + else + echo "Unsupported architecture $(TARGET_CPU)" + fi + BUILD_DIR="$(RULEDIR)/$$BUILD_ARCH" + rm -rf "$$BUILD_DIR" + mkdir -p "$$BUILD_DIR" + + SOURCE_PATH="third-party/webrtc/webrtc-ios/src" + + rsync -aqW "$$SOURCE_PATH" "$$BUILD_DIR/" + #cp -R "$$SOURCE_PATH" "$$BUILD_DIR/" + + DEPOT_TOOLS_PATH="third-party/depot_tools" + + rm -rf "$$BUILD_DIR/depot_tools" + cp -R "$$DEPOT_TOOLS_PATH" "$$BUILD_DIR/" + + rm -rf "$$BUILD_DIR/openssl" + mkdir -p "$$BUILD_DIR/openssl/include/openssl" + for f in $(locations //submodules/openssl:openssl_include); do + cp -f "$$f" "$$BUILD_DIR/openssl/include/openssl/" + done + + mkdir -p "$$BUILD_DIR/openssl/lib" + cp -f "$(location //submodules/openssl:libcrypto.a)" "$$BUILD_DIR/openssl/" + cp -f "$(location //submodules/openssl:libssl.a)" "$$BUILD_DIR/openssl/" + + rm -f "$$BUILD_DIR/build-webrtc-bazel.sh" + cp $(location build-webrtc-bazel.sh) "$$BUILD_DIR/" + + rm -f "$$BUILD_DIR/patch.sh" + cp $(location patch.sh) "$$BUILD_DIR/" + + sh $$BUILD_DIR/build-webrtc-bazel.sh "$$BUILD_DIR" $$BUILD_ARCH + """ + "\n".join([ + "cp -f $$BUILD_DIR/src/out/$$OUT_DIR/obj/{lib} $(location {lib})".format(lib=lib) for lib in webrtc_libs + ]), + outs = webrtc_libs, + visibility = ["//visibility:public",] +) + +cc_library( + name = "webrtc_lib", + srcs = [":" + x for x in webrtc_libs], + visibility = ["//visibility:public"], +) diff --git a/third-party/webrtc/build-webrtc-bazel.sh b/third-party/webrtc/build-webrtc-bazel.sh new file mode 100755 index 0000000000..bad6eb9722 --- /dev/null +++ b/third-party/webrtc/build-webrtc-bazel.sh @@ -0,0 +1,29 @@ +#/bin/sh + +set -x +set -e + +BUILD_DIR="$(pwd)/$1" +ARCH="$2" + +echo "BUILD_DIR=$BUILD_DIR" +echo "ARCH=$ARCH" + +export PATH="$PATH:$BUILD_DIR/depot_tools" + +rm -rf "$BUILD_DIR/src/openssl" +cp -R "$BUILD_DIR/openssl" "$BUILD_DIR/src/" + +pushd "$BUILD_DIR/src" + +sh "../patch.sh" || true + +OUT_DIR="ios" +if [ "$ARCH" == "x64" ]; then + OUT_DIR="ios_sim" +fi + +gn gen out/$OUT_DIR --args="use_xcode_clang=true "" target_cpu=\"$ARCH\""' target_os="ios" is_debug=false is_component_build=false rtc_include_tests=false use_rtti=true rtc_use_x11=false use_custom_libcxx=false use_custom_libcxx_for_host=false rtc_include_builtin_video_codecs=false rtc_build_ssl=false rtc_build_examples=false rtc_build_tools=false ios_deployment_target="9.0" ios_enable_code_signing=false is_unsafe_developer_build=false rtc_enable_protobuf=false rtc_include_builtin_video_codecs=false rtc_use_gtk=false rtc_use_metal_rendering=false rtc_ssl_root="//openssl"' +ninja -C out/$OUT_DIR webrtc + +popd diff --git a/third-party/webrtc/build-webrtc-buck.sh b/third-party/webrtc/build-webrtc-buck.sh new file mode 100755 index 0000000000..4c698aa6a4 --- /dev/null +++ b/third-party/webrtc/build-webrtc-buck.sh @@ -0,0 +1,33 @@ +#/bin/sh + +set -x +set -e + +BUILD_DIR="$1" +ARCH="$2" + +echo "BUILD_DIR=$BUILD_DIR" +echo "ARCH=$ARCH" + +export PATH="$PATH:$BUILD_DIR/depot_tools_sources/depot_tools" + +rm -rf "$BUILD_DIR/webrtc-ios/src/openssl" +cp -R "$BUILD_DIR/openssl" "$BUILD_DIR/webrtc-ios/src/" +cp -R "$BUILD_DIR/libssl" "$BUILD_DIR/webrtc-ios/src/" + +pushd "$BUILD_DIR/webrtc-ios/src" + +mv openssl/lib/libcrypto.a openssl/ +mv libssl/lib/libssl.a openssl/ + +sh "../../patch.sh" || true + +OUT_DIR="ios" +if [ "$ARCH" == "x64" ]; then + OUT_DIR="ios_sim" +fi + +buildtools/mac/gn gen out/$OUT_DIR --args="use_xcode_clang=true "" target_cpu=\"$ARCH\""' target_os="ios" is_debug=false is_component_build=false rtc_include_tests=false use_rtti=true rtc_use_x11=false use_custom_libcxx=false use_custom_libcxx_for_host=false rtc_include_builtin_video_codecs=false rtc_build_ssl=false rtc_build_examples=false rtc_build_tools=false ios_deployment_target="9.0" ios_enable_code_signing=false is_unsafe_developer_build=false rtc_enable_protobuf=false rtc_include_builtin_video_codecs=false rtc_use_gtk=false rtc_use_metal_rendering=false rtc_ssl_root="//openssl"' +ninja -C out/$OUT_DIR webrtc + +popd diff --git a/third-party/webrtc/patch.sh b/third-party/webrtc/patch.sh new file mode 100644 index 0000000000..8b6e418e81 --- /dev/null +++ b/third-party/webrtc/patch.sh @@ -0,0 +1,71 @@ +#!/bin/sh + +PATCH=$(cat <<-END +--- a/rtc_base/BUILD.gn ++++ b/rtc_base/BUILD.gn +@@ -23,7 +23,11 @@ if (!rtc_build_ssl) { + config("external_ssl_library") { + assert(rtc_ssl_root != "", + "You must specify rtc_ssl_root when rtc_build_ssl==0.") +- include_dirs = [ rtc_ssl_root ] ++ include_dirs = [ "\$rtc_ssl_root/include" ] ++ libs = [ ++ "\$rtc_ssl_root/libssl.a", ++ "\$rtc_ssl_root/libcrypto.a" ++ ] + } + } + +--- a/third_party/usrsctp/BUILD.gn ++++ b/third_party/usrsctp/BUILD.gn +@@ -3,6 +3,7 @@ + # found in the LICENSE file. + + import("//build/toolchain/toolchain.gni") ++import("//webrtc.gni") + + config("usrsctp_config") { + include_dirs = [ +@@ -140,7 +141,9 @@ static_library("usrsctp") { + if (is_fuchsia) { + defines += [ "__Userspace_os_Fuchsia" ] + } +- deps = [ +- "//third_party/boringssl", +- ] ++ if (rtc_build_ssl) { ++ deps += [ "//third_party/boringssl" ] ++ } else { ++ configs += [ "//rtc_base:external_ssl_library" ] ++ } + } + +--- a/third_party/libsrtp/BUILD.gn ++++ b/third_party/libsrtp/BUILD.gn +@@ -3,6 +3,7 @@ + # found in the LICENSE file. + + import("//testing/test.gni") ++import("//webrtc.gni") + + declare_args() { + # Tests may not be appropriate for some build environments, e.g. Windows. +@@ -114,9 +115,11 @@ static_library("libsrtp") { + "srtp/ekt.c", + "srtp/srtp.c", + ] +- public_deps = [ +- "//third_party/boringssl:boringssl", +- ] ++ if (rtc_build_ssl) { ++ public_deps = [ "//third_party/boringssl" ] ++ } else { ++ configs += [ "//rtc_base:external_ssl_library" ] ++ } + } + + if (build_libsrtp_tests) { +END +) + +echo "$PATCH" | patch -p1 diff --git a/third-party/webrtc/webrtc-ios b/third-party/webrtc/webrtc-ios new file mode 160000 index 0000000000..8f607fdd69 --- /dev/null +++ b/third-party/webrtc/webrtc-ios @@ -0,0 +1 @@ +Subproject commit 8f607fdd6930223d8db1c8f0496c4cd3ab83be9a