mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-04 13:38:21 +00:00
no message
This commit is contained in:
parent
50979d69c3
commit
221c2bf970
@ -387,6 +387,7 @@ public class Account {
|
||||
private let serviceQueue = Queue()
|
||||
|
||||
public private(set) var stateManager: AccountStateManager!
|
||||
public private(set) var callSessionManager: CallSessionManager!
|
||||
public private(set) var viewTracker: AccountViewTracker!
|
||||
public private(set) var pendingMessageManager: PendingMessageManager!
|
||||
private var peerInputActivityManager: PeerInputActivityManager!
|
||||
@ -436,6 +437,9 @@ public class Account {
|
||||
|
||||
self.peerInputActivityManager = PeerInputActivityManager()
|
||||
self.stateManager = AccountStateManager(account: self, peerInputActivityManager: self.peerInputActivityManager, auxiliaryMethods: auxiliaryMethods)
|
||||
self.callSessionManager = CallSessionManager(postbox: postbox, network: network, addUpdates: { [weak self] updates in
|
||||
self?.stateManager.addUpdates(updates)
|
||||
})
|
||||
self.localInputActivityManager = PeerInputActivityManager()
|
||||
self.viewTracker = AccountViewTracker(account: self)
|
||||
self.pendingMessageManager = PendingMessageManager(network: network, postbox: postbox, stateManager: self.stateManager)
|
||||
|
||||
@ -69,6 +69,7 @@ enum AccountStateMutationOperation {
|
||||
case UpdateMessageImpressionCount(MessageId, Int32)
|
||||
case UpdateInstalledStickerPacks(AccountStateUpdateStickerPacksOperation)
|
||||
case UpdateChatInputState(PeerId, SynchronizeableChatInputState?)
|
||||
case UpdateCall(Api.PhoneCall)
|
||||
}
|
||||
|
||||
struct AccountMutableState {
|
||||
@ -253,9 +254,13 @@ struct AccountMutableState {
|
||||
self.addOperation(.UpdateChatInputState(peerId, state))
|
||||
}
|
||||
|
||||
mutating func addUpdateCall(_ call: Api.PhoneCall) {
|
||||
self.addOperation(.UpdateCall(call))
|
||||
}
|
||||
|
||||
mutating func addOperation(_ operation: AccountStateMutationOperation) {
|
||||
switch operation {
|
||||
case .AddHole, .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMedia, .ReadOutbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedPeerIds, .ReadGlobalMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateChatInputState:
|
||||
case .AddHole, .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMedia, .ReadOutbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedPeerIds, .ReadGlobalMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateChatInputState, .UpdateCall:
|
||||
break
|
||||
case let .AddMessages(messages, _):
|
||||
for message in messages {
|
||||
@ -318,27 +323,31 @@ struct AccountReplayedFinalState {
|
||||
let addedSecretMessageIds: [MessageId]
|
||||
let updatedTypingActivities: [PeerId: [PeerId: PeerInputActivity?]]
|
||||
let updatedWebpages: [MediaId: TelegramMediaWebpage]
|
||||
let updatedCalls: [Api.PhoneCall]
|
||||
}
|
||||
|
||||
struct AccountFinalStateEvents {
|
||||
let addedIncomingMessageIds: [MessageId]
|
||||
let updatedTypingActivities: [PeerId: [PeerId: PeerInputActivity?]]
|
||||
let updatedWebpages: [MediaId: TelegramMediaWebpage]
|
||||
let updatedCalls: [Api.PhoneCall]
|
||||
|
||||
var isEmpty: Bool {
|
||||
return self.addedIncomingMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty
|
||||
return self.addedIncomingMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty
|
||||
}
|
||||
|
||||
init() {
|
||||
self.addedIncomingMessageIds = []
|
||||
self.updatedTypingActivities = [:]
|
||||
self.updatedWebpages = [:]
|
||||
self.updatedCalls = []
|
||||
}
|
||||
|
||||
init(addedIncomingMessageIds: [MessageId], updatedTypingActivities: [PeerId: [PeerId: PeerInputActivity?]], updatedWebpages: [MediaId: TelegramMediaWebpage]) {
|
||||
init(addedIncomingMessageIds: [MessageId], updatedTypingActivities: [PeerId: [PeerId: PeerInputActivity?]], updatedWebpages: [MediaId: TelegramMediaWebpage], updatedCalls: [Api.PhoneCall]) {
|
||||
self.addedIncomingMessageIds = addedIncomingMessageIds
|
||||
self.updatedTypingActivities = updatedTypingActivities
|
||||
self.updatedWebpages = updatedWebpages
|
||||
self.updatedCalls = updatedCalls
|
||||
}
|
||||
|
||||
init(state: AccountReplayedFinalState) {
|
||||
@ -361,9 +370,10 @@ struct AccountFinalStateEvents {
|
||||
self.addedIncomingMessageIds = addedIncomingMessageIds
|
||||
self.updatedTypingActivities = state.updatedTypingActivities
|
||||
self.updatedWebpages = state.updatedWebpages
|
||||
self.updatedCalls = state.updatedCalls
|
||||
}
|
||||
|
||||
func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents {
|
||||
return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages)
|
||||
return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls)
|
||||
}
|
||||
}
|
||||
|
||||
@ -995,6 +995,8 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState,
|
||||
inputState = SynchronizeableChatInputState(replyToMessageId: replyToMessageId, text: message, timestamp: date)
|
||||
}
|
||||
updatedState.addUpdateChatInputState(peerId: peer.peerId, state: inputState)
|
||||
case let .updatePhoneCall(phoneCall):
|
||||
updatedState.addUpdateCall(phoneCall)
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -1369,7 +1371,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
|
||||
var currentAddMessages: OptimizeAddMessagesState?
|
||||
for operation in operations {
|
||||
switch operation {
|
||||
case .AddHole, .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ResetReadState, .UpdatePeerNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedPeerIds, .ReadGlobalMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateChatInputState:
|
||||
case .AddHole, .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ResetReadState, .UpdatePeerNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedPeerIds, .ReadGlobalMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateChatInputState, .UpdateCall:
|
||||
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
|
||||
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
|
||||
}
|
||||
@ -1416,6 +1418,7 @@ func replayFinalState(accountPeerId: PeerId, mediaBox: MediaBox, modifier: Modif
|
||||
var updatedTypingActivities: [PeerId: [PeerId: PeerInputActivity?]] = [:]
|
||||
var updatedSecretChatTypingActivities = Set<PeerId>()
|
||||
var updatedWebpages: [MediaId: TelegramMediaWebpage] = [:]
|
||||
var updatedCalls: [Api.PhoneCall] = []
|
||||
|
||||
var stickerPackOperations: [AccountStateUpdateStickerPacksOperation] = []
|
||||
|
||||
@ -1569,6 +1572,8 @@ func replayFinalState(accountPeerId: PeerId, mediaBox: MediaBox, modifier: Modif
|
||||
modifier.updatePeerChatInterfaceState(peerId, update: { current in
|
||||
return auxiliaryMethods.updatePeerChatInputState(current, inputState)
|
||||
})
|
||||
case let .UpdateCall(call):
|
||||
updatedCalls.append(call)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1742,5 +1747,5 @@ func replayFinalState(accountPeerId: PeerId, mediaBox: MediaBox, modifier: Modif
|
||||
}
|
||||
}
|
||||
|
||||
return AccountReplayedFinalState(state: finalState, addedSecretMessageIds: addedSecretMessageIds, updatedTypingActivities: updatedTypingActivities, updatedWebpages: updatedWebpages)
|
||||
return AccountReplayedFinalState(state: finalState, addedSecretMessageIds: addedSecretMessageIds, updatedTypingActivities: updatedTypingActivities, updatedWebpages: updatedWebpages, updatedCalls: updatedCalls)
|
||||
}
|
||||
|
||||
@ -430,6 +430,11 @@ public final class AccountStateManager {
|
||||
if !events.updatedWebpages.isEmpty {
|
||||
strongSelf.notifyUpdatedWebpages(events.updatedWebpages)
|
||||
}
|
||||
if !events.updatedCalls.isEmpty {
|
||||
for call in events.updatedCalls {
|
||||
strongSelf.account.callSessionManager.updateSession(call)
|
||||
}
|
||||
}
|
||||
strongSelf.operations.removeFirst()
|
||||
var pollCount = 0
|
||||
for i in 0 ..< strongSelf.operations.count {
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
enum CallSessionState {
|
||||
case requested(a: MemoryBuffer, config: SecretChatEncryptionConfig)
|
||||
case accepting(gAHash: MemoryBuffer, b: MemoryBuffer, config: SecretChatEncryptionConfig)
|
||||
case confirming(a: MemoryBuffer, gB: MemoryBuffer, config: SecretChatEncryptionConfig)
|
||||
case active
|
||||
}
|
||||
|
||||
|
||||
@ -9,27 +9,92 @@ import Foundation
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
private struct CallSessionId: Hashable {
|
||||
let id: Int64
|
||||
enum CallSessionInternalState {
|
||||
case ringing(id: Int64, accessHash: Int64, gAHash: Data, b: Data)
|
||||
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, keyFingerprint: SecretChatKeyFingerprint, disposable: Disposable)
|
||||
case active(id: Int64, accessHash: Int64, beginTimestamp: Int32, key: Data, keyId: Int64, keyFingerprint: SecretChatKeyFingerprint, connections: CallSessionConnectionSet)
|
||||
case dropping(Disposable)
|
||||
case terminated
|
||||
}
|
||||
|
||||
public typealias CallSessionInternalId = Int64
|
||||
typealias CallSessionStableId = Int64
|
||||
|
||||
public struct CallSessionRingingState {
|
||||
public let id: CallSessionInternalId
|
||||
public let peerId: PeerId
|
||||
}
|
||||
|
||||
public enum CallSessionState {
|
||||
case ringing
|
||||
case accepting
|
||||
case requesting(ringing: Bool)
|
||||
case active(SecretChatKeyFingerprint, CallSessionConnectionSet)
|
||||
case dropping
|
||||
case terminated
|
||||
|
||||
init(_ id: Int64) {
|
||||
self.id = id
|
||||
fileprivate init(_ context: CallSessionContext) {
|
||||
switch context.state {
|
||||
case .ringing:
|
||||
self = .ringing
|
||||
case .accepting, .awaitingConfirmation:
|
||||
self = .accepting
|
||||
case .requesting, .confirming:
|
||||
self = .requesting(ringing: false)
|
||||
case let .requested(_, _, _, _, _, remoteConfirmationTimestamp):
|
||||
self = .requesting(ringing: remoteConfirmationTimestamp != nil)
|
||||
case let .active(_, _, _, _, _, keyFingerprint, connections):
|
||||
self = .active(keyFingerprint, connections)
|
||||
case .dropping:
|
||||
self = .dropping
|
||||
case .terminated:
|
||||
self = .terminated
|
||||
}
|
||||
}
|
||||
|
||||
var hashValue: Int {
|
||||
return self.id.hashValue
|
||||
}
|
||||
|
||||
static func ==(lhs: CallSessionId, rhs: CallSessionId) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
}
|
||||
|
||||
public struct CallSessionConnection {
|
||||
public let id: Int64
|
||||
public let ip: String
|
||||
public let ipv6: String
|
||||
public let port: Int32
|
||||
public let peerTag: Data
|
||||
}
|
||||
|
||||
private func parseConnection(_ apiConnection: Api.PhoneConnection) -> CallSessionConnection {
|
||||
switch apiConnection {
|
||||
case let .phoneConnection(id, ip, ipv6, port, peerTag):
|
||||
return CallSessionConnection(id: id, ip: ip, ipv6: ipv6, port: port, peerTag: peerTag.makeData())
|
||||
}
|
||||
}
|
||||
|
||||
public struct CallSessionConnectionSet {
|
||||
public let primary: CallSessionConnection
|
||||
public let alternatives: [CallSessionConnection]
|
||||
}
|
||||
|
||||
private func parseConnectionSet(primary: Api.PhoneConnection, alternative: [Api.PhoneConnection]) -> CallSessionConnectionSet {
|
||||
return CallSessionConnectionSet(primary: parseConnection(primary), alternatives: alternative.map { parseConnection($0) })
|
||||
}
|
||||
|
||||
private final class CallSessionContext {
|
||||
let peerId: PeerId
|
||||
var state: CallSessionState
|
||||
var state: CallSessionInternalState
|
||||
let subscribers = Bag<(CallSessionState) -> Void>()
|
||||
|
||||
init(peerId: PeerId, state: CallSessionState) {
|
||||
var isEmpty: Bool {
|
||||
if case .terminated = self.state {
|
||||
return self.subscribers.isEmpty
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
init(peerId: PeerId, state: CallSessionInternalState) {
|
||||
self.peerId = peerId
|
||||
self.state = state
|
||||
}
|
||||
@ -37,27 +102,400 @@ private final class CallSessionContext {
|
||||
|
||||
private final class CallSessionManagerContext {
|
||||
private let queue: Queue
|
||||
private let postbox: Postbox
|
||||
private let network: Network
|
||||
private let addUpdates: (Api.Updates) -> Void
|
||||
|
||||
private var contexts: [CallSessionId: CallSessionContext] = [:]
|
||||
private let ringingSubscribers = Bag<([CallSessionRingingState]) -> Void>()
|
||||
private var nextId: CallSessionInternalId = 0
|
||||
private var contexts: [CallSessionInternalId: CallSessionContext] = [:]
|
||||
private var contextIdByStableId: [CallSessionStableId: Int64] = [:]
|
||||
|
||||
init(queue: Queue) {
|
||||
private let disposables = DisposableSet()
|
||||
|
||||
init(queue: Queue, postbox: Postbox, network: Network, addUpdates: @escaping (Api.Updates) -> Void) {
|
||||
self.queue = queue
|
||||
self.postbox = postbox
|
||||
self.network = network
|
||||
self.addUpdates = addUpdates
|
||||
}
|
||||
|
||||
deinit {
|
||||
assert(self.queue.isCurrent())
|
||||
self.disposables.dispose()
|
||||
}
|
||||
|
||||
func ringingStates() -> Signal<[CallSessionRingingState], NoError> {
|
||||
let queue = self.queue
|
||||
return Signal { [weak self] subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
queue.async {
|
||||
if let strongSelf = self {
|
||||
let index = strongSelf.ringingSubscribers.add { next in
|
||||
subscriber.putNext(next)
|
||||
}
|
||||
disposable.set(ActionDisposable {
|
||||
queue.async {
|
||||
if let strongSelf = self {
|
||||
strongSelf.ringingSubscribers.remove(index)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
func callState(internalId: CallSessionInternalId) -> Signal<CallSessionState, NoError> {
|
||||
let queue = self.queue
|
||||
return Signal { [weak self] subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
queue.async {
|
||||
if let strongSelf = self, let context = strongSelf.contexts[internalId] {
|
||||
let index = context.subscribers.add { next in
|
||||
subscriber.putNext(next)
|
||||
}
|
||||
disposable.set(ActionDisposable {
|
||||
queue.async {
|
||||
if let strongSelf = self, let context = strongSelf.contexts[internalId] {
|
||||
context.subscribers.remove(index)
|
||||
if context.isEmpty {
|
||||
strongSelf.contexts.removeValue(forKey: internalId)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
private func ringingStatesUpdated() {
|
||||
var ringingContexts: [CallSessionRingingState] = []
|
||||
for (id, context) in self.contexts {
|
||||
if case .ringing = context.state {
|
||||
ringingContexts.append(CallSessionRingingState(id: id, peerId: context.peerId))
|
||||
}
|
||||
}
|
||||
for subscriber in self.ringingSubscribers.copyItems() {
|
||||
subscriber(ringingContexts)
|
||||
}
|
||||
}
|
||||
|
||||
private func contextUpdated(internalId: CallSessionInternalId) {
|
||||
if let context = self.contexts[internalId] {
|
||||
let session = CallSessionState(context)
|
||||
for subscriber in context.subscribers.copyItems() {
|
||||
subscriber(session)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func addIncoming(peerId: PeerId, stableId: CallSessionStableId, accessHash: Int64, timestamp: Int32, gAHash: Data) {
|
||||
if self.contextIdByStableId[stableId] != nil {
|
||||
return
|
||||
}
|
||||
|
||||
self.nextId += 1
|
||||
|
||||
let bBytes = malloc(256)!
|
||||
let randomStatus = SecRandomCopyBytes(nil, 256, bBytes.assumingMemoryBound(to: UInt8.self))
|
||||
let b = Data(bytesNoCopy: bBytes, count: 256, deallocator: .free)
|
||||
|
||||
if randomStatus == 0 {
|
||||
let internalId = self.nextId
|
||||
self.contexts[internalId] = CallSessionContext(peerId: peerId, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b))
|
||||
self.contextIdByStableId[stableId] = internalId
|
||||
self.contextUpdated(internalId: internalId)
|
||||
self.ringingStatesUpdated()
|
||||
|
||||
self.queue.after(3.0, { [weak self] in
|
||||
self?.accept(internalId: internalId)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func drop(internalId: CallSessionInternalId) {
|
||||
if let context = self.contexts[internalId] {
|
||||
var dropData: (CallSessionStableId, Int64, DropCallSessionReason)?
|
||||
var wasRinging = false
|
||||
switch context.state {
|
||||
case let .ringing(id, accessHash, _, _):
|
||||
wasRinging = true
|
||||
dropData = (id, accessHash, .abort)
|
||||
case let .accepting(id, accessHash, _, _, disposable):
|
||||
dropData = (id, accessHash, .abort)
|
||||
disposable.dispose()
|
||||
case let .active(id, accessHash, beginTimestamp, _, _, _, _):
|
||||
let duration = max(0, Int32(CFAbsoluteTimeGetCurrent()) - beginTimestamp)
|
||||
dropData = (id, accessHash, .hangUp(duration))
|
||||
case .dropping, .terminated:
|
||||
break
|
||||
case let .awaitingConfirmation(id, accessHash, _, _, _):
|
||||
dropData = (id, accessHash, .abort)
|
||||
case let .confirming(id, accessHash, _, _, _, disposable):
|
||||
disposable.dispose()
|
||||
dropData = (id, accessHash, .abort)
|
||||
case let .requested(id, accessHash, _, _, _, _):
|
||||
dropData = (id, accessHash, .abort)
|
||||
case let .requesting(_, disposable):
|
||||
disposable.dispose()
|
||||
context.state = .terminated
|
||||
self.contextUpdated(internalId: internalId)
|
||||
if context.isEmpty {
|
||||
self.contexts.removeValue(forKey: internalId)
|
||||
}
|
||||
}
|
||||
|
||||
if let (id, accessHash, reason) = dropData {
|
||||
self.contextIdByStableId.removeValue(forKey: id)
|
||||
context.state = .dropping((dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, reason: reason) |> deliverOn(self.queue)).start(completed: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if let context = strongSelf.contexts[internalId] {
|
||||
context.state = .terminated
|
||||
strongSelf.contextUpdated(internalId: internalId)
|
||||
if context.isEmpty {
|
||||
strongSelf.contexts.removeValue(forKey: internalId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
self.contextUpdated(internalId: internalId)
|
||||
if wasRinging {
|
||||
self.ringingStatesUpdated()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func drop(stableId: CallSessionStableId) {
|
||||
if let internalId = self.contextIdByStableId[stableId] {
|
||||
self.contextIdByStableId.removeValue(forKey: stableId)
|
||||
self.drop(internalId: internalId)
|
||||
}
|
||||
}
|
||||
|
||||
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) |> 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 {
|
||||
case .failed:
|
||||
strongSelf.drop(internalId: internalId)
|
||||
case let .success(call):
|
||||
switch call {
|
||||
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):
|
||||
if let (key, keyId, keyFingerprint) = strongSelf.makeSessionEncryptionKey(config: config, gAHash: gAHash, b: b, gA: gA) {
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: timestamp, key: key, keyId: keyId, keyFingerprint: keyFingerprint, connections: connections)
|
||||
strongSelf.contextUpdated(internalId: internalId)
|
||||
} else {
|
||||
strongSelf.drop(internalId: internalId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
self.contextUpdated(internalId: internalId)
|
||||
self.ringingStatesUpdated()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateSession(_ call: Api.PhoneCall) {
|
||||
switch call {
|
||||
case .phoneCallEmpty:
|
||||
break
|
||||
case let .phoneCallAccepted(id, _, date, _, _, gB, `protocol`):
|
||||
if let internalId = self.contextIdByStableId[id] {
|
||||
let context = self.contexts[internalId]!
|
||||
switch context.state {
|
||||
case let .requested(_, accessHash, a, gA, config, _):
|
||||
var key = MTExp(gB.makeData(), a, config.p.makeData())!
|
||||
|
||||
if key.count > 256 {
|
||||
key.count = 256
|
||||
} else {
|
||||
while key.count < 256 {
|
||||
key.insert(0, at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
let keyHash = MTSha1(key)!
|
||||
|
||||
var keyId: Int64 = 0
|
||||
keyHash.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
||||
memcpy(&keyId, bytes.advanced(by: keyHash.count - 8), 8)
|
||||
}
|
||||
|
||||
let keyFingerprint = SecretChatKeyFingerprint(sha1: SecretChatKeySha1Fingerprint(digest: sha1Digest(key)), sha256: SecretChatKeySha256Fingerprint(digest: sha256Digest(key)))
|
||||
|
||||
context.state = .confirming(id: id, accessHash: accessHash, key: key, keyId: keyId, keyFingerprint: keyFingerprint, disposable: (confirmCallSession(network: self.network, stableId: id, accessHash: accessHash, gA: gA, keyFingerprint: keyId) |> 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)
|
||||
} else {
|
||||
strongSelf.drop(internalId: internalId)
|
||||
}
|
||||
}
|
||||
}))
|
||||
self.contextUpdated(internalId: internalId)
|
||||
default:
|
||||
self.drop(internalId: internalId)
|
||||
}
|
||||
}
|
||||
case let .phoneCallDiscarded(_, id, reason, duration):
|
||||
if let internalId = self.contextIdByStableId[id] {
|
||||
let context = self.contexts[internalId]!
|
||||
switch context.state {
|
||||
case let .accepting(_, _, _, _, disposable):
|
||||
disposable.dispose()
|
||||
context.state = .terminated
|
||||
self.contextUpdated(internalId: internalId)
|
||||
case .active:
|
||||
context.state = .terminated
|
||||
self.contextUpdated(internalId: internalId)
|
||||
case .awaitingConfirmation, .requested:
|
||||
context.state = .terminated
|
||||
self.contextUpdated(internalId: internalId)
|
||||
case let .confirming(_, _, _, _, _, disposable):
|
||||
disposable.dispose()
|
||||
context.state = .terminated
|
||||
self.contextUpdated(internalId: internalId)
|
||||
case let .requesting(_, disposable):
|
||||
disposable.dispose()
|
||||
context.state = .terminated
|
||||
self.contextUpdated(internalId: internalId)
|
||||
case .ringing:
|
||||
context.state = .terminated
|
||||
self.ringingStatesUpdated()
|
||||
self.contextUpdated(internalId: internalId)
|
||||
case .dropping, .terminated:
|
||||
break
|
||||
}
|
||||
}
|
||||
case let .phoneCall(id, _, _, _, _, gAOrB, keyFingerprint, _, connection, alternativeConnections, startDate):
|
||||
if let internalId = self.contextIdByStableId[id] {
|
||||
let context = self.contexts[internalId]!
|
||||
switch context.state {
|
||||
case .accepting, .active, .dropping, .requesting, .ringing, .terminated, .requested:
|
||||
break
|
||||
case let .awaitingConfirmation(_, accessHash, gAHash, b, config):
|
||||
if let (key, calculatedKeyId, calculatedKeyFingerprint) = self.makeSessionEncryptionKey(config: config, gAHash: gAHash, b: b, gA: gAOrB.makeData()) {
|
||||
if keyFingerprint == calculatedKeyId {
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: calculatedKeyId, keyFingerprint: calculatedKeyFingerprint, connections: parseConnectionSet(primary: connection, alternative: alternativeConnections))
|
||||
self.contextUpdated(internalId: internalId)
|
||||
} else {
|
||||
self.drop(internalId: internalId)
|
||||
}
|
||||
} else {
|
||||
self.drop(internalId: internalId)
|
||||
}
|
||||
case let .confirming(id, accessHash, key, keyId, keyFingerprint, _):
|
||||
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: keyId, keyFingerprint: keyFingerprint, connections: parseConnectionSet(primary: connection, alternative: alternativeConnections))
|
||||
self.contextUpdated(internalId: internalId)
|
||||
}
|
||||
}
|
||||
case let .phoneCallRequested(id, accessHash, date, adminId, _, gAHash, _):
|
||||
if self.contextIdByStableId[id] == nil {
|
||||
self.addIncoming(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: adminId), stableId: id, accessHash: accessHash, timestamp: date, gAHash: gAHash.makeData())
|
||||
}
|
||||
case let .phoneCallWaiting(_, id, _, _, _, _, _, receiveDate):
|
||||
if let internalId = self.contextIdByStableId[id] {
|
||||
let context = self.contexts[internalId]!
|
||||
switch context.state {
|
||||
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)
|
||||
self.contextUpdated(internalId: internalId)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func makeSessionEncryptionKey(config: SecretChatEncryptionConfig, gAHash: Data, b: Data, gA: Data) -> (key: Data, keyId: Int64, keyFingerprint: SecretChatKeyFingerprint)? {
|
||||
var key = MTExp(gA, b, config.p.makeData())!
|
||||
|
||||
if key.count > 256 {
|
||||
key.count = 256
|
||||
} else {
|
||||
while key.count < 256 {
|
||||
key.insert(0, at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
let keyHash = MTSha1(key)!
|
||||
|
||||
var keyId: Int64 = 0
|
||||
keyHash.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
||||
memcpy(&keyId, bytes.advanced(by: keyHash.count - 8), 8)
|
||||
}
|
||||
|
||||
if MTSha256(gA)! != gAHash {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (key, keyId, SecretChatKeyFingerprint(sha1: SecretChatKeySha1Fingerprint(digest: sha1Digest(key)), sha256: SecretChatKeySha256Fingerprint(digest: sha256Digest(key))))
|
||||
}
|
||||
|
||||
func request(peerId: PeerId) -> 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.nextId += 1
|
||||
|
||||
let internalId = self.nextId
|
||||
self.contexts[internalId] = CallSessionContext(peerId: peerId, state: .requesting(a: a, disposable: (requestCallSession(postbox: self.postbox, network: self.network, peerId: peerId, a: a) |> 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):
|
||||
context.state = .requested(id: id, accessHash: accessHash, a: a, gA: gA, config: config, remoteConfirmationTimestamp: nil)
|
||||
strongSelf.contextIdByStableId[id] = internalId
|
||||
strongSelf.contextUpdated(internalId: internalId)
|
||||
case .failed:
|
||||
context.state = .terminated
|
||||
strongSelf.contextUpdated(internalId: internalId)
|
||||
if context.isEmpty {
|
||||
strongSelf.contexts.removeValue(forKey: internalId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})))
|
||||
self.contextUpdated(internalId: internalId)
|
||||
return internalId
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class CallSessionManager {
|
||||
public enum CallRequestError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public final class CallSessionManager {
|
||||
private let queue = Queue()
|
||||
private var contextRef: Unmanaged<CallSessionManagerContext>?
|
||||
|
||||
init() {
|
||||
init(postbox: Postbox, network: Network, addUpdates: @escaping (Api.Updates) -> Void) {
|
||||
self.queue.async {
|
||||
let context = CallSessionManagerContext(queue: self.queue)
|
||||
let context = CallSessionManagerContext(queue: self.queue, postbox: postbox, network: network, addUpdates: addUpdates)
|
||||
self.contextRef = Unmanaged.passRetained(context)
|
||||
}
|
||||
}
|
||||
@ -77,4 +515,223 @@ final class CallSessionManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateSession(_ call: Api.PhoneCall) {
|
||||
self.withContext { context in
|
||||
context.updateSession(call)
|
||||
}
|
||||
}
|
||||
|
||||
public func drop(internalId: CallSessionInternalId) {
|
||||
self.withContext { context in
|
||||
context.drop(internalId: internalId)
|
||||
}
|
||||
}
|
||||
|
||||
func drop(stableId: CallSessionStableId) {
|
||||
self.withContext { context in
|
||||
context.drop(stableId: stableId)
|
||||
}
|
||||
}
|
||||
|
||||
func accept(internalId: CallSessionInternalId) {
|
||||
self.withContext { context in
|
||||
context.accept(internalId: internalId)
|
||||
}
|
||||
}
|
||||
|
||||
public func request(peerId: PeerId) -> Signal<CallSessionInternalId, CallRequestError> {
|
||||
let queue = self.queue
|
||||
return Signal { [weak self] subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
queue.async {
|
||||
self?.withContext { context in
|
||||
if let internalId = context.request(peerId: peerId) {
|
||||
subscriber.putNext(internalId)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
public func ringingStates() -> Signal<[CallSessionRingingState], NoError> {
|
||||
return Signal { [weak self] subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self?.withContext { context in
|
||||
disposable.set(context.ringingStates().start(next: { next in
|
||||
subscriber.putNext(next)
|
||||
}))
|
||||
}
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
public func callState(internalId: CallSessionInternalId) -> Signal<CallSessionState, NoError> {
|
||||
return Signal { [weak self] subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self?.withContext { context in
|
||||
disposable.set(context.callState(internalId: internalId).start(next: { next in
|
||||
subscriber.putNext(next)
|
||||
}))
|
||||
}
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum AcceptedCall {
|
||||
case waiting(config: SecretChatEncryptionConfig)
|
||||
case call(config: SecretChatEncryptionConfig, gA: Data, timestamp: Int32, connections: CallSessionConnectionSet)
|
||||
}
|
||||
|
||||
private enum AcceptCallResult {
|
||||
case failed
|
||||
case success(AcceptedCall)
|
||||
}
|
||||
|
||||
private func acceptCallSession(postbox: Postbox, network: Network, stableId: CallSessionStableId, accessHash: Int64, b: Data) -> Signal<AcceptCallResult, NoError> {
|
||||
return validatedEncryptionConfig(postbox: postbox, network: network)
|
||||
|> mapToSignal { config in
|
||||
var gValue: Int32 = config.g.byteSwapped
|
||||
let g = Data(bytes: &gValue, count: 4)
|
||||
let p = config.p.makeData()
|
||||
|
||||
let bData = b
|
||||
|
||||
let gb = MTExp(g, bData, p)!
|
||||
|
||||
return network.request(Api.functions.phone.acceptCall(peer: .inputPhoneCall(id: stableId, accessHash: accessHash), gB: Buffer(data: gb), protocol: .phoneCallProtocol(flags: (1 << 0) | (1 << 1), minLayer: 65, maxLayer: 66)))
|
||||
|> map { Optional($0) }
|
||||
|> `catch` { _ -> Signal<Api.phone.PhoneCall?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { call -> Signal<AcceptCallResult, NoError> in
|
||||
if let call = call {
|
||||
return postbox.modify { modifier -> AcceptCallResult in
|
||||
switch call {
|
||||
case let .phoneCall(phoneCall, users):
|
||||
var parsedUsers: [Peer] = []
|
||||
for user in users {
|
||||
parsedUsers.append(TelegramUser(user: user))
|
||||
}
|
||||
updatePeers(modifier: modifier, peers: parsedUsers, update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
|
||||
switch phoneCall {
|
||||
case .phoneCallEmpty, .phoneCallRequested, .phoneCallAccepted, .phoneCallDiscarded:
|
||||
return .failed
|
||||
case .phoneCallWaiting:
|
||||
return .success(.waiting(config: config))
|
||||
case let .phoneCall(id, _, _, _, _, gAOrB, keyFingerprint, `protocol`, connection, alternativeConnections, startDate):
|
||||
if id == stableId {
|
||||
return .success(.call(config: config, gA: gAOrB.makeData(), timestamp: startDate, connections: parseConnectionSet(primary: connection, alternative: alternativeConnections)))
|
||||
} else {
|
||||
return .failed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(.failed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum RequestCallSessionResult {
|
||||
case success(id: CallSessionStableId, accessHash: Int64, config: SecretChatEncryptionConfig, gA: Data)
|
||||
case failed
|
||||
}
|
||||
|
||||
private func requestCallSession(postbox: Postbox, network: Network, peerId: PeerId, a: Data) -> Signal<RequestCallSessionResult, NoError> {
|
||||
return validatedEncryptionConfig(postbox: postbox, network: network)
|
||||
|> mapToSignal { config -> Signal<RequestCallSessionResult, NoError> in
|
||||
return postbox.modify { modifier -> Signal<RequestCallSessionResult, NoError> in
|
||||
if let peer = modifier.getPeer(peerId), let inputUser = apiInputUser(peer) {
|
||||
var gValue: Int32 = config.g.byteSwapped
|
||||
let g = Data(bytes: &gValue, count: 4)
|
||||
let p = config.p.makeData()
|
||||
|
||||
let ga = MTExp(g, a, p)!
|
||||
|
||||
let gAHash = MTSha256(ga)!
|
||||
|
||||
return network.request(Api.functions.phone.requestCall(userId: inputUser, randomId: Int32(bitPattern: arc4random()), gAHash: Buffer(data: gAHash), protocol: .phoneCallProtocol(flags: (1 << 0) | (1 << 1), minLayer: 65, maxLayer: 66)))
|
||||
|> map { Optional($0) }
|
||||
|> `catch` { _ -> Signal<Api.phone.PhoneCall?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> map { result -> RequestCallSessionResult in
|
||||
if let result = result {
|
||||
switch result {
|
||||
case let .phoneCall(phoneCall, _):
|
||||
switch phoneCall {
|
||||
case let .phoneCallRequested(id, accessHash, _, _, _, _, _):
|
||||
return .success(id: id, accessHash: accessHash, config: config, gA: ga)
|
||||
case let .phoneCallWaiting(_, id, accessHash, date, _, _, _, receiveDate):
|
||||
return .success(id: id, accessHash: accessHash, config: config, gA: ga)
|
||||
default:
|
||||
return .failed
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .failed
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(.failed)
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
}
|
||||
|
||||
private func confirmCallSession(network: Network, stableId: CallSessionStableId, accessHash: Int64, gA: Data, keyFingerprint: Int64) -> Signal<Api.PhoneCall?, NoError> {
|
||||
return network.request(Api.functions.phone.confirmCall(peer: Api.InputPhoneCall.inputPhoneCall(id: stableId, accessHash: accessHash), gA: Buffer(data: gA), keyFingerprint: keyFingerprint, protocol: .phoneCallProtocol(flags: (1 << 0) | (1 << 1), minLayer: 65, maxLayer: 66)))
|
||||
|> map { Optional($0) }
|
||||
|> `catch` { _ -> Signal<Api.phone.PhoneCall?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> map { result -> Api.PhoneCall? in
|
||||
if let result = result {
|
||||
switch result {
|
||||
case let .phoneCall(phoneCall, _):
|
||||
return phoneCall
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum DropCallSessionReason {
|
||||
case abort
|
||||
case hangUp(Int32)
|
||||
}
|
||||
|
||||
private func dropCallSession(network: Network, addUpdates: @escaping (Api.Updates) -> Void, stableId: CallSessionStableId, accessHash: Int64, reason: DropCallSessionReason) -> Signal<Void, NoError> {
|
||||
var mappedReason: Api.PhoneCallDiscardReason
|
||||
var duration: Int32 = 0
|
||||
switch reason {
|
||||
case .abort:
|
||||
mappedReason = .phoneCallDiscardReasonHangup
|
||||
case let .hangUp(value):
|
||||
duration = value
|
||||
mappedReason = .phoneCallDiscardReasonHangup
|
||||
}
|
||||
return network.request(Api.functions.phone.discardCall(peer: Api.InputPhoneCall.inputPhoneCall(id: stableId, accessHash: accessHash), duration: duration, reason: mappedReason, connectionId: 0))
|
||||
|> map { Optional($0) }
|
||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<Void, NoError> in
|
||||
if let updates = updates {
|
||||
addUpdates(updates)
|
||||
}
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ func fetchResource(account: Account, resource: MediaResource, range: Range<Int>,
|
||||
} else if let secretFileResource = resource as? SecretFileMediaResource {
|
||||
return .single(.dataPart(data: Data(), range: 0 ..< 0, complete: false)) |> then(fetchSecretFileResource(account: account, resource: secretFileResource, range: range, tag: tag))
|
||||
} else if let cloudResource = resource as? TelegramCloudMediaResource {
|
||||
return .single(.dataPart(data: Data(), range: 0 ..< 0, complete: false)) |> then(fetchCloudMediaLocation(account: account, resource: cloudResource, size: resource.size, range: range, tag: tag))
|
||||
return .single(.dataPart(data: Data(), range: 0 ..< 0, complete: false)) |> then(fetchCloudMediaLocation(account: account, resource: cloudResource, size: resource.size == 0 ? nil : resource.size, range: range, tag: tag))
|
||||
} else if let localFileResource = resource as? LocalFileReferenceMediaResource {
|
||||
if false {
|
||||
//return .single(.dataPart(data: Data(), range: 0 ..< 0, complete: false)) |> then(fetchLocalFileResource(path: localFileResource.localFilePath) |> delay(10.0, queue: Queue.concurrentDefaultQueue()))
|
||||
|
||||
@ -48,6 +48,7 @@ public struct Namespaces {
|
||||
public static let CloudRecentInlineBots: Int32 = 3
|
||||
public static let CloudFeaturedStickerPacks: Int32 = 4
|
||||
public static let CloudArchivedStickerPacks: Int32 = 5
|
||||
public static let CloudWallpapers: Int32 = 6
|
||||
}
|
||||
|
||||
struct CachedItemCollection {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user