//
//  Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//

#import "OWSMessageDecrypter.h"
#import "NSData+messagePadding.h"
#import "NSString+SSK.h"
#import "NotificationsProtocol.h"
#import "OWSAnalytics.h"
#import "OWSBlockingManager.h"
#import "OWSDevice.h"
#import "OWSError.h"
#import "OWSIdentityManager.h"
#import "OWSPrimaryStorage+PreKeyStore.h"
#import "OWSPrimaryStorage+SessionStore.h"
#import "OWSPrimaryStorage+SignedPreKeyStore.h"
#import "OWSPrimaryStorage.h"
#import "SSKEnvironment.h"
#import "SignalRecipient.h"
#import "TSAccountManager.h"
#import "TSContactThread.h"
#import "TSErrorMessage.h"
#import "TSPreKeyManager.h"
#import <SessionAxolotlKit/AxolotlExceptions.h>
#import <SessionAxolotlKit/NSData+keyVersionByte.h>
#import <SessionAxolotlKit/SessionCipher.h>
#import <SessionCoreKit/NSData+OWS.h>
#import <SessionCoreKit/Randomness.h>
#import <SessionCoreKit/SCKExceptionWrapper.h>
#import <SessionMetadataKit/SessionMetadataKit-Swift.h>
#import <SessionServiceKit/SessionServiceKit-Swift.h>

NS_ASSUME_NONNULL_BEGIN

NSError *EnsureDecryptError(NSError *_Nullable error, NSString *fallbackErrorDescription)
{
    if (error) {
        return error;
    }
    OWSCFailDebug(@"Caller should provide specific error");
    return OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptUDMessage, fallbackErrorDescription);
}

#pragma mark -

@interface OWSMessageDecryptResult ()

@property (nonatomic) NSData *envelopeData;
@property (nonatomic, nullable) NSData *plaintextData;
@property (nonatomic) NSString *source;
@property (nonatomic) UInt32 sourceDevice;
@property (nonatomic) BOOL isUDMessage;

@end

#pragma mark -

@implementation OWSMessageDecryptResult

+ (OWSMessageDecryptResult *)resultWithEnvelopeData:(NSData *)envelopeData
                                      plaintextData:(nullable NSData *)plaintextData
                                             source:(NSString *)source
                                       sourceDevice:(UInt32)sourceDevice
                                        isUDMessage:(BOOL)isUDMessage
{
    OWSAssertDebug(envelopeData);
    OWSAssertDebug(source.length > 0);
    OWSAssertDebug(sourceDevice > 0);

    OWSMessageDecryptResult *result = [OWSMessageDecryptResult new];
    result.envelopeData = envelopeData;
    result.plaintextData = plaintextData;
    result.source = source;
    result.sourceDevice = sourceDevice;
    result.isUDMessage = isUDMessage;
    return result;
}

@end

#pragma mark -

@interface OWSMessageDecrypter ()

@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage;
@property (nonatomic, readonly) LKSessionResetImplementation *sessionResetImplementation;
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;

@end

#pragma mark -

@implementation OWSMessageDecrypter

- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage
{
    self = [super init];

    if (!self) {
        return self;
    }

    _primaryStorage = primaryStorage;
    _sessionResetImplementation = [LKSessionResetImplementation new];
    _dbConnection = primaryStorage.newDatabaseConnection;

    OWSSingletonAssert();

    return self;
}

#pragma mark - Dependencies

- (OWSBlockingManager *)blockingManager
{
    OWSAssertDebug(SSKEnvironment.shared.blockingManager);

    return SSKEnvironment.shared.blockingManager;
}

- (OWSIdentityManager *)identityManager
{
    OWSAssertDebug(SSKEnvironment.shared.identityManager);

    return SSKEnvironment.shared.identityManager;
}

- (id<OWSUDManager>)udManager
{
    OWSAssertDebug(SSKEnvironment.shared.udManager);

    return SSKEnvironment.shared.udManager;
}

- (TSAccountManager *)tsAccountManager
{
    OWSAssertDebug(SSKEnvironment.shared.tsAccountManager);
    
    return SSKEnvironment.shared.tsAccountManager;
}

#pragma mark - Blocking

- (BOOL)isEnvelopeSenderBlocked:(SSKProtoEnvelope *)envelope
{
    OWSAssertDebug(envelope);

    return [self.blockingManager.blockedPhoneNumbers containsObject:envelope.source];
}

#pragma mark - Decryption

- (void)decryptEnvelope:(SSKProtoEnvelope *)envelope
           envelopeData:(NSData *)envelopeData
           successBlock:(DecryptSuccessBlock)successBlockParameter
           failureBlock:(DecryptFailureBlock)failureBlockParameter
{
    OWSAssertDebug(envelope);
    OWSAssertDebug(envelopeData);
    OWSAssertDebug(successBlockParameter);
    OWSAssertDebug(failureBlockParameter);
    OWSAssertDebug([self.tsAccountManager isRegistered]);

    // successBlock is called synchronously so that we can avail ourselves of
    // the transaction.
    //
    // Ensure that failureBlock is called on a worker queue.
    DecryptFailureBlock failureBlock = ^() {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            failureBlockParameter();
        });
    };

    NSString *localRecipientId = self.tsAccountManager.localNumber;
    uint32_t localDeviceId = OWSDevicePrimaryDeviceId;
    DecryptSuccessBlock successBlock = ^(
        OWSMessageDecryptResult *result, YapDatabaseReadWriteTransaction *transaction) {
        // Ensure all blocked messages are discarded.
        if ([self isEnvelopeSenderBlocked:envelope]) {
            OWSLogInfo(@"Ignoring blocked envelope from: %@.", envelope.source);
            return failureBlock();
        }

        if ([result.source isEqualToString:localRecipientId] && result.sourceDevice == localDeviceId) {
            // Self-sent messages should be discarded during the decryption process.
            OWSFailDebug(@"Unexpected self-sent sync message.");
            return failureBlock();
        }

        // Having received a valid (decryptable) message from this user,
        // make note of the fact that they have a valid Signal account.
        [SignalRecipient markRecipientAsRegistered:result.source deviceId:result.sourceDevice transaction:transaction];

        successBlockParameter(result, transaction);
    };

    @try {
        OWSLogInfo(@"Decrypting envelope: %@.", [self descriptionForEnvelope:envelope]);

        if (envelope.type != SSKProtoEnvelopeTypeUnidentifiedSender) {
            if (!envelope.hasSource || envelope.source.length < 1 || ![ECKeyPair isValidHexEncodedPublicKeyWithCandidate:envelope.source]) {
                OWSFailDebug(@"Incoming envelope with invalid source.");
                return failureBlock();
            }
            if (!envelope.hasSourceDevice || envelope.sourceDevice < 1) {
                OWSFailDebug(@"Incoming envelope with invalid source device.");
                return failureBlock();
            }

            // We block UD messages later, after they are decrypted.
            if ([self isEnvelopeSenderBlocked:envelope]) {
                OWSLogInfo(@"Ignoring blocked envelope from: %@.", envelope.source);
                return failureBlock();
            }
        }

        switch (envelope.type) {
            case SSKProtoEnvelopeTypeFallbackMessage: {
                [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
                    [self decryptFallbackMessage:envelope
                         envelopeData:envelopeData
                         successBlock:^(OWSMessageDecryptResult *result) {
                             OWSLogDebug(@"Decrypted fallback message.");
                             successBlock(result, transaction);
                         }
                         failureBlock:^(NSError * _Nullable error) {
                             OWSLogError(@"Decrypting fallback message from: %@ failed with error: %@.",
                                envelopeAddress(envelope),
                                error);
                             failureBlock();
                         }];
                } error:nil];
                // Return to avoid double-acknowledging
                return;
            }
            case SSKProtoEnvelopeTypeCiphertext: {
                [self throws_decryptSecureMessage:envelope
                    envelopeData:envelopeData
                    successBlock:^(OWSMessageDecryptResult *result, YapDatabaseReadWriteTransaction *transaction) {
                        OWSLogDebug(@"Decrypted secure message.");
                        successBlock(result, transaction);
                    }
                    failureBlock:^(NSError *_Nullable error) {
                        OWSLogError(@"Decrypting secure message from: %@ failed with error: %@.",
                            envelopeAddress(envelope),
                            error);
                        OWSProdError([OWSAnalyticsEvents messageManagerErrorCouldNotHandleSecureMessage]);
                        failureBlock();
                    }];
                // Return to avoid double-acknowledging.
                return;
            }
            case SSKProtoEnvelopeTypePrekeyBundle: {
                [self throws_decryptPreKeyBundle:envelope
                    envelopeData:envelopeData
                    successBlock:^(OWSMessageDecryptResult *result, YapDatabaseReadWriteTransaction *transaction) {
                        OWSLogDebug(@"Decrypted pre key bundle message.");
                        successBlock(result, transaction);
                    }
                    failureBlock:^(NSError *_Nullable error) {
                        OWSLogError(@"Decrypting pre key bundle message from: %@ failed with error: %@.",
                            envelopeAddress(envelope),
                            error);
                        OWSProdError([OWSAnalyticsEvents messageManagerErrorCouldNotHandlePrekeyBundle]);
                        failureBlock();
                    }];
                // Return to avoid double-acknowledging.
                return;
            }
            // These message types don't have a payload to decrypt.
            case SSKProtoEnvelopeTypeReceipt:
            case SSKProtoEnvelopeTypeKeyExchange:
            case SSKProtoEnvelopeTypeUnknown: {
                [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
                    OWSMessageDecryptResult *result =
                        [OWSMessageDecryptResult resultWithEnvelopeData:envelopeData
                                                          plaintextData:nil
                                                                 source:envelope.source
                                                           sourceDevice:envelope.sourceDevice
                                                            isUDMessage:NO];
                    successBlock(result, transaction);
                } error:nil];
                // Return to avoid double-acknowledging.
                return;
            }
            case SSKProtoEnvelopeTypeClosedGroupCiphertext: // Loki: Fall through
            case SSKProtoEnvelopeTypeUnidentifiedSender: {
                [self decryptUnidentifiedSender:envelope
                    successBlock:^(OWSMessageDecryptResult *result, YapDatabaseReadWriteTransaction *transaction) {
                        OWSLogDebug(@"Decrypted unidentified sender message.");
                        successBlock(result, transaction);
                    }
                    failureBlock:^(NSError *_Nullable error) {
                        OWSLogError(@"Decrypting unidentified sender message from: %@ failed with error: %@.",
                            envelopeAddress(envelope),
                            error);
                        OWSProdError([OWSAnalyticsEvents messageManagerErrorCouldNotHandleUnidentifiedSenderMessage]);
                        failureBlock();
                    }];
                // Return to avoid double-acknowledging.
                return;
            }
            default:
                OWSLogWarn(@"Received unhandled envelope type: %d.", (int)envelope.type);
                break;
        }
    } @catch (NSException *exception) {
        OWSFailDebug(@"Received an invalid envelope: %@.", exception.debugDescription);
        OWSProdFail([OWSAnalyticsEvents messageManagerErrorInvalidProtocolMessage]);

        [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
            TSErrorMessage *errorMessage = [TSErrorMessage corruptedMessageInUnknownThread];
            [SSKEnvironment.shared.notificationsManager notifyUserForThreadlessErrorMessage:errorMessage
                                                                                transaction:transaction];
        } error:nil];
    }

    failureBlock();
}

- (void)decryptFallbackMessage:(SSKProtoEnvelope *)envelope
                  envelopeData:(NSData *)envelopeData
                  successBlock:(void (^)(OWSMessageDecryptResult *result))successBlock
                  failureBlock:(void (^)(NSError *_Nullable error))failureBlock
{
    OWSAssertDebug(envelope);
    OWSAssertDebug(envelopeData);
    OWSAssertDebug(successBlock);
    OWSAssertDebug(failureBlock);
    
    NSData *ivAndCiphertext = envelope.content;
    if (ivAndCiphertext == nil) {
        OWSProdFail([OWSAnalyticsEvents messageManagerErrorMessageEnvelopeHasNoContent]);
        NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptMessage, @"Envelope has no content.");
        return failureBlock(error);
    }
    
    NSString *recipientId = envelope.source;
    ECKeyPair *identityKeyPair = self.identityManager.identityKeyPair;
    FallBackSessionCipher *cipher = [[FallBackSessionCipher alloc] initWithRecipientPublicKey:recipientId privateKey:identityKeyPair.privateKey];

    NSData *_Nullable plaintext = [[cipher decrypt:ivAndCiphertext] removePadding];
    if (plaintext == nil) {
        NSString *errorDescription = [NSString stringWithFormat:@"Failed to decrypt fallback message from: %@.", recipientId];
        NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptMessage, errorDescription);
        return failureBlock(error);
    }
   
    OWSMessageDecryptResult *result = [OWSMessageDecryptResult resultWithEnvelopeData:envelopeData
                                                                        plaintextData:plaintext
                                                                               source:envelope.source
                                                                         sourceDevice:envelope.sourceDevice
                                                                          isUDMessage:NO];
    successBlock(result);
}

- (void)throws_decryptSecureMessage:(SSKProtoEnvelope *)envelope
                       envelopeData:(NSData *)envelopeData
                       successBlock:(DecryptSuccessBlock)successBlock
                       failureBlock:(void (^)(NSError *_Nullable error))failureBlock
{
    OWSAssertDebug(envelope);
    OWSAssertDebug(envelopeData);
    OWSAssertDebug(successBlock);
    OWSAssertDebug(failureBlock);

    [self decryptEnvelope:envelope
              envelopeData:envelopeData
            cipherTypeName:@"Secure Message"
        cipherMessageBlock:^(NSData *encryptedData) {
            return [[WhisperMessage alloc] init_throws_withData:encryptedData];
        }
              successBlock:successBlock
              failureBlock:failureBlock];
}

- (void)throws_decryptPreKeyBundle:(SSKProtoEnvelope *)envelope
                      envelopeData:(NSData *)envelopeData
                      successBlock:(DecryptSuccessBlock)successBlock
                      failureBlock:(void (^)(NSError *_Nullable error))failureBlock
{
    OWSAssertDebug(envelope);
    OWSAssertDebug(envelopeData);
    OWSAssertDebug(successBlock);
    OWSAssertDebug(failureBlock);

    // Check whether we need to refresh our PreKeys every time we receive a PreKeyWhisperMessage.
    [TSPreKeyManager checkPreKeys];

    [self decryptEnvelope:envelope
              envelopeData:envelopeData
            cipherTypeName:@"PreKey Bundle"
        cipherMessageBlock:^(NSData *encryptedData) {
            return [[PreKeyWhisperMessage alloc] init_throws_withData:encryptedData];
        }
              successBlock:successBlock
              failureBlock:failureBlock];
}

- (void)decryptEnvelope:(SSKProtoEnvelope *)envelope
           envelopeData:(NSData *)envelopeData
         cipherTypeName:(NSString *)cipherTypeName
     cipherMessageBlock:(id<CipherMessage> (^_Nonnull)(NSData *))cipherMessageBlock
           successBlock:(DecryptSuccessBlock)successBlock
           failureBlock:(void (^)(NSError *_Nullable error))failureBlock
{
    OWSAssertDebug(envelope);
    OWSAssertDebug(envelopeData);
    OWSAssertDebug(cipherTypeName.length > 0);
    OWSAssertDebug(cipherMessageBlock);
    OWSAssertDebug(successBlock);
    OWSAssertDebug(failureBlock);

    NSString *recipientId = envelope.source;
    int deviceId = envelope.sourceDevice;

    // DEPRECATED - Remove `legacyMessage` after all clients have been upgraded.
    NSData *encryptedData = envelope.content ?: envelope.legacyMessage;
    if (!encryptedData) {
        OWSProdFail([OWSAnalyticsEvents messageManagerErrorMessageEnvelopeHasNoContent]);
        NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptMessage, @"Envelope has no content.");
        return failureBlock(error);
    }

    [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
        @try {
            id<CipherMessage> cipherMessage = cipherMessageBlock(encryptedData);
            LKSessionCipher *cipher = [[LKSessionCipher alloc]
                                       initWithSessionResetImplementation:self.sessionResetImplementation
                                       sessionStore:self.primaryStorage
                                       preKeyStore:self.primaryStorage
                                       signedPreKeyStore:self.primaryStorage
                                       identityKeyStore:self.identityManager
                                       recipientID:recipientId
                                       deviceID:deviceId];

            // plaintextData may be nil for some envelope types.
            NSError *error = nil;
            NSData *_Nullable decryptedData = [cipher decrypt:cipherMessage protocolContext:transaction error:&error];
            // Throw if we got an error
            SCKRaiseIfExceptionWrapperError(error);
            NSData *_Nullable plaintextData = decryptedData != nil ? [decryptedData removePadding] : nil;

            OWSMessageDecryptResult *result = [OWSMessageDecryptResult resultWithEnvelopeData:envelopeData
                                                                                plaintextData:plaintextData
                                                                                       source:envelope.source
                                                                                 sourceDevice:envelope.sourceDevice
                                                                                  isUDMessage:NO];
            successBlock(result, transaction);
        } @catch (NSException *exception) {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                [self processException:exception envelope:envelope];
                NSString *errorDescription = [NSString
                    stringWithFormat:@"Exception while decrypting %@: %@.", cipherTypeName, exception.description];
                OWSLogError(@"%@", errorDescription);
                NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptMessage, errorDescription);
                failureBlock(error);
            });
        }
    }];
}

- (void)decryptUnidentifiedSender:(SSKProtoEnvelope *)envelope
                     successBlock:(DecryptSuccessBlock)successBlock
                     failureBlock:(void (^)(NSError *_Nullable error))failureBlock
{
    OWSAssertDebug(envelope);
    OWSAssertDebug(successBlock);
    OWSAssertDebug(failureBlock);

    // NOTE: We don't need to bother with `legacyMessage` for UD messages.
    NSData *encryptedData = envelope.content;
    if (!encryptedData) {
        NSString *errorDescription = @"UD Envelope is missing content.";
        OWSFailDebug(@"%@", errorDescription);
        NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptUDMessage, errorDescription);
        return failureBlock(error);
    }

    UInt64 serverTimestamp = envelope.timestamp;

    id<SMKCertificateValidator> certificateValidator =
        [[SMKCertificateDefaultValidator alloc] initWithTrustRoot:self.udManager.trustRoot];

    NSString *localRecipientId = self.tsAccountManager.localNumber;
    uint32_t localDeviceId = OWSDevicePrimaryDeviceId;

    [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
        NSError *cipherError;
        SMKSecretSessionCipher *_Nullable cipher =
            [[SMKSecretSessionCipher alloc] initWithSessionResetImplementation:self.sessionResetImplementation
                                                                  sessionStore:self.primaryStorage
                                                                   preKeyStore:self.primaryStorage
                                                             signedPreKeyStore:self.primaryStorage
                                                                 identityStore:self.identityManager
                                                sharedSenderKeysImplementation:LKSharedSenderKeysImplementation.shared
                                                           error:&cipherError];

        if (cipherError || !cipher) {
            OWSFailDebug(@"Could not create secret session cipher: %@.", cipherError);
            cipherError = EnsureDecryptError(cipherError, @"Could not create secret session cipher.");
            return failureBlock(cipherError);
        }

        NSError *decryptError;
        SMKDecryptResult *_Nullable decryptResult =
            [cipher throwswrapped_decryptMessageWithCertificateValidator:certificateValidator
                                                         senderPublicKey:envelope.source
                                                          cipherTextData:encryptedData
                                                               timestamp:serverTimestamp
                                                        localRecipientId:localRecipientId
                                                           localDeviceId:localDeviceId
                                                         protocolContext:transaction
                                                                   error:&decryptError];

        if (!decryptResult) {
            if (!decryptError) {
                OWSFailDebug(@"Caller should provide specific error.");
                NSError *error = OWSErrorWithCodeDescription(
                    OWSErrorCodeFailedToDecryptUDMessage, @"Could not decrypt UD message.");
                return failureBlock(error);
            }

            // Decrypt Failure Part 1: Unwrap failure details

            NSError *_Nullable underlyingError;
            SSKProtoEnvelope *_Nullable identifiedEnvelope;

            if (![decryptError.domain isEqualToString:@"SignalMetadataKit.SecretSessionKnownSenderError"]) {
                underlyingError = decryptError;
                identifiedEnvelope = envelope;
            } else {
                underlyingError = decryptError.userInfo[NSUnderlyingErrorKey];

                NSString *senderRecipientId
                    = decryptError.userInfo[SecretSessionKnownSenderError.kSenderRecipientIdKey];
                OWSAssert(senderRecipientId);

                NSNumber *senderDeviceId = decryptError.userInfo[SecretSessionKnownSenderError.kSenderDeviceIdKey];
                OWSAssert(senderDeviceId);

                SSKProtoEnvelopeBuilder *identifiedEnvelopeBuilder = envelope.asBuilder;
                identifiedEnvelopeBuilder.source = senderRecipientId;
                identifiedEnvelopeBuilder.sourceDevice = senderDeviceId.unsignedIntValue;
                NSError *identifiedEnvelopeBuilderError;

                identifiedEnvelope = [identifiedEnvelopeBuilder buildAndReturnError:&identifiedEnvelopeBuilderError];
                if (identifiedEnvelopeBuilderError) {
                    OWSFailDebug(@"identifiedEnvelopeBuilderError: %@", identifiedEnvelopeBuilderError);
                }
            }
            OWSAssert(underlyingError);
            OWSAssert(identifiedEnvelope);

            NSException *_Nullable underlyingException;
            if ([underlyingError.domain isEqualToString:SCKExceptionWrapperErrorDomain]
                && underlyingError.code == SCKExceptionWrapperErrorThrown) {

                underlyingException = underlyingError.userInfo[SCKExceptionWrapperUnderlyingExceptionKey];
                OWSAssert(underlyingException);
            }

            // Decrypt Failure Part 2: Handle unwrapped failure details

            if (underlyingException) {
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                    [self processException:underlyingException envelope:identifiedEnvelope];
                    NSString *errorDescription = [NSString
                        stringWithFormat:@"Exception while decrypting UD message: %@.", underlyingException.description];
                    OWSLogError(@"%@", errorDescription);
                    NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptMessage, errorDescription);
                    failureBlock(error);
                });
                return;
            }

            if ([underlyingError.domain isEqualToString:@"SignalMetadataKit.SMKSecretSessionCipherError"]
                && underlyingError.code == SMKSecretSessionCipherErrorSelfSentMessage) {
                // Self-sent messages can be safely discarded.
                failureBlock(underlyingError);
                return;
            }

            // FIXME: This is a temporary patch for bad mac issues. At least with this people will be able to message again. We have to figure out the root cause of the issue though.
            if ([decryptError userInfo][NSUnderlyingErrorKey] != nil) {
                NSDictionary *underlyingErrorUserInfo = [[decryptError userInfo][NSUnderlyingErrorKey] userInfo];
                if (underlyingErrorUserInfo[SCKExceptionWrapperUnderlyingExceptionKey] != nil) {
                    NSException *underlyingUnderlyingError = underlyingErrorUserInfo[SCKExceptionWrapperUnderlyingExceptionKey];
                    if ([[underlyingUnderlyingError reason] hasPrefix:@"Bad Mac!"]) {
                        if ([underlyingError userInfo][@"kSenderRecipientIdKey"] != nil) {
                            NSString *senderPublicKey = [underlyingError userInfo][@"kSenderRecipientIdKey"];
                            TSContactThread *thread = [TSContactThread getThreadWithContactId:senderPublicKey transaction:transaction];
                            if (thread != nil) {
                                [thread addSessionRestoreDevice:senderPublicKey transaction:transaction];
                                [LKSessionManagementProtocol startSessionResetInThread:thread transaction:transaction];
                            }
                        }
                    }
                }
            }

            failureBlock(underlyingError);
            return;
        }

        if (decryptResult.messageType == SMKMessageTypePrekey) {
            [TSPreKeyManager checkPreKeys];
        }

        NSString *source = decryptResult.senderRecipientId;
        if (source.length < 1) {
            NSString *errorDescription = @"Invalid UD sender.";
            OWSFailDebug(@"%@", errorDescription);
            NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptUDMessage, errorDescription);
            return failureBlock(error);
        }

        long sourceDeviceId = decryptResult.senderDeviceId;
        if (sourceDeviceId < 1 || sourceDeviceId > UINT32_MAX) {
            NSString *errorDescription = @"Invalid UD sender device ID.";
            OWSFailDebug(@"%@", errorDescription);
            NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptUDMessage, errorDescription);
            return failureBlock(error);
        }
        NSData *plaintextData = [decryptResult.paddedPayload removePadding];

        SSKProtoEnvelopeBuilder *envelopeBuilder = [envelope asBuilder];
        [envelopeBuilder setSource:source];
        [envelopeBuilder setSourceDevice:(uint32_t)sourceDeviceId];
        if (decryptResult.messageType == SMKMessageTypeFallback) {
            [envelopeBuilder setType:SSKProtoEnvelopeTypeFallbackMessage];
            OWSLogInfo(@"SMKMessageTypeFallback");
        }
        NSError *envelopeBuilderError;
        NSData *_Nullable newEnvelopeData = [envelopeBuilder buildSerializedDataAndReturnError:&envelopeBuilderError];
        if (envelopeBuilderError || !newEnvelopeData) {
            OWSFailDebug(@"Could not update UD envelope data: %@", envelopeBuilderError);
            NSError *error = EnsureDecryptError(envelopeBuilderError, @"Could not update UD envelope data");
            return failureBlock(error);
        }

        OWSMessageDecryptResult *result = [OWSMessageDecryptResult resultWithEnvelopeData:newEnvelopeData
                                                                            plaintextData:plaintextData
                                                                                   source:source
                                                                             sourceDevice:(uint32_t)sourceDeviceId
                                                                              isUDMessage:YES];
        successBlock(result, transaction);
    }];
}

- (void)processException:(NSException *)exception envelope:(SSKProtoEnvelope *)envelope
{
    OWSLogError(
        @"Got exception: %@ of type: %@ with reason: %@", exception.description, exception.name, exception.reason);

    [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
        TSErrorMessage *errorMessage;

        if (envelope.source.length == 0) {
            TSErrorMessage *errorMessage = [TSErrorMessage corruptedMessageInUnknownThread];
            [SSKEnvironment.shared.notificationsManager notifyUserForThreadlessErrorMessage:errorMessage
                                                                                transaction:transaction];
            return;
        }

        if ([exception.name isEqualToString:NoSessionException]) {
            OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorNoSession], envelope);
            errorMessage = [TSErrorMessage missingSessionWithEnvelope:envelope withTransaction:transaction];
        } else if ([exception.name isEqualToString:InvalidKeyException]) {
            OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorInvalidKey], envelope);
            errorMessage = [TSErrorMessage invalidKeyExceptionWithEnvelope:envelope withTransaction:transaction];
        } else if ([exception.name isEqualToString:InvalidKeyIdException]) {
            OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorInvalidKeyId], envelope);
            errorMessage = [TSErrorMessage invalidKeyExceptionWithEnvelope:envelope withTransaction:transaction];
        } else if ([exception.name isEqualToString:DuplicateMessageException]) {
            // Duplicate messages are silently discarded.
            return;
        } else if ([exception.name isEqualToString:InvalidVersionException]) {
            OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorInvalidMessageVersion], envelope);
            errorMessage = [TSErrorMessage invalidVersionWithEnvelope:envelope withTransaction:transaction];
        } else if ([exception.name isEqualToString:UntrustedIdentityKeyException]) {
            // Should no longer get here, since we now record the new identity for incoming messages.
            OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorUntrustedIdentityKeyException], envelope);
            OWSFailDebug(@"Failed to trust identity on incoming message from: %@", envelopeAddress(envelope));
            return;
        } else {
            OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorCorruptMessage], envelope);
            errorMessage = [TSErrorMessage corruptedMessageWithEnvelope:envelope withTransaction:transaction];
        }

        OWSAssertDebug(errorMessage);
        if (errorMessage != nil) {
            [errorMessage saveWithTransaction:transaction];
            [LKSessionManagementProtocol handleDecryptionError:errorMessage.errorType forPublicKey:envelope.source transaction:transaction];
            [self notifyUserForErrorMessage:errorMessage envelope:envelope transaction:transaction];
        }
    } error:nil];
}

- (void)notifyUserForErrorMessage:(TSErrorMessage *)errorMessage
                         envelope:(SSKProtoEnvelope *)envelope
                      transaction:(YapDatabaseReadWriteTransaction *)transaction
{
    NSString *masterPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:envelope.source in:transaction] ?: envelope.source;
    TSThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:masterPublicKey transaction:transaction];
    [SSKEnvironment.shared.notificationsManager notifyUserForErrorMessage:errorMessage
                                                                   thread:contactThread
                                                              transaction:transaction];
}

@end

NS_ASSUME_NONNULL_END
