no message

This commit is contained in:
Peter 2017-05-02 20:17:10 +03:00
parent 50979d69c3
commit 221c2bf970
8 changed files with 709 additions and 32 deletions

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -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()))

View File

@ -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 {