mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-17 11:00:07 +00:00

Added info for restricted channels Fixed media pre-upload for secret chats Added unread counter tags Fixed file and webpage cached resource collection Added contact registered notification
1383 lines
85 KiB
Swift
1383 lines
85 KiB
Swift
import Foundation
|
|
#if os(macOS)
|
|
import PostboxMac
|
|
import SwiftSignalKitMac
|
|
import MtProtoKitMac
|
|
#else
|
|
import Postbox
|
|
import SwiftSignalKit
|
|
import MtProtoKitDynamic
|
|
#endif
|
|
|
|
func addSecretChatOutgoingOperation(transaction: Transaction, peerId: PeerId, operation: SecretChatOutgoingOperationContents, state: SecretChatState) -> SecretChatState {
|
|
var updatedState = state
|
|
switch updatedState.embeddedState {
|
|
case let .sequenceBasedLayer(sequenceState):
|
|
let keyValidityOperationIndex = transaction.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing)
|
|
let keyValidityOperationCanonicalIndex = sequenceState.canonicalIncomingOperationIndex(keyValidityOperationIndex)
|
|
if let key = state.keychain.latestKey(validForSequenceBasedCanonicalIndex: keyValidityOperationCanonicalIndex) {
|
|
updatedState = updatedState.withUpdatedKeychain(updatedState.keychain.withUpdatedKey(fingerprint: key.fingerprint, { key in
|
|
return key?.withIncrementedUseCount()
|
|
}))
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
transaction.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: operation, mutable: true, delivered: false))
|
|
return secretChatInitiateRekeySessionIfNeeded(transaction: transaction, peerId: peerId, state: updatedState)
|
|
}
|
|
|
|
private final class ManagedSecretChatOutgoingOperationsHelper {
|
|
var operationDisposables: [Int32: (PeerMergedOperationLogEntry, Disposable)] = [:]
|
|
|
|
func update(_ entries: [PeerMergedOperationLogEntry]) -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) {
|
|
var disposeOperations: [Disposable] = []
|
|
var beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)] = []
|
|
|
|
var hasRunningOperationForPeerId = Set<PeerId>()
|
|
var validMergedIndices = Set<Int32>()
|
|
for entry in entries {
|
|
if let entryAndDisposable = self.operationDisposables[entry.mergedIndex] {
|
|
if let lhsOperation = entryAndDisposable.0.contents as? SecretChatOutgoingOperation, let rhsOperation = entry.contents as? SecretChatOutgoingOperation {
|
|
var lhsDelete = false
|
|
if case .deleteMessages = lhsOperation.contents {
|
|
lhsDelete = true
|
|
}
|
|
var rhsDelete = false
|
|
if case .deleteMessages = rhsOperation.contents {
|
|
rhsDelete = true
|
|
}
|
|
if lhsDelete != rhsDelete {
|
|
disposeOperations.append(entryAndDisposable.1)
|
|
self.operationDisposables.removeValue(forKey: entry.mergedIndex)
|
|
}
|
|
}
|
|
}
|
|
|
|
if !hasRunningOperationForPeerId.contains(entry.peerId) {
|
|
hasRunningOperationForPeerId.insert(entry.peerId)
|
|
validMergedIndices.insert(entry.mergedIndex)
|
|
|
|
if self.operationDisposables[entry.mergedIndex] == nil {
|
|
let disposable = MetaDisposable()
|
|
beginOperations.append((entry, disposable))
|
|
self.operationDisposables[entry.mergedIndex] = (entry, disposable)
|
|
}
|
|
}
|
|
}
|
|
|
|
var removeMergedIndices: [Int32] = []
|
|
for (mergedIndex, entryAndDisposable) in self.operationDisposables {
|
|
if !validMergedIndices.contains(mergedIndex) {
|
|
removeMergedIndices.append(mergedIndex)
|
|
disposeOperations.append(entryAndDisposable.1)
|
|
}
|
|
}
|
|
|
|
for mergedIndex in removeMergedIndices {
|
|
self.operationDisposables.removeValue(forKey: mergedIndex)
|
|
}
|
|
|
|
return (disposeOperations, beginOperations)
|
|
}
|
|
|
|
func reset() -> [Disposable] {
|
|
let disposables = Array(self.operationDisposables.values)
|
|
self.operationDisposables.removeAll()
|
|
return disposables.map { $0.1 }
|
|
}
|
|
}
|
|
|
|
private func takenImmutableOperation(postbox: Postbox, peerId: PeerId, tagLocalIndex: Int32) -> Signal<PeerMergedOperationLogEntry?, NoError> {
|
|
return postbox.transaction { transaction -> PeerMergedOperationLogEntry? in
|
|
var result: PeerMergedOperationLogEntry?
|
|
transaction.operationLogUpdateEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: tagLocalIndex, { entry in
|
|
if let entry = entry, let _ = entry.mergedIndex, let operation = entry.contents as? SecretChatOutgoingOperation {
|
|
if operation.mutable {
|
|
let updatedContents = SecretChatOutgoingOperation(contents: operation.contents, mutable: false, delivered: operation.delivered)
|
|
result = entry.withUpdatedContents(updatedContents).mergedEntry!
|
|
return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .update(updatedContents))
|
|
} else {
|
|
result = entry.mergedEntry!
|
|
}
|
|
return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none)
|
|
} else {
|
|
return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none)
|
|
}
|
|
})
|
|
return result
|
|
}
|
|
}
|
|
|
|
func managedSecretChatOutgoingOperations(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
|
|
return Signal { _ in
|
|
let helper = Atomic<ManagedSecretChatOutgoingOperationsHelper>(value: ManagedSecretChatOutgoingOperationsHelper())
|
|
|
|
let disposable = postbox.mergedOperationLogView(tag: OperationLogTags.SecretOutgoing, limit: 10).start(next: { view in
|
|
let (disposeOperations, beginOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) in
|
|
return helper.update(view.entries)
|
|
}
|
|
|
|
for disposable in disposeOperations {
|
|
disposable.dispose()
|
|
}
|
|
|
|
for (entry, disposable) in beginOperations {
|
|
let signal = takenImmutableOperation(postbox: postbox, peerId: entry.peerId, tagLocalIndex: entry.tagLocalIndex)
|
|
|> mapToSignal { entry -> Signal<Void, NoError> in
|
|
if let entry = entry {
|
|
if let operation = entry.contents as? SecretChatOutgoingOperation {
|
|
switch operation.contents {
|
|
case let .initialHandshakeAccept(gA, accessHash, b):
|
|
return initialHandshakeAccept(postbox: postbox, network: network, peerId: entry.peerId, accessHash: accessHash, gA: gA, b: b, tagLocalIndex: entry.tagLocalIndex)
|
|
case let .sendMessage(layer, id, file):
|
|
return sendMessage(postbox: postbox, network: network, messageId: id, file: file, tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered, layer: layer)
|
|
case let .reportLayerSupport(layer, actionGloballyUniqueId, layerSupport):
|
|
return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .reportLayerSupport(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, layerSupport: layerSupport), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered)
|
|
case let .deleteMessages(layer, actionGloballyUniqueId, globallyUniqueIds):
|
|
return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .deleteMessages(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, globallyUniqueIds: globallyUniqueIds), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered)
|
|
case let .clearHistory(layer, actionGloballyUniqueId):
|
|
return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .clearHistory(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered)
|
|
case let .pfsRequestKey(layer, actionGloballyUniqueId, rekeySessionId, a):
|
|
return pfsRequestKey(postbox: postbox, network: network, peerId: entry.peerId, layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, rekeySessionId: rekeySessionId, a: a, tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered)
|
|
case let .pfsCommitKey(layer, actionGloballyUniqueId, rekeySessionId, keyFingerprint):
|
|
return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .pfsCommitKey(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, rekeySessionId: rekeySessionId, keyFingerprint: keyFingerprint), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered)
|
|
case let .pfsAcceptKey(layer, actionGloballyUniqueId, rekeySessionId, gA, b):
|
|
return pfsAcceptKey(postbox: postbox, network: network, peerId: entry.peerId, layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, rekeySessionId: rekeySessionId, gA: gA, b: b, tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered)
|
|
case let .pfsAbortSession(layer, actionGloballyUniqueId, rekeySessionId):
|
|
return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .pfsAbortSession(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, rekeySessionId: rekeySessionId), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered)
|
|
case let .noop(layer, actionGloballyUniqueId):
|
|
return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .noop(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered)
|
|
case let .readMessagesContent(layer, actionGloballyUniqueId, globallyUniqueIds):
|
|
return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .readMessageContents(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, globallyUniqueIds: globallyUniqueIds), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered)
|
|
case let .setMessageAutoremoveTimeout(layer, actionGloballyUniqueId, timeout, messageId):
|
|
return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .setMessageAutoremoveTimeout(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, timeout: timeout, messageId: messageId), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered)
|
|
case let .resendOperations(layer, actionGloballyUniqueId, fromSeqNo, toSeqNo):
|
|
return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .resendOperations(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, fromSeqNo: fromSeqNo, toSeqNo: toSeqNo), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered)
|
|
case let .screenshotMessages(layer, actionGloballyUniqueId, globallyUniqueIds, messageId):
|
|
return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .screenshotMessages(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, globallyUniqueIds: globallyUniqueIds, messageId: messageId), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered)
|
|
case let .terminate(reportSpam):
|
|
return requestTerminateSecretChat(postbox: postbox, network: network, peerId: entry.peerId, tagLocalIndex: entry.tagLocalIndex, reportSpam: reportSpam)
|
|
}
|
|
} else {
|
|
assertionFailure()
|
|
}
|
|
}
|
|
return .complete()
|
|
}
|
|
disposable.set(signal.start())
|
|
}
|
|
})
|
|
|
|
return ActionDisposable {
|
|
disposable.dispose()
|
|
|
|
let disposables = helper.with { helper -> [Disposable] in
|
|
return helper.reset()
|
|
}
|
|
for disposable in disposables {
|
|
disposable.dispose()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func initialHandshakeAccept(postbox: Postbox, network: Network, peerId: PeerId, accessHash: Int64, gA: MemoryBuffer, b: MemoryBuffer, tagLocalIndex: Int32) -> Signal<Void, NoError> {
|
|
return validatedEncryptionConfig(postbox: postbox, network: network)
|
|
|> mapToSignal { config -> Signal<Void, NoError> in
|
|
let p = config.p.makeData()
|
|
|
|
if !MTCheckIsSafeGAOrB(gA.makeData(), p) {
|
|
return postbox.transaction { transaction -> Void in
|
|
let removed = transaction.operationLogRemoveEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: tagLocalIndex)
|
|
assert(removed)
|
|
if let state = transaction.getPeerChatState(peerId) as? SecretChatState {
|
|
var updatedState = state
|
|
updatedState = updatedState.withUpdatedEmbeddedState(.terminated)
|
|
transaction.setPeerChatState(peerId, state: updatedState)
|
|
if let peer = transaction.getPeer(peerId) as? TelegramSecretChat {
|
|
updatePeers(transaction: transaction, peers: [peer.withUpdatedEmbeddedState(updatedState.embeddedState.peerState)], update: { _, updated in
|
|
return updated
|
|
})
|
|
}
|
|
} else {
|
|
assertionFailure()
|
|
}
|
|
}
|
|
}
|
|
|
|
var gValue: Int32 = config.g.byteSwapped
|
|
let g = Data(bytes: &gValue, count: 4)
|
|
|
|
let bData = b.makeData()
|
|
|
|
let gb = MTExp(g, bData, p)!
|
|
|
|
var key = MTExp(gA.makeData(), bData, p)!
|
|
|
|
if key.count > 256 {
|
|
key.count = 256
|
|
} else {
|
|
while key.count < 256 {
|
|
key.insert(0, at: 0)
|
|
}
|
|
}
|
|
|
|
let keyHash = MTSha1(key)!
|
|
|
|
var keyFingerprint: Int64 = 0
|
|
keyHash.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
|
memcpy(&keyFingerprint, bytes.advanced(by: keyHash.count - 8), 8)
|
|
}
|
|
|
|
let result = network.request(Api.functions.messages.acceptEncryption(peer: .inputEncryptedChat(chatId: peerId.id, accessHash: accessHash), gB: Buffer(data: gb), keyFingerprint: keyFingerprint))
|
|
|
|
let response = result
|
|
|> map { result -> Api.EncryptedChat? in
|
|
return result
|
|
}
|
|
|> `catch` { error -> Signal<Api.EncryptedChat?, NoError> in
|
|
return .single(nil)
|
|
}
|
|
|
|
return response
|
|
|> mapToSignal { result -> Signal<Void, NoError> in
|
|
return postbox.transaction { transaction -> Void in
|
|
let removed = transaction.operationLogRemoveEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: tagLocalIndex)
|
|
assert(removed)
|
|
if let state = transaction.getPeerChatState(peerId) as? SecretChatState {
|
|
var updatedState = state
|
|
updatedState = updatedState.withUpdatedKeychain(SecretChatKeychain(keys: [SecretChatKey(fingerprint: keyFingerprint, key: MemoryBuffer(data: key), validity: .indefinite, useCount: 0)]))
|
|
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(SecretChatSequenceBasedLayerState(layerNegotiationState: SecretChatLayerNegotiationState(activeLayer: .layer46, locallyRequestedLayer: nil, remotelyRequestedLayer: nil), rekeyState: nil, baseIncomingOperationIndex: transaction.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretIncomingDecrypted), baseOutgoingOperationIndex: transaction.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing), topProcessedCanonicalIncomingOperationIndex: nil)))
|
|
updatedState = updatedState.withUpdatedKeyFingerprint(SecretChatKeyFingerprint(sha1: SecretChatKeySha1Fingerprint(digest: sha1Digest(key)), sha256: SecretChatKeySha256Fingerprint(digest: sha256Digest(key))))
|
|
|
|
var layer: SecretChatLayer?
|
|
switch updatedState.embeddedState {
|
|
case .terminated, .handshake:
|
|
break
|
|
case .basicLayer:
|
|
layer = .layer8
|
|
case let .sequenceBasedLayer(sequenceState):
|
|
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
|
|
}
|
|
if let layer = layer {
|
|
updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: peerId, operation: .reportLayerSupport(layer: layer, actionGloballyUniqueId: arc4random64(), layerSupport: 46), state: updatedState)
|
|
}
|
|
transaction.setPeerChatState(peerId, state: updatedState)
|
|
if let peer = transaction.getPeer(peerId) as? TelegramSecretChat {
|
|
updatePeers(transaction: transaction, peers: [peer.withUpdatedEmbeddedState(updatedState.embeddedState.peerState)], update: { _, updated in
|
|
return updated
|
|
})
|
|
}
|
|
} else {
|
|
assertionFailure()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func pfsRequestKey(postbox: Postbox, network: Network, peerId: PeerId, layer: SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64, rekeySessionId: Int64, a: MemoryBuffer, tagLocalIndex: Int32, wasDelivered: Bool) -> Signal<Void, NoError> {
|
|
return validatedEncryptionConfig(postbox: postbox, network: network)
|
|
|> mapToSignal { config -> Signal<Void, NoError> in
|
|
var gValue: Int32 = config.g.byteSwapped
|
|
let g = Data(bytes: &gValue, count: 4)
|
|
let p = config.p.makeData()
|
|
|
|
let aData = a.makeData()
|
|
let ga = MTExp(g, aData, p)!
|
|
|
|
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
|
if let state = transaction.getPeerChatState(peerId) as? SecretChatState {
|
|
switch state.embeddedState {
|
|
case let .sequenceBasedLayer(sequenceState):
|
|
if let rekeyState = sequenceState.rekeyState, case .requesting = rekeyState.data {
|
|
transaction.setPeerChatState(peerId, state: state.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedRekeyState(SecretChatRekeySessionState(id: rekeyState.id, data: .requested(a: a, config: config))))))
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
return sendServiceActionMessage(postbox: postbox, network: network, peerId: peerId, action: .pfsRequestKey(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, rekeySessionId:rekeySessionId, gA: MemoryBuffer(data: ga)), tagLocalIndex: tagLocalIndex, wasDelivered: wasDelivered)
|
|
}
|
|
|> switchToLatest
|
|
}
|
|
}
|
|
|
|
private func pfsAcceptKey(postbox: Postbox, network: Network, peerId: PeerId, layer: SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64, rekeySessionId: Int64, gA: MemoryBuffer, b: MemoryBuffer, tagLocalIndex: Int32, wasDelivered: Bool) -> Signal<Void, NoError> {
|
|
return validatedEncryptionConfig(postbox: postbox, network: network)
|
|
|> mapToSignal { config -> Signal<Void, NoError> in
|
|
var gValue: Int32 = config.g.byteSwapped
|
|
let g = Data(bytes: &gValue, count: 4)
|
|
let p = config.p.makeData()
|
|
|
|
let bData = b.makeData()
|
|
|
|
let gb = MTExp(g, bData, p)!
|
|
|
|
var key = MTExp(gA.makeData(), bData, p)!
|
|
|
|
if key.count > 256 {
|
|
key.count = 256
|
|
} else {
|
|
while key.count < 256 {
|
|
key.insert(0, at: 0)
|
|
}
|
|
}
|
|
|
|
let keyHash = MTSha1(key)!
|
|
|
|
var keyFingerprint: Int64 = 0
|
|
keyHash.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
|
memcpy(&keyFingerprint, bytes.advanced(by: keyHash.count - 8), 8)
|
|
}
|
|
|
|
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
|
if let state = transaction.getPeerChatState(peerId) as? SecretChatState {
|
|
switch state.embeddedState {
|
|
case let .sequenceBasedLayer(sequenceState):
|
|
if let rekeyState = sequenceState.rekeyState, case .accepting = rekeyState.data {
|
|
transaction.setPeerChatState(peerId, state: state.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedRekeyState(SecretChatRekeySessionState(id: rekeyState.id, data: .accepted(key: MemoryBuffer(data: key), keyFingerprint: keyFingerprint))))))
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
return sendServiceActionMessage(postbox: postbox, network: network, peerId: peerId, action: .pfsAcceptKey(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, rekeySessionId:rekeySessionId, gB: MemoryBuffer(data: gb), keyFingerprint: keyFingerprint), tagLocalIndex: tagLocalIndex, wasDelivered: wasDelivered)
|
|
}
|
|
|> switchToLatest
|
|
}
|
|
}
|
|
|
|
private enum BoxedDecryptedMessage {
|
|
case layer8(SecretApi8.DecryptedMessage)
|
|
case layer46(SecretApi46.DecryptedMessage)
|
|
case layer73(SecretApi73.DecryptedMessage)
|
|
|
|
func serialize(_ buffer: Buffer, role: SecretChatRole, sequenceInfo: SecretChatOperationSequenceInfo?) {
|
|
switch self {
|
|
case let .layer8(message):
|
|
let _ = message.serialize(buffer, true)
|
|
case let .layer46(message):
|
|
buffer.appendInt32(0x1be31789)
|
|
let randomBytes = malloc(15)!
|
|
arc4random_buf(randomBytes, 15)
|
|
serializeBytes(Buffer(memory: randomBytes, size: 15, capacity: 15, freeWhenDone: false), buffer: buffer, boxed: false)
|
|
free(randomBytes)
|
|
buffer.appendInt32(46)
|
|
|
|
if let sequenceInfo = sequenceInfo {
|
|
let inSeqNo = (sequenceInfo.topReceivedOperationIndex + 1) * 2 + (role == .creator ? 0 : 1)
|
|
let outSeqNo = sequenceInfo.operationIndex * 2 + (role == .creator ? 1 : 0)
|
|
buffer.appendInt32(inSeqNo)
|
|
buffer.appendInt32(outSeqNo)
|
|
} else {
|
|
buffer.appendInt32(0)
|
|
buffer.appendInt32(0)
|
|
assertionFailure()
|
|
}
|
|
|
|
let _ = message.serialize(buffer, true)
|
|
case let .layer73(message):
|
|
buffer.appendInt32(0x1be31789)
|
|
let randomBytes = malloc(15)!
|
|
arc4random_buf(randomBytes, 15)
|
|
serializeBytes(Buffer(memory: randomBytes, size: 15, capacity: 15, freeWhenDone: false), buffer: buffer, boxed: false)
|
|
free(randomBytes)
|
|
buffer.appendInt32(73)
|
|
|
|
if let sequenceInfo = sequenceInfo {
|
|
let inSeqNo = (sequenceInfo.topReceivedOperationIndex + 1) * 2 + (role == .creator ? 0 : 1)
|
|
let outSeqNo = sequenceInfo.operationIndex * 2 + (role == .creator ? 1 : 0)
|
|
buffer.appendInt32(inSeqNo)
|
|
buffer.appendInt32(outSeqNo)
|
|
} else {
|
|
buffer.appendInt32(0)
|
|
buffer.appendInt32(0)
|
|
assertionFailure()
|
|
}
|
|
|
|
let _ = message.serialize(buffer, true)
|
|
}
|
|
}
|
|
}
|
|
|
|
private enum SecretMessageAction {
|
|
case deleteMessages(layer: SecretChatLayer, actionGloballyUniqueId: Int64, globallyUniqueIds: [Int64])
|
|
case screenshotMessages(layer: SecretChatLayer, actionGloballyUniqueId: Int64, globallyUniqueIds: [Int64], messageId: MessageId)
|
|
case clearHistory(layer: SecretChatLayer, actionGloballyUniqueId: Int64)
|
|
case resendOperations(layer: SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64, fromSeqNo: Int32, toSeqNo: Int32)
|
|
case reportLayerSupport(layer: SecretChatLayer, actionGloballyUniqueId: Int64, layerSupport: Int32)
|
|
case pfsRequestKey(layer: SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64, rekeySessionId: Int64, gA: MemoryBuffer)
|
|
case pfsAcceptKey(layer: SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64, rekeySessionId: Int64, gB: MemoryBuffer, keyFingerprint: Int64)
|
|
case pfsAbortSession(layer: SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64, rekeySessionId: Int64)
|
|
case pfsCommitKey(layer: SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64, rekeySessionId: Int64, keyFingerprint: Int64)
|
|
case noop(layer: SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64)
|
|
case readMessageContents(layer: SecretChatLayer, actionGloballyUniqueId: Int64, globallyUniqueIds: [Int64])
|
|
case setMessageAutoremoveTimeout(layer: SecretChatLayer, actionGloballyUniqueId: Int64, timeout: Int32, messageId: MessageId)
|
|
|
|
var globallyUniqueId: Int64 {
|
|
switch self {
|
|
case let .deleteMessages(_, actionGloballyUniqueId, _):
|
|
return actionGloballyUniqueId
|
|
case let .screenshotMessages(_, actionGloballyUniqueId, _, _):
|
|
return actionGloballyUniqueId
|
|
case let .clearHistory(_, actionGloballyUniqueId):
|
|
return actionGloballyUniqueId
|
|
case let .resendOperations(_, actionGloballyUniqueId, _, _):
|
|
return actionGloballyUniqueId
|
|
case let .reportLayerSupport(_, actionGloballyUniqueId, _):
|
|
return actionGloballyUniqueId
|
|
case let .pfsRequestKey(_, actionGloballyUniqueId, _, _):
|
|
return actionGloballyUniqueId
|
|
case let .pfsAcceptKey(_, actionGloballyUniqueId, _, _, _):
|
|
return actionGloballyUniqueId
|
|
case let .pfsAbortSession(_, actionGloballyUniqueId, _):
|
|
return actionGloballyUniqueId
|
|
case let .pfsCommitKey(_, actionGloballyUniqueId, _, _):
|
|
return actionGloballyUniqueId
|
|
case let .noop(_, actionGloballyUniqueId):
|
|
return actionGloballyUniqueId
|
|
case let .readMessageContents(_, actionGloballyUniqueId, _):
|
|
return actionGloballyUniqueId
|
|
case let .setMessageAutoremoveTimeout(_, actionGloballyUniqueId, _, _):
|
|
return actionGloballyUniqueId
|
|
}
|
|
}
|
|
|
|
var messageId: MessageId? {
|
|
switch self {
|
|
case let .setMessageAutoremoveTimeout(_, _, _, messageId):
|
|
return messageId
|
|
case let .screenshotMessages(_, _, _, messageId):
|
|
return messageId
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
private func decryptedAttributes46(_ attributes: [TelegramMediaFileAttribute], transaction: Transaction) -> [SecretApi46.DocumentAttribute] {
|
|
var result: [SecretApi46.DocumentAttribute] = []
|
|
for attribute in attributes {
|
|
switch attribute {
|
|
case let .FileName(fileName):
|
|
result.append(.documentAttributeFilename(fileName: fileName))
|
|
case .Animated:
|
|
result.append(.documentAttributeAnimated)
|
|
case let .Sticker(displayText, packReference, _):
|
|
var stickerSet: SecretApi46.InputStickerSet = .inputStickerSetEmpty
|
|
if let packReference = packReference {
|
|
switch packReference {
|
|
case let .name(name):
|
|
stickerSet = .inputStickerSetShortName(shortName: name)
|
|
case .id:
|
|
if let (info, _, _) = cachedStickerPack(transaction: transaction, reference: packReference) {
|
|
stickerSet = .inputStickerSetShortName(shortName: info.shortName)
|
|
}
|
|
}
|
|
}
|
|
result.append(.documentAttributeSticker(alt: displayText, stickerset: stickerSet))
|
|
case let .ImageSize(size):
|
|
result.append(.documentAttributeImageSize(w: Int32(size.width), h: Int32(size.height)))
|
|
case let .Video(duration, size, videoFlags):
|
|
result.append(.documentAttributeVideo(duration: Int32(duration), w: Int32(size.width), h: Int32(size.height)))
|
|
case let .Audio(isVoice, duration, title, performer, waveform):
|
|
var flags: Int32 = 0
|
|
if isVoice {
|
|
flags |= (1 << 10)
|
|
}
|
|
if let _ = title {
|
|
flags |= Int32(1 << 0)
|
|
}
|
|
if let _ = performer {
|
|
flags |= Int32(1 << 1)
|
|
}
|
|
var waveformBuffer: Buffer?
|
|
if let waveform = waveform {
|
|
flags |= Int32(1 << 2)
|
|
waveformBuffer = Buffer(data: waveform.makeData())
|
|
}
|
|
result.append(.documentAttributeAudio(flags: flags, duration: Int32(duration), title: title, performer: performer, waveform: waveformBuffer))
|
|
case .HasLinkedStickers:
|
|
break
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
private func decryptedAttributes73(_ attributes: [TelegramMediaFileAttribute], transaction: Transaction) -> [SecretApi73.DocumentAttribute] {
|
|
var result: [SecretApi73.DocumentAttribute] = []
|
|
for attribute in attributes {
|
|
switch attribute {
|
|
case let .FileName(fileName):
|
|
result.append(.documentAttributeFilename(fileName: fileName))
|
|
case .Animated:
|
|
result.append(.documentAttributeAnimated)
|
|
case let .Sticker(displayText, packReference, _):
|
|
var stickerSet: SecretApi73.InputStickerSet = .inputStickerSetEmpty
|
|
if let packReference = packReference {
|
|
switch packReference {
|
|
case let .name(name):
|
|
stickerSet = .inputStickerSetShortName(shortName: name)
|
|
case .id:
|
|
if let (info, _, _) = cachedStickerPack(transaction: transaction, reference: packReference) {
|
|
stickerSet = .inputStickerSetShortName(shortName: info.shortName)
|
|
}
|
|
}
|
|
}
|
|
result.append(.documentAttributeSticker(alt: displayText, stickerset: stickerSet))
|
|
case let .ImageSize(size):
|
|
result.append(.documentAttributeImageSize(w: Int32(size.width), h: Int32(size.height)))
|
|
case let .Video(duration, size, videoFlags):
|
|
var flags: Int32 = 0
|
|
if videoFlags.contains(.instantRoundVideo) {
|
|
flags |= 1 << 0
|
|
}
|
|
result.append(.documentAttributeVideo(flags: flags, duration: Int32(duration), w: Int32(size.width), h: Int32(size.height)))
|
|
case let .Audio(isVoice, duration, title, performer, waveform):
|
|
var flags: Int32 = 0
|
|
if isVoice {
|
|
flags |= (1 << 10)
|
|
}
|
|
if let _ = title {
|
|
flags |= Int32(1 << 0)
|
|
}
|
|
if let _ = performer {
|
|
flags |= Int32(1 << 1)
|
|
}
|
|
var waveformBuffer: Buffer?
|
|
if let waveform = waveform {
|
|
flags |= Int32(1 << 2)
|
|
waveformBuffer = Buffer(data: waveform.makeData())
|
|
}
|
|
result.append(.documentAttributeAudio(flags: flags, duration: Int32(duration), title: title, performer: performer, waveform: waveformBuffer))
|
|
case .HasLinkedStickers:
|
|
break
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
private func decryptedEntities73(_ entities: [MessageTextEntity]?) -> [SecretApi73.MessageEntity]? {
|
|
guard let entities = entities else {
|
|
return nil
|
|
}
|
|
|
|
var result: [SecretApi73.MessageEntity] = []
|
|
for entity in entities {
|
|
switch entity.type {
|
|
case .Unknown:
|
|
break
|
|
case .Mention:
|
|
result.append(.messageEntityMention(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
|
|
case .Hashtag:
|
|
result.append(.messageEntityHashtag(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
|
|
case .BotCommand:
|
|
result.append(.messageEntityBotCommand(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
|
|
case .Url:
|
|
result.append(.messageEntityUrl(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
|
|
case .Email:
|
|
result.append(.messageEntityEmail(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
|
|
case .Bold:
|
|
result.append(.messageEntityBold(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
|
|
case .Italic:
|
|
result.append(.messageEntityItalic(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
|
|
case .Code:
|
|
result.append(.messageEntityCode(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
|
|
case .Pre:
|
|
result.append(.messageEntityPre(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count), language: ""))
|
|
case let .TextUrl(url):
|
|
result.append(.messageEntityTextUrl(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count), url: url))
|
|
case .TextMention:
|
|
break
|
|
case .PhoneNumber:
|
|
break
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
private func boxedDecryptedMessage(transaction: Transaction, message: Message, globallyUniqueId: Int64, uploadedFile: SecretChatOutgoingFile?, thumbnailData: [MediaId: Data], layer: SecretChatLayer) -> BoxedDecryptedMessage {
|
|
let media: Media? = message.media.first
|
|
var messageAutoremoveTimeout: Int32 = 0
|
|
var replyGlobalId: Int64? = nil
|
|
var flags: Int32 = 0
|
|
for attribute in message.attributes {
|
|
if let attribute = attribute as? ReplyMessageAttribute {
|
|
if let message = message.associatedMessages[attribute.messageId] {
|
|
replyGlobalId = message.globallyUniqueId
|
|
flags |= (1 << 3)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
var viaBotName: String?
|
|
var entities: [MessageTextEntity]?
|
|
|
|
for attribute in message.attributes {
|
|
if let attribute = attribute as? AutoremoveTimeoutMessageAttribute {
|
|
messageAutoremoveTimeout = attribute.timeout
|
|
} else if let attribute = attribute as? InlineBotMessageAttribute {
|
|
if let title = attribute.title {
|
|
viaBotName = title
|
|
} else if let peerId = attribute.peerId, let peer = transaction.getPeer(peerId), let addressName = peer.addressName {
|
|
viaBotName = addressName
|
|
}
|
|
} else if let attribute = attribute as? TextEntitiesMessageAttribute {
|
|
entities = attribute.entities
|
|
}
|
|
}
|
|
|
|
if let media = media {
|
|
if let image = media as? TelegramMediaImage, let uploadedFile = uploadedFile, let largestRepresentation = largestImageRepresentation(image.representations) {
|
|
let thumbW: Int32
|
|
let thumbH: Int32
|
|
let thumb: Buffer
|
|
if let smallestRepresentation = smallestImageRepresentation(image.representations), smallestRepresentation.dimensions.width < 100.0 && smallestRepresentation.dimensions.height < 100.0, let data = thumbnailData[image.imageId] {
|
|
thumbW = Int32(smallestRepresentation.dimensions.width)
|
|
thumbH = Int32(smallestRepresentation.dimensions.height)
|
|
thumb = Buffer(data: data)
|
|
} else {
|
|
thumbW = 90
|
|
thumbH = 90
|
|
thumb = Buffer()
|
|
}
|
|
|
|
switch layer {
|
|
case .layer8:
|
|
let randomBytesData = malloc(15)!
|
|
arc4random_buf(randomBytesData, 15)
|
|
let randomBytes = Buffer(memory: randomBytesData, size: 15, capacity: 15, freeWhenDone: true)
|
|
|
|
let decryptedMedia = SecretApi8.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: thumb, thumbW: thumbW, thumbH: thumbH, w: Int32(largestRepresentation.dimensions.width), h: Int32(largestRepresentation.dimensions.height), size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv))
|
|
|
|
return .layer8(.decryptedMessage(randomId: globallyUniqueId, randomBytes: randomBytes, message: message.text, media: decryptedMedia))
|
|
case .layer46:
|
|
if let _ = viaBotName {
|
|
flags |= (1 << 11)
|
|
}
|
|
let decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: thumb, thumbW: thumbW, thumbH: thumbH, w: Int32(largestRepresentation.dimensions.width), h: Int32(largestRepresentation.dimensions.height), size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), caption: "")
|
|
flags |= (1 << 9)
|
|
return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: viaBotName, replyToRandomId: replyGlobalId))
|
|
case .layer73:
|
|
if let _ = viaBotName {
|
|
flags |= (1 << 11)
|
|
}
|
|
let decryptedEntites = entities.flatMap(decryptedEntities73)
|
|
if let _ = decryptedEntites {
|
|
flags |= (1 << 7)
|
|
}
|
|
let decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: thumb, thumbW: thumbW, thumbH: thumbH, w: Int32(largestRepresentation.dimensions.width), h: Int32(largestRepresentation.dimensions.height), size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), caption: "")
|
|
flags |= (1 << 9)
|
|
if message.groupingKey != nil {
|
|
flags |= (1 << 17)
|
|
}
|
|
return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: decryptedEntites, viaBotName: viaBotName, replyToRandomId: replyGlobalId, groupedId: message.groupingKey))
|
|
}
|
|
} else if let file = media as? TelegramMediaFile {
|
|
let thumbW: Int32
|
|
let thumbH: Int32
|
|
let thumb: Buffer
|
|
if let smallestRepresentation = smallestImageRepresentation(file.previewRepresentations), let data = thumbnailData[file.fileId] {
|
|
thumbW = Int32(smallestRepresentation.dimensions.width)
|
|
thumbH = Int32(smallestRepresentation.dimensions.height)
|
|
thumb = Buffer(data: data)
|
|
} else {
|
|
thumbW = 0
|
|
thumbH = 0
|
|
thumb = Buffer()
|
|
}
|
|
|
|
switch layer {
|
|
case .layer8:
|
|
if let uploadedFile = uploadedFile {
|
|
let randomBytesData = malloc(15)!
|
|
arc4random_buf(randomBytesData, 15)
|
|
let randomBytes = Buffer(memory: randomBytesData, size: 15, capacity: 15, freeWhenDone: true)
|
|
|
|
let decryptedMedia = SecretApi8.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: thumb, thumbW: thumbW, thumbH: thumbH, fileName: file.fileName ?? "file", mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv))
|
|
|
|
return .layer8(.decryptedMessage(randomId: globallyUniqueId, randomBytes: randomBytes, message: message.text, media: decryptedMedia))
|
|
}
|
|
case .layer46:
|
|
var decryptedMedia: SecretApi46.DecryptedMessageMedia?
|
|
|
|
if let uploadedFile = uploadedFile {
|
|
var voiceDuration: Int32?
|
|
for attribute in file.attributes {
|
|
if case let .Audio(isVoice, duration, _, _, _) = attribute {
|
|
if isVoice {
|
|
voiceDuration = Int32(duration)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
if let voiceDuration = voiceDuration {
|
|
decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: voiceDuration, mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv))
|
|
} else {
|
|
decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: thumb, thumbW: thumbW, thumbH: thumbH, mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), attributes: decryptedAttributes46(file.attributes, transaction: transaction), caption: "")
|
|
}
|
|
} else {
|
|
if let resource = file.resource as? CloudDocumentMediaResource, let size = file.size {
|
|
let thumb: SecretApi46.PhotoSize
|
|
if let smallestRepresentation = smallestImageRepresentation(file.previewRepresentations), let thumbResource = smallestRepresentation.resource as? CloudFileMediaResource {
|
|
thumb = .photoSize(type: "s", location: .fileLocation(dcId: Int32(thumbResource.datacenterId), volumeId: thumbResource.volumeId, localId: thumbResource.localId, secret: thumbResource.secret), w: Int32(smallestRepresentation.dimensions.width), h: Int32(smallestRepresentation.dimensions.height), size: thumbResource.size.flatMap(Int32.init) ?? 0)
|
|
} else {
|
|
thumb = SecretApi46.PhotoSize.photoSizeEmpty(type: "s")
|
|
}
|
|
decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaExternalDocument(id: resource.fileId, accessHash: resource.accessHash, date: 0, mimeType: file.mimeType, size: Int32(size), thumb: thumb, dcId: Int32(resource.datacenterId), attributes: decryptedAttributes46(file.attributes, transaction: transaction))
|
|
}
|
|
}
|
|
|
|
if let decryptedMedia = decryptedMedia {
|
|
if let _ = viaBotName {
|
|
flags |= (1 << 11)
|
|
}
|
|
flags |= (1 << 9)
|
|
return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: viaBotName, replyToRandomId: replyGlobalId))
|
|
}
|
|
case .layer73:
|
|
var decryptedMedia: SecretApi73.DecryptedMessageMedia?
|
|
|
|
if let uploadedFile = uploadedFile {
|
|
var voiceDuration: Int32?
|
|
for attribute in file.attributes {
|
|
if case let .Audio(isVoice, duration, _, _, _) = attribute {
|
|
if isVoice {
|
|
voiceDuration = Int32(duration)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
/*if let voiceDuration = voiceDuration {
|
|
decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: voiceDuration, mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv))
|
|
} else { */
|
|
decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: thumb, thumbW: thumbW, thumbH: thumbH, mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), attributes: decryptedAttributes73(file.attributes, transaction: transaction), caption: "")
|
|
//}
|
|
} else {
|
|
if let resource = file.resource as? CloudDocumentMediaResource, let size = file.size {
|
|
let thumb: SecretApi73.PhotoSize
|
|
if let smallestRepresentation = smallestImageRepresentation(file.previewRepresentations), let thumbResource = smallestRepresentation.resource as? CloudFileMediaResource {
|
|
thumb = .photoSize(type: "s", location: .fileLocation(dcId: Int32(thumbResource.datacenterId), volumeId: thumbResource.volumeId, localId: thumbResource.localId, secret: thumbResource.secret), w: Int32(smallestRepresentation.dimensions.width), h: Int32(smallestRepresentation.dimensions.height), size: thumbResource.size.flatMap(Int32.init) ?? 0)
|
|
} else {
|
|
thumb = SecretApi73.PhotoSize.photoSizeEmpty(type: "s")
|
|
}
|
|
decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaExternalDocument(id: resource.fileId, accessHash: resource.accessHash, date: 0, mimeType: file.mimeType, size: Int32(size), thumb: thumb, dcId: Int32(resource.datacenterId), attributes: decryptedAttributes73(file.attributes, transaction: transaction))
|
|
}
|
|
}
|
|
|
|
if let decryptedMedia = decryptedMedia {
|
|
if let _ = viaBotName {
|
|
flags |= (1 << 11)
|
|
}
|
|
let decryptedEntites = entities.flatMap(decryptedEntities73)
|
|
if let _ = decryptedEntites {
|
|
flags |= (1 << 7)
|
|
}
|
|
if message.groupingKey != nil {
|
|
flags |= (1 << 17)
|
|
}
|
|
flags |= (1 << 9)
|
|
return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: decryptedEntites, viaBotName: viaBotName, replyToRandomId: replyGlobalId, groupedId: message.groupingKey))
|
|
}
|
|
}
|
|
} else if let webpage = media as? TelegramMediaWebpage {
|
|
var url: String?
|
|
if case let .Loaded(content) = webpage.content {
|
|
url = content.url
|
|
}
|
|
|
|
if let url = url, !url.isEmpty {
|
|
switch layer {
|
|
case .layer8:
|
|
break
|
|
case .layer46:
|
|
if let _ = viaBotName {
|
|
flags |= (1 << 11)
|
|
}
|
|
let decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaWebPage(url: url)
|
|
flags |= (1 << 9)
|
|
return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: viaBotName, replyToRandomId: replyGlobalId))
|
|
case .layer73:
|
|
if let _ = viaBotName {
|
|
flags |= (1 << 11)
|
|
}
|
|
let decryptedEntites = entities.flatMap(decryptedEntities73)
|
|
if let _ = decryptedEntites {
|
|
flags |= (1 << 7)
|
|
}
|
|
let decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaWebPage(url: url)
|
|
flags |= (1 << 9)
|
|
return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: decryptedEntites, viaBotName: viaBotName, replyToRandomId: replyGlobalId, groupedId: message.groupingKey))
|
|
}
|
|
}
|
|
} else if let location = media as? TelegramMediaMap {
|
|
switch layer {
|
|
case .layer8:
|
|
break
|
|
case .layer46:
|
|
if let _ = viaBotName {
|
|
flags |= (1 << 11)
|
|
}
|
|
let decryptedMedia: SecretApi46.DecryptedMessageMedia
|
|
flags |= (1 << 9)
|
|
if let venue = location.venue {
|
|
decryptedMedia = .decryptedMessageMediaVenue(lat: location.latitude, long: location.longitude, title: venue.title, address: venue.address ?? "", provider: venue.provider ?? "", venueId: venue.id ?? "")
|
|
} else {
|
|
decryptedMedia = .decryptedMessageMediaGeoPoint(lat: location.latitude, long: location.longitude)
|
|
}
|
|
return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: viaBotName, replyToRandomId: replyGlobalId))
|
|
case .layer73:
|
|
if let _ = viaBotName {
|
|
flags |= (1 << 11)
|
|
}
|
|
let decryptedEntites = entities.flatMap(decryptedEntities73)
|
|
if let _ = decryptedEntites {
|
|
flags |= (1 << 7)
|
|
}
|
|
|
|
let decryptedMedia: SecretApi73.DecryptedMessageMedia
|
|
flags |= (1 << 9)
|
|
if let venue = location.venue {
|
|
decryptedMedia = .decryptedMessageMediaVenue(lat: location.latitude, long: location.longitude, title: venue.title, address: venue.address ?? "", provider: venue.provider ?? "", venueId: venue.id ?? "")
|
|
} else {
|
|
decryptedMedia = .decryptedMessageMediaGeoPoint(lat: location.latitude, long: location.longitude)
|
|
}
|
|
return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: decryptedEntites, viaBotName: viaBotName, replyToRandomId: replyGlobalId, groupedId: message.groupingKey))
|
|
}
|
|
} else if let contact = media as? TelegramMediaContact {
|
|
switch layer {
|
|
case .layer8:
|
|
break
|
|
case .layer46:
|
|
if let _ = viaBotName {
|
|
flags |= (1 << 11)
|
|
}
|
|
let decryptedMedia: SecretApi46.DecryptedMessageMedia = .decryptedMessageMediaContact(phoneNumber: contact.phoneNumber, firstName: contact.firstName, lastName: contact.lastName, userId: 0)
|
|
flags |= (1 << 9)
|
|
return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: viaBotName, replyToRandomId: replyGlobalId))
|
|
case .layer73:
|
|
if let _ = viaBotName {
|
|
flags |= (1 << 11)
|
|
}
|
|
let decryptedEntites = entities.flatMap(decryptedEntities73)
|
|
if let _ = decryptedEntites {
|
|
flags |= (1 << 7)
|
|
}
|
|
|
|
let decryptedMedia: SecretApi73.DecryptedMessageMedia = .decryptedMessageMediaContact(phoneNumber: contact.phoneNumber, firstName: contact.firstName, lastName: contact.lastName, userId: 0)
|
|
flags |= (1 << 9)
|
|
return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: decryptedEntites, viaBotName: viaBotName, replyToRandomId: replyGlobalId, groupedId: message.groupingKey))
|
|
}
|
|
}
|
|
}
|
|
|
|
switch layer {
|
|
case .layer8:
|
|
let randomBytesData = malloc(15)!
|
|
arc4random_buf(randomBytesData, 15)
|
|
let randomBytes = Buffer(memory: randomBytesData, size: 15, capacity: 15, freeWhenDone: true)
|
|
|
|
return .layer8(.decryptedMessage(randomId: globallyUniqueId, randomBytes: randomBytes, message: message.text, media: .decryptedMessageMediaEmpty))
|
|
case .layer46:
|
|
if let _ = viaBotName {
|
|
flags |= (1 << 11)
|
|
}
|
|
return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: .decryptedMessageMediaEmpty, entities: nil, viaBotName: viaBotName, replyToRandomId: replyGlobalId))
|
|
case .layer73:
|
|
if let _ = viaBotName {
|
|
flags |= (1 << 11)
|
|
}
|
|
let decryptedEntites = entities.flatMap(decryptedEntities73)
|
|
if let _ = decryptedEntites {
|
|
flags |= (1 << 7)
|
|
}
|
|
return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: .decryptedMessageMediaEmpty, entities: decryptedEntites, viaBotName: viaBotName, replyToRandomId: replyGlobalId, groupedId: message.groupingKey))
|
|
}
|
|
}
|
|
|
|
private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> BoxedDecryptedMessage {
|
|
switch action {
|
|
case let .deleteMessages(layer, actionGloballyUniqueId, globallyUniqueIds):
|
|
switch layer {
|
|
case .layer8:
|
|
let randomBytesData = malloc(15)!
|
|
arc4random_buf(randomBytesData, 15)
|
|
let randomBytes = Buffer(memory: randomBytesData, size: 15, capacity: 15, freeWhenDone: true)
|
|
|
|
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionDeleteMessages(randomIds: globallyUniqueIds)))
|
|
case .layer46:
|
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionDeleteMessages(randomIds: globallyUniqueIds)))
|
|
case .layer73:
|
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionDeleteMessages(randomIds: globallyUniqueIds)))
|
|
}
|
|
case let .screenshotMessages(layer, actionGloballyUniqueId, globallyUniqueIds, _):
|
|
switch layer {
|
|
case .layer8:
|
|
let randomBytesData = malloc(15)!
|
|
arc4random_buf(randomBytesData, 15)
|
|
let randomBytes = Buffer(memory: randomBytesData, size: 15, capacity: 15, freeWhenDone: true)
|
|
|
|
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionScreenshotMessages(randomIds: globallyUniqueIds)))
|
|
case .layer46:
|
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionScreenshotMessages(randomIds: globallyUniqueIds)))
|
|
case .layer73:
|
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionScreenshotMessages(randomIds: globallyUniqueIds)))
|
|
}
|
|
case let .clearHistory(layer, actionGloballyUniqueId):
|
|
switch layer {
|
|
case .layer8:
|
|
let randomBytesData = malloc(15)!
|
|
arc4random_buf(randomBytesData, 15)
|
|
let randomBytes = Buffer(memory: randomBytesData, size: 15, capacity: 15, freeWhenDone: true)
|
|
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionFlushHistory))
|
|
case .layer46:
|
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionFlushHistory))
|
|
case .layer73:
|
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionFlushHistory))
|
|
}
|
|
case let .resendOperations(layer, actionGloballyUniqueId, fromSeqNo, toSeqNo):
|
|
switch layer {
|
|
case .layer46:
|
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionResend(startSeqNo: fromSeqNo, endSeqNo: toSeqNo)))
|
|
case .layer73:
|
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionResend(startSeqNo: fromSeqNo, endSeqNo: toSeqNo)))
|
|
}
|
|
case let .reportLayerSupport(layer, actionGloballyUniqueId, layerSupport):
|
|
switch layer {
|
|
case .layer8:
|
|
let randomBytesData = malloc(15)!
|
|
arc4random_buf(randomBytesData, 15)
|
|
let randomBytes = Buffer(memory: randomBytesData, size: 15, capacity: 15, freeWhenDone: true)
|
|
|
|
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionNotifyLayer(layer: layerSupport)))
|
|
case .layer46:
|
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNotifyLayer(layer: layerSupport)))
|
|
case .layer73:
|
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNotifyLayer(layer: layerSupport)))
|
|
}
|
|
case let .pfsRequestKey(layer, actionGloballyUniqueId, rekeySessionId, gA):
|
|
switch layer {
|
|
case .layer46:
|
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionRequestKey(exchangeId: rekeySessionId, gA: Buffer(buffer: gA))))
|
|
case .layer73:
|
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionRequestKey(exchangeId: rekeySessionId, gA: Buffer(buffer: gA))))
|
|
}
|
|
case let .pfsAcceptKey(layer, actionGloballyUniqueId, rekeySessionId, gB, keyFingerprint):
|
|
switch layer {
|
|
case .layer46:
|
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionAcceptKey(exchangeId: rekeySessionId, gB: Buffer(buffer: gB), keyFingerprint: keyFingerprint)))
|
|
case .layer73:
|
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionAcceptKey(exchangeId: rekeySessionId, gB: Buffer(buffer: gB), keyFingerprint: keyFingerprint)))
|
|
}
|
|
case let .pfsAbortSession(layer, actionGloballyUniqueId, rekeySessionId):
|
|
switch layer {
|
|
case .layer46:
|
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionAbortKey(exchangeId: rekeySessionId)))
|
|
case .layer73:
|
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionAbortKey(exchangeId: rekeySessionId)))
|
|
}
|
|
case let .pfsCommitKey(layer, actionGloballyUniqueId, rekeySessionId, keyFingerprint):
|
|
switch layer {
|
|
case .layer46:
|
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionCommitKey(exchangeId: rekeySessionId, keyFingerprint: keyFingerprint)))
|
|
case .layer73:
|
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionCommitKey(exchangeId: rekeySessionId, keyFingerprint: keyFingerprint)))
|
|
}
|
|
case let .noop(layer, actionGloballyUniqueId):
|
|
switch layer {
|
|
case .layer46:
|
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNoop))
|
|
case .layer73:
|
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNoop))
|
|
}
|
|
case let .readMessageContents(layer, actionGloballyUniqueId, globallyUniqueIds):
|
|
switch layer {
|
|
case .layer8:
|
|
let randomBytesData = malloc(15)!
|
|
arc4random_buf(randomBytesData, 15)
|
|
let randomBytes = Buffer(memory: randomBytesData, size: 15, capacity: 15, freeWhenDone: true)
|
|
|
|
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionReadMessages(randomIds: globallyUniqueIds)))
|
|
case .layer46:
|
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionReadMessages(randomIds: globallyUniqueIds)))
|
|
case .layer73:
|
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionReadMessages(randomIds: globallyUniqueIds)))
|
|
}
|
|
case let .setMessageAutoremoveTimeout(layer, actionGloballyUniqueId, timeout, _):
|
|
switch layer {
|
|
case .layer8:
|
|
let randomBytesData = malloc(15)!
|
|
arc4random_buf(randomBytesData, 15)
|
|
let randomBytes = Buffer(memory: randomBytesData, size: 15, capacity: 15, freeWhenDone: true)
|
|
|
|
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionSetMessageTTL(ttlSeconds: timeout)))
|
|
case .layer46:
|
|
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionSetMessageTTL(ttlSeconds: timeout)))
|
|
case .layer73:
|
|
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionSetMessageTTL(ttlSeconds: timeout)))
|
|
}
|
|
}
|
|
}
|
|
|
|
private func markOutgoingOperationAsCompleted(transaction: Transaction, peerId: PeerId, tagLocalIndex: Int32, forceRemove: Bool) {
|
|
var removeFromTagMergedIndexOnly = false
|
|
if let state = transaction.getPeerChatState(peerId) as? SecretChatState {
|
|
switch state.embeddedState {
|
|
case let .sequenceBasedLayer(sequenceState):
|
|
if tagLocalIndex >= sequenceState.baseOutgoingOperationIndex {
|
|
removeFromTagMergedIndexOnly = true
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
if removeFromTagMergedIndexOnly && !forceRemove {
|
|
transaction.operationLogUpdateEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: tagLocalIndex, { entry in
|
|
if let operation = entry?.contents as? SecretChatOutgoingOperation {
|
|
return PeerOperationLogEntryUpdate(mergedIndex: .remove, contents: .update(operation.withUpdatedDelivered(true)))
|
|
} else {
|
|
assertionFailure()
|
|
return PeerOperationLogEntryUpdate(mergedIndex: .remove, contents: .none)
|
|
}
|
|
})
|
|
} else {
|
|
let _ = transaction.operationLogRemoveEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: tagLocalIndex)
|
|
}
|
|
}
|
|
|
|
private func replaceOutgoingOperationWithEmptyMessage(transaction: Transaction, peerId: PeerId, tagLocalIndex: Int32, globallyUniqueId: Int64) {
|
|
var layer: SecretChatLayer?
|
|
let state = transaction.getPeerChatState(peerId) as? SecretChatState
|
|
if let state = state {
|
|
switch state.embeddedState {
|
|
case .terminated, .handshake:
|
|
break
|
|
case .basicLayer:
|
|
layer = .layer8
|
|
case let .sequenceBasedLayer(sequenceState):
|
|
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
|
|
}
|
|
}
|
|
if let layer = layer {
|
|
transaction.operationLogUpdateEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: tagLocalIndex, { entry in
|
|
if let _ = entry?.contents as? SecretChatOutgoingOperation {
|
|
return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .update(SecretChatOutgoingOperation(contents: SecretChatOutgoingOperationContents.deleteMessages(layer: layer, actionGloballyUniqueId: arc4random64(), globallyUniqueIds: [globallyUniqueId]), mutable: true, delivered: false)))
|
|
} else {
|
|
assertionFailure()
|
|
return PeerOperationLogEntryUpdate(mergedIndex: .remove, contents: .none)
|
|
}
|
|
})
|
|
} else {
|
|
assertionFailure()
|
|
let _ = transaction.operationLogRemoveEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: tagLocalIndex)
|
|
}
|
|
}
|
|
|
|
private func resourceThumbnailData(mediaBox: MediaBox, resource: MediaResource, mediaId: MediaId) -> Signal<(MediaId, Data)?, NoError> {
|
|
return mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false))
|
|
|> take(1)
|
|
|> map { data -> (MediaId, Data)? in
|
|
if data.complete, data.size < 1024 * 1024, let content = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
|
|
return (mediaId, content)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
private func messageWithThumbnailData(mediaBox: MediaBox, message: Message) -> Signal<[MediaId: Data], NoError> {
|
|
var signals: [Signal<(MediaId, Data)?, NoError>] = []
|
|
for media in message.media {
|
|
if let image = media as? TelegramMediaImage {
|
|
if let smallestRepresentation = smallestImageRepresentation(image.representations) {
|
|
signals.append(resourceThumbnailData(mediaBox: mediaBox, resource: smallestRepresentation.resource, mediaId: image.imageId))
|
|
}
|
|
} else if let file = media as? TelegramMediaFile {
|
|
if let smallestRepresentation = smallestImageRepresentation(file.previewRepresentations) {
|
|
signals.append(resourceThumbnailData(mediaBox: mediaBox, resource: smallestRepresentation.resource, mediaId: file.fileId))
|
|
}
|
|
}
|
|
}
|
|
return combineLatest(signals)
|
|
|> map { values in
|
|
var result: [MediaId: Data] = [:]
|
|
for value in values {
|
|
if let value = value {
|
|
result[value.0] = value.1
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
}
|
|
|
|
private func sendMessage(postbox: Postbox, network: Network, messageId: MessageId, file: SecretChatOutgoingFile?, tagLocalIndex: Int32, wasDelivered: Bool, layer: SecretChatLayer) -> Signal<Void, NoError> {
|
|
return postbox.transaction { transaction -> Signal<[MediaId: Data], NoError> in
|
|
if let message = transaction.getMessage(messageId) {
|
|
return messageWithThumbnailData(mediaBox: postbox.mediaBox, message: message)
|
|
} else {
|
|
return .single([:])
|
|
}
|
|
}
|
|
|> switchToLatest
|
|
|> mapToSignal { thumbnailData -> Signal<Void, NoError> in
|
|
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
|
if let state = transaction.getPeerChatState(messageId.peerId) as? SecretChatState, let peer = transaction.getPeer(messageId.peerId) as? TelegramSecretChat {
|
|
if let message = transaction.getMessage(messageId), let globallyUniqueId = message.globallyUniqueId {
|
|
let decryptedMessage = boxedDecryptedMessage(transaction: transaction, message: message, globallyUniqueId: globallyUniqueId, uploadedFile: file, thumbnailData: thumbnailData, layer: layer)
|
|
return sendBoxedDecryptedMessage(postbox: postbox, network: network, peer: peer, state: state, operationIndex: tagLocalIndex, decryptedMessage: decryptedMessage, globallyUniqueId: globallyUniqueId, file: file, asService: wasDelivered, wasDelivered: wasDelivered)
|
|
|> mapToSignal { result in
|
|
return postbox.transaction { transaction -> Void in
|
|
if result == nil {
|
|
replaceOutgoingOperationWithEmptyMessage(transaction: transaction, peerId: messageId.peerId, tagLocalIndex: tagLocalIndex, globallyUniqueId: globallyUniqueId)
|
|
} else {
|
|
markOutgoingOperationAsCompleted(transaction: transaction, peerId: messageId.peerId, tagLocalIndex: tagLocalIndex, forceRemove: result == nil)
|
|
}
|
|
|
|
var timestamp = message.timestamp
|
|
var encryptedFile: SecretChatFileReference?
|
|
if let result = result {
|
|
switch result {
|
|
case let .sentEncryptedMessage(date):
|
|
timestamp = date
|
|
case let .sentEncryptedFile(date, file):
|
|
timestamp = date
|
|
encryptedFile = SecretChatFileReference(file)
|
|
}
|
|
}
|
|
|
|
transaction.offsetPendingMessagesTimestamps(lowerBound: message.id, excludeIds: Set([messageId]), timestamp: timestamp)
|
|
|
|
transaction.updateMessage(message.id, update: { currentMessage in
|
|
var flags = StoreMessageFlags(currentMessage.flags)
|
|
if let _ = result {
|
|
flags.remove(.Unsent)
|
|
flags.remove(.Sending)
|
|
} else {
|
|
flags = [.Failed]
|
|
}
|
|
var storeForwardInfo: StoreMessageForwardInfo?
|
|
if let forwardInfo = currentMessage.forwardInfo {
|
|
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
|
}
|
|
|
|
var updatedMedia = currentMessage.media
|
|
|
|
if let fromMedia = currentMessage.media.first, let encryptedFile = encryptedFile, let file = file {
|
|
var toMedia: Media?
|
|
if let fromMedia = fromMedia as? TelegramMediaFile {
|
|
let updatedFile = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: encryptedFile.id), partialReference: nil, resource: SecretFileMediaResource(fileId: encryptedFile.id, accessHash: encryptedFile.accessHash, containerSize: encryptedFile.size, decryptedSize: file.size, datacenterId: Int(encryptedFile.datacenterId), key: file.key), previewRepresentations: fromMedia.previewRepresentations, mimeType: fromMedia.mimeType, size: fromMedia.size, attributes: fromMedia.attributes)
|
|
toMedia = updatedFile
|
|
updatedMedia = [updatedFile]
|
|
}
|
|
|
|
if let toMedia = toMedia {
|
|
applyMediaResourceChanges(from: fromMedia, to: toMedia, postbox: postbox)
|
|
}
|
|
}
|
|
|
|
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: updatedMedia))
|
|
})
|
|
|
|
maybeReadSecretOutgoingMessage(transaction: transaction, index: MessageIndex(id: message.id, timestamp: timestamp))
|
|
|
|
var sentStickers: [TelegramMediaFile] = []
|
|
for media in message.media {
|
|
if let file = media as? TelegramMediaFile {
|
|
if file.isSticker {
|
|
sentStickers.append(file)
|
|
}
|
|
}
|
|
}
|
|
|
|
for file in sentStickers {
|
|
transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentStickers, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 20)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
replaceOutgoingOperationWithEmptyMessage(transaction: transaction, peerId: messageId.peerId, tagLocalIndex: tagLocalIndex, globallyUniqueId: arc4random64())
|
|
deleteMessages(transaction: transaction, mediaBox: postbox.mediaBox, ids: [messageId])
|
|
//assertionFailure()
|
|
return .complete()
|
|
}
|
|
} else {
|
|
return .complete()
|
|
}
|
|
} |> switchToLatest
|
|
}
|
|
}
|
|
|
|
private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId: PeerId, action: SecretMessageAction, tagLocalIndex: Int32, wasDelivered: Bool) -> Signal<Void, NoError> {
|
|
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
|
if let state = transaction.getPeerChatState(peerId) as? SecretChatState, let peer = transaction.getPeer(peerId) as? TelegramSecretChat {
|
|
let decryptedMessage = boxedDecryptedSecretMessageAction(action: action)
|
|
return sendBoxedDecryptedMessage(postbox: postbox, network: network, peer: peer, state: state, operationIndex: tagLocalIndex, decryptedMessage: decryptedMessage, globallyUniqueId: action.globallyUniqueId, file: nil, asService: true, wasDelivered: wasDelivered)
|
|
|> mapToSignal { result in
|
|
return postbox.transaction { transaction -> Void in
|
|
if result == nil {
|
|
replaceOutgoingOperationWithEmptyMessage(transaction: transaction, peerId: peerId, tagLocalIndex: tagLocalIndex, globallyUniqueId: action.globallyUniqueId)
|
|
} else {
|
|
markOutgoingOperationAsCompleted(transaction: transaction, peerId: peerId, tagLocalIndex: tagLocalIndex, forceRemove: result == nil)
|
|
}
|
|
if let messageId = action.messageId {
|
|
var resultTimestamp: Int32?
|
|
transaction.updateMessage(messageId, update: { currentMessage in
|
|
var flags = StoreMessageFlags(currentMessage.flags)
|
|
var timestamp = currentMessage.timestamp
|
|
if let result = result {
|
|
switch result {
|
|
case let .sentEncryptedMessage(date):
|
|
timestamp = date
|
|
case let .sentEncryptedFile(date, _):
|
|
timestamp = date
|
|
}
|
|
flags.remove(.Unsent)
|
|
flags.remove(.Sending)
|
|
} else {
|
|
flags = [.Failed]
|
|
}
|
|
resultTimestamp = timestamp
|
|
var storeForwardInfo: StoreMessageForwardInfo?
|
|
if let forwardInfo = currentMessage.forwardInfo {
|
|
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
|
}
|
|
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media))
|
|
})
|
|
|
|
if let resultTimestamp = resultTimestamp {
|
|
maybeReadSecretOutgoingMessage(transaction: transaction, index: MessageIndex(id: messageId, timestamp: resultTimestamp))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
return .complete()
|
|
}
|
|
}
|
|
|> switchToLatest
|
|
}
|
|
|
|
private func sendBoxedDecryptedMessage(postbox: Postbox, network: Network, peer: TelegramSecretChat, state: SecretChatState, operationIndex: Int32, decryptedMessage: BoxedDecryptedMessage, globallyUniqueId: Int64, file: SecretChatOutgoingFile?, asService: Bool, wasDelivered: Bool) -> Signal<Api.messages.SentEncryptedMessage?, NoError> {
|
|
let payload = Buffer()
|
|
var sequenceInfo: SecretChatOperationSequenceInfo?
|
|
var maybeParameters: SecretChatEncryptionParameters?
|
|
|
|
let mode: SecretChatEncryptionMode
|
|
switch decryptedMessage {
|
|
case .layer8, .layer46:
|
|
mode = .v1
|
|
default:
|
|
mode = .v2(role: state.role)
|
|
}
|
|
|
|
switch state.embeddedState {
|
|
case .terminated, .handshake:
|
|
break
|
|
case .basicLayer:
|
|
if let key = state.keychain.indefinitelyValidKey() {
|
|
maybeParameters = SecretChatEncryptionParameters(key: key, mode: mode)
|
|
}
|
|
case let .sequenceBasedLayer(sequenceState):
|
|
let topReceivedOperationIndex: Int32
|
|
if let topProcessedCanonicalIncomingOperationIndex = sequenceState.topProcessedCanonicalIncomingOperationIndex {
|
|
topReceivedOperationIndex = topProcessedCanonicalIncomingOperationIndex
|
|
} else {
|
|
topReceivedOperationIndex = -1
|
|
}
|
|
let canonicalOperationIndex = sequenceState.canonicalOutgoingOperationIndex(operationIndex)
|
|
if let key = state.keychain.latestKey(validForSequenceBasedCanonicalIndex: canonicalOperationIndex) {
|
|
maybeParameters = SecretChatEncryptionParameters(key: key, mode: mode)
|
|
}
|
|
Logger.shared.log("SecretChat", "sending message with index \(canonicalOperationIndex) key \(String(describing: maybeParameters?.key.fingerprint))")
|
|
sequenceInfo = SecretChatOperationSequenceInfo(topReceivedOperationIndex: topReceivedOperationIndex, operationIndex: canonicalOperationIndex)
|
|
}
|
|
|
|
guard let parameters = maybeParameters else {
|
|
Logger.shared.log("SecretChat", "no valid key found")
|
|
return .single(nil)
|
|
}
|
|
|
|
decryptedMessage.serialize(payload, role: state.role, sequenceInfo: sequenceInfo)
|
|
let encryptedPayload = encryptedMessageContents(parameters: parameters, data: MemoryBuffer(payload))
|
|
let sendMessage: Signal<Api.messages.SentEncryptedMessage, MTRpcError>
|
|
let inputPeer = Api.InputEncryptedChat.inputEncryptedChat(chatId: peer.id.id, accessHash: peer.accessHash)
|
|
|
|
if asService {
|
|
let actionRandomId: Int64
|
|
if wasDelivered {
|
|
actionRandomId = arc4random64()
|
|
} else {
|
|
actionRandomId = globallyUniqueId
|
|
}
|
|
sendMessage = network.request(Api.functions.messages.sendEncryptedService(peer: inputPeer, randomId: actionRandomId, data: Buffer(data: encryptedPayload)))
|
|
} else {
|
|
if let file = file {
|
|
sendMessage = network.request(Api.functions.messages.sendEncryptedFile(peer: inputPeer, randomId: globallyUniqueId, data: Buffer(data: encryptedPayload), file: file.reference.apiInputFile))
|
|
} else {
|
|
sendMessage = network.request(Api.functions.messages.sendEncrypted(peer: inputPeer, randomId: globallyUniqueId, data: Buffer(data: encryptedPayload)))
|
|
}
|
|
}
|
|
return sendMessage
|
|
|> map { next -> Api.messages.SentEncryptedMessage? in
|
|
return next
|
|
}
|
|
|> `catch`{ _ in
|
|
return .single(nil)
|
|
}
|
|
}
|
|
|
|
private func requestTerminateSecretChat(postbox: Postbox, network: Network, peerId: PeerId, tagLocalIndex: Int32, reportSpam: Bool) -> Signal<Void, NoError> {
|
|
return network.request(Api.functions.messages.discardEncryption(chatId: peerId.id))
|
|
|> map(Optional.init)
|
|
|> `catch` { _ in
|
|
return .single(nil)
|
|
}
|
|
|> mapToSignal { _ -> Signal<Void, NoError> in
|
|
if reportSpam {
|
|
return postbox.transaction { transaction -> TelegramSecretChat? in
|
|
if let peer = transaction.getPeer(peerId) as? TelegramSecretChat {
|
|
return peer
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|> mapToSignal { peer -> Signal<Void, NoError> in
|
|
if let peer = peer {
|
|
return network.request(Api.functions.messages.reportEncryptedSpam(peer: Api.InputEncryptedChat.inputEncryptedChat(chatId: peer.id.id, accessHash: peer.accessHash)))
|
|
|> map(Optional.init)
|
|
|> `catch` { _ -> Signal<Api.Bool?, NoError> in
|
|
return .single(nil)
|
|
}
|
|
|> mapToSignal { result -> Signal<Void, NoError> in
|
|
return postbox.transaction { transaction -> Void in
|
|
if result != nil {
|
|
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
|
if let current = current as? CachedSecretChatData {
|
|
return current.withUpdatedReportStatus(.didReport)
|
|
} else {
|
|
return current
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
return .single(Void())
|
|
}
|
|
}
|
|
} else {
|
|
return .single(Void())
|
|
}
|
|
}
|
|
|> mapToSignal { _ -> Signal<Void, NoError> in
|
|
return postbox.transaction { transaction -> Void in
|
|
markOutgoingOperationAsCompleted(transaction: transaction, peerId: peerId, tagLocalIndex: tagLocalIndex, forceRemove: true)
|
|
}
|
|
}
|
|
}
|