Conference calls

This commit is contained in:
Isaac
2025-03-30 02:06:50 +04:00
parent 66c244f540
commit aaf52d4282
1585 changed files with 712275 additions and 1047 deletions

View File

@@ -4,7 +4,6 @@ import MtProtoKit
import SwiftSignalKit
import TelegramApi
private let minLayer: Int32 = 65
public enum CallSessionError: Equatable {
@@ -15,11 +14,11 @@ public enum CallSessionError: Equatable {
case disconnected
}
public enum CallSessionEndedType {
public enum CallSessionEndedType: Equatable {
case hungUp
case busy
case missed
case switchedToConference
case switchedToConference(slug: String)
}
public enum CallSessionTerminationReason: Equatable {
@@ -65,41 +64,47 @@ public struct GroupCallReference: Equatable {
}
extension GroupCallReference {
init(_ apiGroupCall: Api.InputGroupCall) {
init?(_ apiGroupCall: Api.InputGroupCall) {
switch apiGroupCall {
case let .inputGroupCall(id, accessHash):
self.init(id: id, accessHash: accessHash)
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
return nil
}
}
var apiInputGroupCall: Api.InputGroupCall {
return .inputGroupCall(id: self.id, accessHash: self.accessHash)
}
}
enum CallSessionInternalState {
case ringing(id: Int64, accessHash: Int64, gAHash: Data, b: Data, versions: [String], conferenceCall: GroupCallReference?)
case accepting(id: Int64, accessHash: Int64, gAHash: Data, b: Data, conferenceCall: GroupCallReference?, disposable: Disposable)
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, conferenceCall: GroupCallReference?, disposable: Disposable)
case requested(id: Int64, accessHash: Int64, a: Data, gA: Data, config: SecretChatEncryptionConfig, remoteConfirmationTimestamp: Int32?, conferenceCall: GroupCallReference?)
case confirming(id: Int64, accessHash: Int64, key: Data, keyId: Int64, keyVisualHash: Data, conferenceCall: GroupCallReference?, disposable: Disposable)
case active(id: Int64, accessHash: Int64, beginTimestamp: Int32, key: Data, keyId: Int64, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool, conferenceCall: GroupCallReference?, willSwitchToConference: Bool)
case switchedToConference(key: Data, keyVisualHash: Data, conferenceCall: GroupCallReference)
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, version: String, customParameters: String?, allowsP2P: Bool)
case switchedToConference(slug: String)
case dropping(reason: CallSessionTerminationReason, disposable: Disposable)
case terminated(id: Int64?, accessHash: Int64?, reason: CallSessionTerminationReason, reportRating: Bool, sendDebugLogs: Bool)
var stableId: Int64? {
switch self {
case let .ringing(id, _, _, _, _, _):
case let .ringing(id, _, _, _, _):
return id
case let .accepting(id, _, _, _, _, _):
case let .accepting(id, _, _, _, _):
return id
case let .awaitingConfirmation(id, _, _, _, _):
return id
case .requesting:
return nil
case let .requested(id, _, _, _, _, _, _):
case let .requested(id, _, _, _, _, _):
return id
case let .confirming(id, _, _, _, _, _, _):
case let .confirming(id, _, _, _, _, _):
return id
case let .active(id, _, _, _, _, _, _, _, _, _, _, _, _):
case let .active(id, _, _, _, _, _, _, _, _, _, _):
return id
case .switchedToConference:
return nil
@@ -138,7 +143,8 @@ public struct CallSessionRingingState: Equatable {
public let peerId: PeerId
public let isVideo: Bool
public let isVideoPossible: Bool
public let isIncomingConference: Bool
public let conferenceSource: MessageId?
public let otherParticipants: [EnginePeer]
}
public enum DropCallReason {
@@ -166,9 +172,9 @@ public struct CallTerminationOptions: OptionSet {
public enum CallSessionState {
case ringing
case accepting
case requesting(ringing: Bool, conferenceCall: GroupCallReference?)
case active(id: CallId, key: Data, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool, conferenceCall: GroupCallReference?, willSwitchToConference: Bool)
case switchedToConference(key: Data, keyVisualHash: Data, conferenceCall: GroupCallReference)
case requesting(ringing: Bool)
case active(id: CallId, key: Data, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool)
case switchedToConference(slug: String)
case dropping(reason: CallSessionTerminationReason)
case terminated(id: CallId?, reason: CallSessionTerminationReason, options: CallTerminationOptions)
@@ -178,33 +184,37 @@ public enum CallSessionState {
self = .ringing
case .accepting, .awaitingConfirmation:
self = .accepting
case let .requesting(_, conferenceCall, _):
self = .requesting(ringing: false, conferenceCall: conferenceCall)
case let .confirming(_, _, _, _, _, conferenceCall, _):
self = .requesting(ringing: true, conferenceCall: conferenceCall)
case let .requested(_, _, _, _, _, remoteConfirmationTimestamp, conferenceCall):
self = .requesting(ringing: remoteConfirmationTimestamp != nil, conferenceCall: conferenceCall)
case let .active(id, accessHash, _, key, _, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, conferenceCall, willSwitchToConference):
self = .active(id: CallId(id: id, accessHash: accessHash), key: key, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: conferenceCall, willSwitchToConference: willSwitchToConference)
case .requesting:
self = .requesting(ringing: false)
case .confirming:
self = .requesting(ringing: true)
case let .requested(_, _, _, _, _, remoteConfirmationTimestamp):
self = .requesting(ringing: remoteConfirmationTimestamp != nil)
case let .active(id, accessHash, _, key, _, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P):
self = .active(id: CallId(id: id, accessHash: accessHash), key: key, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P)
case let .dropping(reason, _):
self = .dropping(reason: reason)
case let .terminated(id, accessHash, reason, reportRating, sendDebugLogs):
var options = CallTerminationOptions()
if reportRating {
options.insert(.reportRating)
}
if sendDebugLogs {
options.insert(.sendDebugLogs)
}
let callId: CallId?
if let id = id, let accessHash = accessHash {
callId = CallId(id: id, accessHash: accessHash)
if case let .ended(endedReason) = reason, case let .switchedToConference(slug) = endedReason {
self = .switchedToConference(slug: slug)
} else {
callId = nil
var options = CallTerminationOptions()
if reportRating {
options.insert(.reportRating)
}
if sendDebugLogs {
options.insert(.sendDebugLogs)
}
let callId: CallId?
if let id = id, let accessHash = accessHash {
callId = CallId(id: id, accessHash: accessHash)
} else {
callId = nil
}
self = .terminated(id: callId, reason: reason, options: options)
}
self = .terminated(id: callId, reason: reason, options: options)
case let .switchedToConference(key, keyVisualHash, conferenceCall):
self = .switchedToConference(key: key, keyVisualHash: keyVisualHash, conferenceCall: conferenceCall)
case let .switchedToConference(slug):
self = .switchedToConference(slug: slug)
}
}
}
@@ -222,7 +232,7 @@ public struct CallSession {
public let state: CallSessionState
public let isVideoPossible: Bool
init(
public init(
id: CallSessionInternalId,
stableId: Int64?,
isOutgoing: Bool,
@@ -336,17 +346,15 @@ private func parseConnectionSet(primary: Api.PhoneConnection, alternative: [Api.
private final class CallSessionContext {
let peerId: PeerId
let isOutgoing: Bool
let isIncomingConference: Bool
var type: CallSession.CallType
var isVideoPossible: Bool
let pendingConference: (conference: GroupCallReference, encryptionKey: Data)?
var state: CallSessionInternalState
let subscribers = Bag<(CallSession) -> Void>()
var signalingReceiver: (([Data]) -> Void)?
let signalingDisposables = DisposableSet()
var createConferenceCallDisposable: Disposable?
let acknowledgeIncomingCallDisposable = MetaDisposable()
var createConferenceCallDisposable: Disposable?
var isEmpty: Bool {
if case .terminated = self.state {
@@ -356,13 +364,11 @@ private final class CallSessionContext {
}
}
init(peerId: PeerId, isOutgoing: Bool, isIncomingConference: Bool, type: CallSession.CallType, isVideoPossible: Bool, pendingConference: (conference: GroupCallReference, encryptionKey: Data)?, state: CallSessionInternalState) {
init(peerId: PeerId, isOutgoing: Bool, type: CallSession.CallType, isVideoPossible: Bool, state: CallSessionInternalState) {
self.peerId = peerId
self.isOutgoing = isOutgoing
self.isIncomingConference = isIncomingConference
self.type = type
self.isVideoPossible = isVideoPossible
self.pendingConference = pendingConference
self.state = state
}
@@ -373,6 +379,73 @@ private final class CallSessionContext {
}
}
private final class IncomingConferenceInvitationContext {
enum State: Equatable {
case pending
case ringing(callId: Int64, otherParticipants: [EnginePeer])
case stopped
}
private let queue: Queue
private var disposable: Disposable?
let internalId: CallSessionInternalId
private(set) var state: State = .pending
init(queue: Queue, postbox: Postbox, messageId: MessageId, updated: @escaping () -> Void) {
self.queue = queue
self.internalId = CallSessionInternalId()
let key = PostboxViewKey.messages(Set([messageId]))
self.disposable = (postbox.combinedView(keys: [key])
|> map { view -> Message? in
guard let view = view.views[key] as? MessagesView else {
return nil
}
return view.messages[messageId]
}
|> deliverOn(self.queue)).startStrict(next: { [weak self] message in
guard let self = self else {
return
}
let state: State
if let message = message {
var foundAction: TelegramMediaAction?
for media in message.media {
if let action = media as? TelegramMediaAction {
foundAction = action
break
}
}
if let action = foundAction, case let .conferenceCall(callId, duration, otherParticipants) = action.action {
if duration != nil {
state = .stopped
} else {
state = .ringing(callId: callId, otherParticipants: otherParticipants.compactMap { id -> EnginePeer? in
return message.peers[id].flatMap(EnginePeer.init)
})
}
} else {
state = .stopped
}
} else {
state = .stopped
}
if self.state != state {
self.state = state
updated()
}
})
}
deinit {
self.disposable?.dispose()
}
}
private func selectVersionOnAccept(localVersions: [CallSessionManagerImplementationVersion], remoteVersions: [String]) -> [String]? {
let filteredVersions = localVersions.map({ $0.version }).filter(remoteVersions.contains)
if filteredVersions.isEmpty {
@@ -406,10 +479,13 @@ private final class CallSessionManagerContext {
private var futureContextSubscribers: [CallSessionInternalId: Bag<(CallSession) -> Void>] = [:]
private var contextIdByStableId: [CallSessionStableId: CallSessionInternalId] = [:]
private var incomingConferenceInvitationContexts: [MessageId: IncomingConferenceInvitationContext] = [:]
private var enqueuedSignalingData: [Int64: [Data]] = [:]
private let disposables = DisposableSet()
private let rejectConferenceInvitationDisposables = DisposableSet()
init(queue: Queue, postbox: Postbox, network: Network, accountPeerId: PeerId, maxLayer: Int32, versions: [CallSessionManagerImplementationVersion], addUpdates: @escaping (Api.Updates) -> Void) {
self.queue = queue
self.postbox = postbox
@@ -423,6 +499,7 @@ private final class CallSessionManagerContext {
deinit {
assert(self.queue.isCurrent())
self.disposables.dispose()
self.rejectConferenceInvitationDisposables.dispose()
}
func updateVersions(versions: [CallSessionManagerImplementationVersion]) {
@@ -556,7 +633,20 @@ private final class CallSessionManagerContext {
peerId: context.peerId,
isVideo: context.type == .video,
isVideoPossible: context.isVideoPossible,
isIncomingConference: context.isIncomingConference
conferenceSource: nil,
otherParticipants: []
))
}
}
for (id, context) in self.incomingConferenceInvitationContexts {
if case let .ringing(_, otherParticipants) = context.state {
ringingContexts.append(CallSessionRingingState(
id: context.internalId,
peerId: id.peerId,
isVideo: false,
isVideoPossible: true,
conferenceSource: id,
otherParticipants: otherParticipants
))
}
}
@@ -584,7 +674,7 @@ private final class CallSessionManagerContext {
}
}
private func addIncoming(peerId: PeerId, stableId: CallSessionStableId, accessHash: Int64, timestamp: Int32, gAHash: Data, versions: [String], isVideo: Bool, conferenceCall: GroupCallReference?) -> CallSessionInternalId? {
private func addIncoming(peerId: PeerId, stableId: CallSessionStableId, accessHash: Int64, timestamp: Int32, gAHash: Data, versions: [String], isVideo: Bool) -> CallSessionInternalId? {
if self.contextIdByStableId[stableId] != nil {
return nil
}
@@ -594,13 +684,10 @@ private final class CallSessionManagerContext {
let b = Data(bytesNoCopy: bBytes, count: 256, deallocator: .free)
if randomStatus == 0 {
var isVideoPossible = self.videoVersions().contains(where: { versions.contains($0) })
//#if DEBUG
isVideoPossible = true
//#endif
let isVideoPossible = true
let internalId = CallSessionManager.getStableIncomingUUID(stableId: stableId)
let context = CallSessionContext(peerId: peerId, isOutgoing: false, isIncomingConference: conferenceCall != nil, type: isVideo ? .video : .audio, isVideoPossible: isVideoPossible, pendingConference: nil, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b, versions: versions, conferenceCall: conferenceCall))
let context = CallSessionContext(peerId: peerId, isOutgoing: false, type: isVideo ? .video : .audio, isVideoPossible: isVideoPossible, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b, versions: versions))
self.contexts[internalId] = context
let queue = self.queue
@@ -632,38 +719,62 @@ private final class CallSessionManagerContext {
}
func drop(internalId: CallSessionInternalId, reason: DropCallReason, debugLog: Signal<String?, NoError>) {
for (id, context) in self.incomingConferenceInvitationContexts {
if context.internalId == internalId {
self.incomingConferenceInvitationContexts.removeValue(forKey: id)
self.ringingStatesUpdated()
let addUpdates = self.addUpdates
let rejectSignal = self.network.request(Api.functions.phone.declineConferenceCallInvite(msgId: id.id))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
|> mapToSignal { updates -> Signal<Never, NoError> in
if let updates {
addUpdates(updates)
}
return .complete()
}
self.rejectConferenceInvitationDisposables.add(rejectSignal.startStrict())
return
}
}
if let context = self.contexts[internalId] {
var dropData: (CallSessionStableId, Int64, DropCallSessionReason)?
var wasRinging = false
let isVideo = context.type == .video
switch context.state {
case let .ringing(id, accessHash, _, _, _, _):
case let .ringing(id, accessHash, _, _, _):
wasRinging = true
let internalReason: DropCallSessionReason
switch reason {
case .busy:
internalReason = .busy
case .hangUp:
internalReason = .hangUp(0)
case .disconnect:
internalReason = .disconnect
case .missed:
internalReason = .missed
case .busy:
internalReason = .busy
case .hangUp:
internalReason = .hangUp(0)
case .disconnect:
internalReason = .disconnect
case .missed:
internalReason = .missed
}
dropData = (id, accessHash, internalReason)
case let .accepting(id, accessHash, _, _, _, disposable):
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 {
case .busy, .hangUp:
internalReason = .hangUp(duration)
case .disconnect:
internalReason = .disconnect
case .missed:
internalReason = .missed
case .busy, .hangUp:
internalReason = .hangUp(duration)
case .disconnect:
internalReason = .disconnect
case .missed:
internalReason = .missed
}
dropData = (id, accessHash, internalReason)
case .switchedToConference:
@@ -672,21 +783,21 @@ private final class CallSessionManagerContext {
break
case let .awaitingConfirmation(id, accessHash, _, _, _):
dropData = (id, accessHash, .abort)
case let .confirming(id, accessHash, _, _, _, _, disposable):
case let .confirming(id, accessHash, _, _, _, disposable):
disposable.dispose()
dropData = (id, accessHash, .abort)
case let .requested(id, accessHash, _, _, _, _, _):
case let .requested(id, accessHash, _, _, _, _):
let internalReason: DropCallSessionReason
switch reason {
case .busy, .hangUp:
internalReason = .missed
case .disconnect:
internalReason = .disconnect
case .missed:
internalReason = .missed
case .busy, .hangUp:
internalReason = .missed
case .disconnect:
internalReason = .disconnect
case .missed:
internalReason = .missed
}
dropData = (id, accessHash, internalReason)
case let .requesting(_, _, disposable):
case let .requesting(_, disposable):
disposable.dispose()
context.state = .terminated(id: nil, accessHash: nil, reason: .ended(.hungUp), reportRating: false, sendDebugLogs: false)
self.contextUpdated(internalId: internalId)
@@ -709,8 +820,8 @@ private final class CallSessionManagerContext {
mappedReason = .ended(.hungUp)
case .missed:
mappedReason = .ended(.missed)
case .switchToConference:
mappedReason = .ended(.switchedToConference)
case let .switchToConference(slug):
mappedReason = .ended(.switchedToConference(slug: slug))
}
context.state = .dropping(reason: mappedReason, disposable: (dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, isVideo: isVideo, reason: reason)
|> deliverOn(self.queue)).start(next: { [weak self] reportRating, sendDebugLogs in
@@ -751,12 +862,12 @@ private final class CallSessionManagerContext {
}
}
func dropToConference(internalId: CallSessionInternalId, encryptedGroupKey: Data) {
func dropToConference(internalId: CallSessionInternalId, slug: String) {
if let context = self.contexts[internalId] {
var dropData: (CallSessionStableId, Int64)?
let isVideo = context.type == .video
switch context.state {
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, _, _):
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _):
dropData = (id, accessHash)
default:
break
@@ -764,11 +875,11 @@ private final class CallSessionManagerContext {
if let (id, accessHash) = dropData {
self.contextIdByStableId.removeValue(forKey: id)
context.state = .dropping(reason: .ended(.switchedToConference), disposable: (dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, isVideo: isVideo, reason: .switchToConference(encryptedGroupKey: encryptedGroupKey))
context.state = .dropping(reason: .ended(.switchedToConference(slug: slug)), disposable: (dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, isVideo: isVideo, reason: .switchToConference(slug: slug))
|> deliverOn(self.queue)).start(next: { [weak self] reportRating, sendDebugLogs in
if let strongSelf = self {
if let context = strongSelf.contexts[internalId] {
context.state = .terminated(id: id, accessHash: accessHash, reason: .ended(.hungUp), reportRating: reportRating, sendDebugLogs: sendDebugLogs)
context.state = .switchedToConference(slug: slug)
strongSelf.contextUpdated(internalId: internalId)
if context.isEmpty {
strongSelf.contexts.removeValue(forKey: internalId)
@@ -793,9 +904,9 @@ private final class CallSessionManagerContext {
func accept(internalId: CallSessionInternalId) {
if let context = self.contexts[internalId] {
switch context.state {
case let .ringing(id, accessHash, gAHash, b, _, conferenceCall):
case let .ringing(id, accessHash, gAHash, b, _):
let acceptVersions = self.versions.map({ $0.version })
context.state = .accepting(id: id, accessHash: accessHash, gAHash: gAHash, b: b, conferenceCall: conferenceCall, disposable: (acceptCallSession(accountPeerId: self.accountPeerId, 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
context.state = .accepting(id: id, accessHash: accessHash, gAHash: gAHash, b: b, disposable: (acceptCallSession(accountPeerId: self.accountPeerId, 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 {
@@ -806,9 +917,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, version, customParameters, allowsP2P, conferenceCall):
case let .call(config, gA, timestamp, connections, maxLayer, version, customParameters, 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, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: conferenceCall, willSwitchToConference: context.isIncomingConference)
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: timestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P)
strongSelf.contextUpdated(internalId: internalId)
} else {
strongSelf.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
@@ -829,7 +940,7 @@ private final class CallSessionManagerContext {
func sendSignalingData(internalId: CallSessionInternalId, data: Data) {
if let context = self.contexts[internalId] {
switch context.state {
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, _, _):
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _):
context.signalingDisposables.add(self.network.request(Api.functions.phone.sendSignalingData(peer: .inputPhoneCall(id: id, accessHash: accessHash), data: Buffer(data: data))).start())
default:
break
@@ -845,33 +956,29 @@ private final class CallSessionManagerContext {
var idAndAccessHash: (id: Int64, accessHash: Int64)?
switch context.state {
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, conferenceCall, _):
if conferenceCall != nil {
return
}
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _):
idAndAccessHash = (id, accessHash)
default:
break
}
if let (id, accessHash) = idAndAccessHash {
context.createConferenceCallDisposable = (createConferenceCall(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, callId: CallId(id: id, accessHash: accessHash))
if idAndAccessHash != nil {
context.createConferenceCallDisposable = (_internal_createConferenceCall(
postbox: self.postbox,
network: self.network,
accountPeerId: self.accountPeerId
)
|> deliverOn(self.queue)).startStrict(next: { [weak self] result in
guard let self else {
return
}
self.dropToConference(internalId: internalId, slug: result.slug)
}, error: { [weak self] error in
guard let self else {
return
}
guard let context = self.contexts[internalId] else {
return
}
if let result {
switch context.state {
case let .active(id, accessHash, beginTimestamp, key, keyId, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, _, _):
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: beginTimestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: result, willSwitchToConference: false)
self.contextUpdated(internalId: internalId)
default:
break
}
}
self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
})
}
}
@@ -889,7 +996,7 @@ private final class CallSessionManagerContext {
switch call {
case .phoneCallEmpty:
break
case let .phoneCallAccepted(_, id, _, _, _, _, gB, remoteProtocol, conferenceCall):
case let .phoneCallAccepted(_, id, _, _, _, _, gB, remoteProtocol):
let remoteVersions: [String]
switch remoteProtocol {
case let .phoneCallProtocol(_, _, _, versions):
@@ -903,7 +1010,7 @@ private final class CallSessionManagerContext {
if let context = self.contexts[internalId] {
switch context.state {
case let .requested(_, accessHash, a, gA, config, _, _):
case let .requested(_, accessHash, a, gA, config, _):
let p = config.p.makeData()
if !MTCheckIsSafeGAOrB(self.network.encryptionProvider, gA, p) {
self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
@@ -928,14 +1035,10 @@ private final class CallSessionManagerContext {
let keyVisualHash = MTSha256(key + gA)
context.state = .confirming(id: id, accessHash: accessHash, key: key, keyId: keyId, keyVisualHash: keyVisualHash, conferenceCall: conferenceCall.flatMap(GroupCallReference.init), 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
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 })
if let pendingConference = context.pendingConference {
strongSelf.dropToConference(internalId: internalId, encryptedGroupKey: pendingConference.encryptionKey)
}
} else {
strongSelf.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
}
@@ -949,8 +1052,7 @@ private final class CallSessionManagerContext {
assertionFailure()
}
}
case let .phoneCallDiscarded(flags, id, reason, _, conferenceCall):
let _ = conferenceCall
case let .phoneCallDiscarded(flags, id, reason, _):
let reportRating = (flags & (1 << 2)) != 0
let sendDebugLogs = (flags & (1 << 3)) != 0
if let internalId = self.contextIdByStableId[id] {
@@ -963,47 +1065,39 @@ private final class CallSessionManagerContext {
case .phoneCallDiscardReasonDisconnect:
parsedReason = .error(.disconnected)
case .phoneCallDiscardReasonHangup:
if context.pendingConference != nil {
parsedReason = .ended(.switchedToConference)
} else {
parsedReason = .ended(.hungUp)
}
parsedReason = .ended(.hungUp)
case .phoneCallDiscardReasonMissed:
parsedReason = .ended(.missed)
case .phoneCallDiscardReasonAllowGroupCall:
parsedReason = .ended(.switchedToConference)
case let .phoneCallDiscardReasonMigrateConferenceCall(slug):
parsedReason = .ended(.switchedToConference(slug: slug))
}
} else {
parsedReason = .ended(.hungUp)
}
switch context.state {
case let .accepting(id, accessHash, _, _, _, disposable):
case let .accepting(id, accessHash, _, _, disposable):
disposable.dispose()
context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
self.contextUpdated(internalId: internalId)
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, conferenceCall, _):
if let conferenceCall, case let .phoneCallDiscardReasonAllowGroupCall(encryptedGroupKey) = reason {
context.state = .switchedToConference(key: encryptedGroupKey.makeData(), keyVisualHash: MTSha256(encryptedGroupKey.makeData()), conferenceCall: conferenceCall)
} else {
context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
}
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, _, _, _):
context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
self.contextUpdated(internalId: internalId)
case let .requested(id, accessHash, _, _, _, _, _):
case let .requested(id, accessHash, _, _, _, _):
context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
self.contextUpdated(internalId: internalId)
case let .confirming(id, accessHash, _, _, _, _, disposable):
case let .confirming(id, accessHash, _, _, _, disposable):
disposable.dispose()
context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
self.contextUpdated(internalId: internalId)
case let .requesting(_, _, disposable):
case let .requesting(_, disposable):
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)
@@ -1014,15 +1108,15 @@ private final class CallSessionManagerContext {
//assertionFailure()
}
}
case let .phoneCall(flags, id, _, _, _, _, gAOrB, keyFingerprint, callProtocol, connections, startDate, customParameters, conferenceCall):
case let .phoneCall(flags, id, _, _, _, _, gAOrB, keyFingerprint, callProtocol, connections, startDate, customParameters):
let allowsP2P = (flags & (1 << 5)) != 0
if let internalId = self.contextIdByStableId[id] {
if let context = self.contexts[internalId] {
switch context.state {
case .accepting, .dropping, .requesting, .ringing, .terminated, .requested, .switchedToConference:
break
case let .active(id, accessHash, beginTimestamp, key, keyId, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, _, _):
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: beginTimestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: conferenceCall.flatMap(GroupCallReference.init), willSwitchToConference: context.isIncomingConference)
case let .active(id, accessHash, beginTimestamp, key, keyId, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P):
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: beginTimestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P)
self.contextUpdated(internalId: internalId)
case let .awaitingConfirmation(_, accessHash, gAHash, b, config):
if let (key, calculatedKeyId, keyVisualHash) = self.makeSessionEncryptionKey(config: config, gAHash: gAHash, b: b, gA: gAOrB.makeData()) {
@@ -1041,7 +1135,7 @@ private final class CallSessionManagerContext {
let isVideoPossible = self.videoVersions().contains(where: { versions.contains($0) })
context.isVideoPossible = isVideoPossible
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], customParameters: customParametersValue, allowsP2P: allowsP2P, conferenceCall: conferenceCall.flatMap(GroupCallReference.init), willSwitchToConference: context.isIncomingConference)
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], customParameters: customParametersValue, allowsP2P: allowsP2P)
self.contextUpdated(internalId: internalId)
} else {
self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
@@ -1053,7 +1147,7 @@ private final class CallSessionManagerContext {
} else {
self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
}
case let .confirming(id, accessHash, key, keyId, keyVisualHash, _, _):
case let .confirming(id, accessHash, key, keyId, keyVisualHash, _):
switch callProtocol {
case let .phoneCallProtocol(_, _, maxLayer, versions):
if !versions.isEmpty {
@@ -1068,7 +1162,7 @@ private final class CallSessionManagerContext {
let isVideoPossible = self.videoVersions().contains(where: { versions.contains($0) })
context.isVideoPossible = isVideoPossible
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], customParameters: customParametersValue, allowsP2P: allowsP2P, conferenceCall: conferenceCall.flatMap(GroupCallReference.init), willSwitchToConference: context.isIncomingConference)
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], customParameters: customParametersValue, allowsP2P: allowsP2P)
self.contextUpdated(internalId: internalId)
} else {
self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
@@ -1079,7 +1173,7 @@ private final class CallSessionManagerContext {
assertionFailure()
}
}
case let .phoneCallRequested(flags, id, accessHash, date, adminId, _, gAHash, requestedProtocol, conferenceCall):
case let .phoneCallRequested(flags, id, accessHash, date, adminId, _, gAHash, requestedProtocol):
let isVideo = (flags & (1 << 6)) != 0
let versions: [String]
switch requestedProtocol {
@@ -1087,7 +1181,7 @@ private final class CallSessionManagerContext {
versions = libraryVersions
}
if self.contextIdByStableId[id] == nil {
let internalId = self.addIncoming(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(adminId)), stableId: id, accessHash: accessHash, timestamp: date, gAHash: gAHash.makeData(), versions: versions, isVideo: isVideo, conferenceCall: conferenceCall.flatMap(GroupCallReference.init))
let internalId = self.addIncoming(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(adminId)), stableId: id, accessHash: accessHash, timestamp: date, gAHash: gAHash.makeData(), versions: versions, isVideo: isVideo)
if let internalId = internalId {
var resultRingingStateValue: CallSessionRingingState?
for ringingState in self.ringingStatesValue() {
@@ -1104,13 +1198,13 @@ private final class CallSessionManagerContext {
}
}
}
case let .phoneCallWaiting(_, id, _, _, _, _, _, receiveDate, conferenceCall):
case let .phoneCallWaiting(_, id, _, _, _, _, _, receiveDate):
if let internalId = self.contextIdByStableId[id] {
if let context = self.contexts[internalId] {
switch context.state {
case let .requested(id, accessHash, a, gA, config, remoteConfirmationTimestamp, _):
case let .requested(id, accessHash, a, gA, config, remoteConfirmationTimestamp):
if let receiveDate = receiveDate, remoteConfirmationTimestamp == nil {
context.state = .requested(id: id, accessHash: accessHash, a: a, gA: gA, config: config, remoteConfirmationTimestamp: receiveDate, conferenceCall: conferenceCall.flatMap(GroupCallReference.init))
context.state = .requested(id: id, accessHash: accessHash, a: a, gA: gA, config: config, remoteConfirmationTimestamp: receiveDate)
self.contextUpdated(internalId: internalId)
}
default:
@@ -1144,6 +1238,29 @@ private final class CallSessionManagerContext {
}
}
}
func addConferenceInvitationMessages(ids: [MessageId]) {
for id in ids {
if self.incomingConferenceInvitationContexts[id] == nil {
let context = IncomingConferenceInvitationContext(queue: self.queue, postbox: self.postbox, messageId: id, updated: { [weak self] in
guard let self else {
return
}
if let context = self.incomingConferenceInvitationContexts[id] {
switch context.state {
case .pending:
break
case .ringing:
self.ringingStatesUpdated()
case .stopped:
self.incomingConferenceInvitationContexts.removeValue(forKey: id)
}
}
})
self.incomingConferenceInvitationContexts[id] = context
}
}
}
private func makeSessionEncryptionKey(config: SecretChatEncryptionConfig, gAHash: Data, b: Data, gA: Data) -> (key: Data, keyId: Int64, keyVisualHash: Data)? {
var key = MTExp(self.network.encryptionProvider, gA, b, config.p.makeData())!
@@ -1173,17 +1290,17 @@ private final class CallSessionManagerContext {
return (key, keyId, keyVisualHash)
}
func request(peerId: PeerId, internalId: CallSessionInternalId, isVideo: Bool, enableVideo: Bool, conferenceCall: (conference: GroupCallReference, encryptionKey: Data)?) -> CallSessionInternalId? {
func request(peerId: PeerId, internalId: CallSessionInternalId, isVideo: Bool, enableVideo: Bool) -> CallSessionInternalId? {
let aBytes = malloc(256)!
let randomStatus = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self))
let a = Data(bytesNoCopy: aBytes, count: 256, deallocator: .free)
if randomStatus == 0 {
self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: true, isIncomingConference: false, type: isVideo ? .video : .audio, isVideoPossible: enableVideo || isVideo, pendingConference: conferenceCall, state: .requesting(a: a, conferenceCall: conferenceCall?.conference, disposable: (requestCallSession(postbox: self.postbox, network: self.network, peerId: peerId, a: a, maxLayer: self.maxLayer, versions: self.filteredVersions(enableVideo: true), isVideo: isVideo, conferenceCall: conferenceCall?.conference) |> deliverOn(queue)).start(next: { [weak self] result in
self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: true, type: isVideo ? .video : .audio, isVideoPossible: enableVideo || isVideo, state: .requesting(a: a, disposable: (requestCallSession(postbox: self.postbox, network: self.network, peerId: peerId, a: a, maxLayer: self.maxLayer, versions: self.filteredVersions(enableVideo: true), isVideo: isVideo) |> deliverOn(queue)).start(next: { [weak self] result in
if let strongSelf = self, let context = strongSelf.contexts[internalId] {
if case .requesting = context.state {
switch result {
case let .success(id, accessHash, config, gA, remoteConfirmationTimestamp):
context.state = .requested(id: id, accessHash: accessHash, a: a, gA: gA, config: config, remoteConfirmationTimestamp: remoteConfirmationTimestamp, conferenceCall: conferenceCall?.conference)
context.state = .requested(id: id, accessHash: accessHash, a: a, gA: gA, config: config, remoteConfirmationTimestamp: remoteConfirmationTimestamp)
strongSelf.contextIdByStableId[id] = internalId
strongSelf.contextUpdated(internalId: internalId)
strongSelf.deliverCallSignalingData(id: id)
@@ -1253,6 +1370,12 @@ public final class CallSessionManager {
context.addCallSignalingData(id: id, data: data)
}
}
func addConferenceInvitationMessages(ids: [MessageId]) {
self.withContext { context in
context.addConferenceInvitationMessages(ids: ids)
}
}
public func drop(internalId: CallSessionInternalId, reason: DropCallReason, debugLog: Signal<String?, NoError>) {
self.withContext { context in
@@ -1278,12 +1401,12 @@ public final class CallSessionManager {
}
}
public func request(peerId: PeerId, isVideo: Bool, enableVideo: Bool, conferenceCall: (conference: GroupCallReference, encryptionKey: Data)?, internalId: CallSessionInternalId = CallSessionInternalId()) -> Signal<CallSessionInternalId, NoError> {
public func request(peerId: PeerId, isVideo: Bool, enableVideo: Bool, internalId: CallSessionInternalId = CallSessionInternalId()) -> Signal<CallSessionInternalId, NoError> {
return Signal { [weak self] subscriber in
let disposable = MetaDisposable()
self?.withContext { context in
if let internalId = context.request(peerId: peerId, internalId: internalId, isVideo: isVideo, enableVideo: enableVideo, conferenceCall: conferenceCall) {
if let internalId = context.request(peerId: peerId, internalId: internalId, isVideo: isVideo, enableVideo: enableVideo) {
subscriber.putNext(internalId)
subscriber.putCompletion()
}
@@ -1354,7 +1477,7 @@ public final class CallSessionManager {
private enum AcceptedCall {
case waiting(config: SecretChatEncryptionConfig)
case call(config: SecretChatEncryptionConfig, gA: Data, timestamp: Int32, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool, conferenceCall: GroupCallReference?)
case call(config: SecretChatEncryptionConfig, gA: Data, timestamp: Int32, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool)
}
private enum AcceptCallResult {
@@ -1394,7 +1517,7 @@ private func acceptCallSession(accountPeerId: PeerId, postbox: Postbox, network:
return .failed
case .phoneCallWaiting:
return .success(.waiting(config: config))
case let .phoneCall(flags, id, _, _, _, _, gAOrB, _, callProtocol, connections, startDate, customParameters, conferenceCall):
case let .phoneCall(flags, id, _, _, _, _, gAOrB, _, callProtocol, connections, startDate, customParameters):
if id == stableId {
switch callProtocol{
case let .phoneCallProtocol(_, _, maxLayer, versions):
@@ -1407,7 +1530,7 @@ private func acceptCallSession(accountPeerId: PeerId, postbox: Postbox, network:
customParametersValue = data
}
return .success(.call(config: config, gA: gAOrB.makeData(), timestamp: startDate, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: (flags & (1 << 5)) != 0, conferenceCall: conferenceCall.flatMap(GroupCallReference.init)))
return .success(.call(config: config, gA: gAOrB.makeData(), timestamp: startDate, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: (flags & (1 << 5)) != 0))
} else {
return .failed
}
@@ -1430,7 +1553,7 @@ private enum RequestCallSessionResult {
case failed(CallSessionError)
}
private func requestCallSession(postbox: Postbox, network: Network, peerId: PeerId, a: Data, maxLayer: Int32, versions: [String], isVideo: Bool, conferenceCall: GroupCallReference?) -> Signal<RequestCallSessionResult, NoError> {
private func requestCallSession(postbox: Postbox, network: Network, peerId: PeerId, a: Data, maxLayer: Int32, versions: [String], isVideo: Bool) -> Signal<RequestCallSessionResult, NoError> {
return validatedEncryptionConfig(postbox: postbox, network: network)
|> mapToSignal { config -> Signal<RequestCallSessionResult, NoError> in
return postbox.transaction { transaction -> Signal<RequestCallSessionResult, NoError> in
@@ -1450,18 +1573,15 @@ private func requestCallSession(postbox: Postbox, network: Network, peerId: Peer
if isVideo {
callFlags |= 1 << 0
}
if conferenceCall != nil {
callFlags |= 1 << 1
}
return network.request(Api.functions.phone.requestCall(flags: callFlags, userId: inputUser, conferenceCall: conferenceCall.flatMap { Api.InputGroupCall.inputGroupCall(id: $0.id, accessHash: $0.accessHash) }, randomId: Int32(bitPattern: arc4random()), gAHash: Buffer(data: gAHash), protocol: .phoneCallProtocol(flags: (1 << 0) | (1 << 1), minLayer: minLayer, maxLayer: maxLayer, libraryVersions: versions)))
return network.request(Api.functions.phone.requestCall(flags: callFlags, userId: inputUser, randomId: Int32(bitPattern: arc4random()), gAHash: Buffer(data: gAHash), protocol: .phoneCallProtocol(flags: (1 << 0) | (1 << 1), minLayer: minLayer, maxLayer: maxLayer, libraryVersions: versions)))
|> map { result -> RequestCallSessionResult in
switch result {
case let .phoneCall(phoneCall, _):
switch phoneCall {
case let .phoneCallRequested(_, id, accessHash, _, _, _, _, _, _):
case let .phoneCallRequested(_, id, accessHash, _, _, _, _, _):
return .success(id: id, accessHash: accessHash, config: config, gA: ga, remoteConfirmationTimestamp: nil)
case let .phoneCallWaiting(_, id, accessHash, _, _, _, _, receiveDate, _):
case let .phoneCallWaiting(_, id, accessHash, _, _, _, _, receiveDate):
return .success(id: id, accessHash: accessHash, config: config, gA: ga, remoteConfirmationTimestamp: receiveDate)
default:
return .failed(.generic)
@@ -1514,7 +1634,7 @@ private enum DropCallSessionReason {
case busy
case disconnect
case missed
case switchToConference(encryptedGroupKey: Data)
case switchToConference(slug: String)
}
private func dropCallSession(network: Network, addUpdates: @escaping (Api.Updates) -> Void, stableId: CallSessionStableId, accessHash: Int64, isVideo: Bool, reason: DropCallSessionReason) -> Signal<(Bool, Bool), NoError> {
@@ -1532,8 +1652,8 @@ private func dropCallSession(network: Network, addUpdates: @escaping (Api.Update
mappedReason = .phoneCallDiscardReasonDisconnect
case .missed:
mappedReason = .phoneCallDiscardReasonMissed
case let .switchToConference(encryptedGroupKey):
mappedReason = .phoneCallDiscardReasonAllowGroupCall(encryptedKey: Buffer(data: encryptedGroupKey))
case let .switchToConference(slug):
mappedReason = .phoneCallDiscardReasonMigrateConferenceCall(slug: slug)
}
var callFlags: Int32 = 0
@@ -1556,7 +1676,7 @@ private func dropCallSession(network: Network, addUpdates: @escaping (Api.Update
switch update {
case .updatePhoneCall(let phoneCall):
switch phoneCall {
case let .phoneCallDiscarded(flags, _, _, _, _):
case let .phoneCallDiscarded(flags, _, _, _):
reportRating = (flags & (1 << 2)) != 0
sendDebugLogs = (flags & (1 << 3)) != 0
default:
@@ -1578,34 +1698,3 @@ private func dropCallSession(network: Network, addUpdates: @escaping (Api.Update
}
}
private func createConferenceCall(postbox: Postbox, network: Network, accountPeerId: PeerId, callId: CallId) -> Signal<GroupCallReference?, NoError> {
return network.request(Api.functions.phone.createConferenceCall(peer: .inputPhoneCall(id: callId.id, accessHash: callId.accessHash), keyFingerprint: 1))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.phone.PhoneCall?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<GroupCallReference?, NoError> in
guard let result else {
return .single(nil)
}
return postbox.transaction { transaction -> GroupCallReference? in
switch result {
case let .phoneCall(phoneCall, users):
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: AccumulatedPeers(users: users))
switch phoneCall {
case .phoneCallEmpty, .phoneCallRequested, .phoneCallAccepted, .phoneCallDiscarded:
return nil
case .phoneCallWaiting:
return nil
case let .phoneCall(_, _, _, _, _, _, _, _, _, _, _, _, conferenceCall):
if let conferenceCall {
return GroupCallReference(conferenceCall)
} else {
return nil
}
}
}
}
}
}