Signal-iOS/SignalServiceKit/Messages/MessageSender+Errors.swift
Jordan Rose b0de59f2e2 Remove required from every init that is not dynamically dispatched
This included:
- Removing unavailable inits wholesale if no longer `required`
- Marking a few classes `final` so they could continue using
  `Self(...)` rather than `OWSWhatever(...)`
2024-04-01 15:27:20 -07:00

560 lines
18 KiB
Swift

//
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import LibSignalClient
public enum MessageSenderError: Error, IsRetryableProvider, UserErrorDescriptionProvider {
case prekeyRateLimit
case missingDevice
case blockedContactRecipient
case threadMissing
public var localizedDescription: String {
switch self {
case .blockedContactRecipient:
return OWSLocalizedString(
"ERROR_DESCRIPTION_MESSAGE_SEND_FAILED_DUE_TO_BLOCK_LIST",
comment: "Error message indicating that message send failed due to block list"
)
case .prekeyRateLimit, .missingDevice, .threadMissing:
return OWSLocalizedString(
"MESSAGE_STATUS_SEND_FAILED",
comment: "Label indicating that a message failed to send."
)
}
}
// MARK: - IsRetryableProvider
public var isRetryableProvider: Bool {
switch self {
case .prekeyRateLimit:
// TODO: Retry with backoff.
// TODO: Can we honor a retry delay hint from the response?
return true
case .missingDevice:
return true
case .blockedContactRecipient:
return false
case .threadMissing:
return false
}
}
}
// MARK: -
extension NSError {
@objc
public var shouldBeIgnoredForNonContactThreads: Bool {
(self as Error).shouldBeIgnoredForNonContactThreads
}
}
// MARK: -
extension Error {
public var shouldBeIgnoredForNonContactThreads: Bool {
self is MessageSenderNoSuchSignalRecipientError
}
}
// MARK: -
extension NSError {
@objc
public var isFatalError: Bool { isFatalErrorImpl }
fileprivate var isFatalErrorImpl: Bool {
let error: Error = self as Error
switch error {
case is MessageSenderNoSessionForTransientMessageError:
return true
case is UntrustedIdentityError:
return true
case is SignalServiceRateLimitedError:
// Avoid exacerbating the rate limiting.
return true
case is MessageDeletedBeforeSentError:
return true
default:
// Default to NOT fatal.
return false
}
}
}
// MARK: -
extension Error {
public var isFatalError: Bool { (self as NSError).isFatalErrorImpl }
}
// MARK: -
@objc
public class MessageSenderNoSuchSignalRecipientError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
// NSError bridging: the domain of the error.
public static var errorDomain: String { OWSSignalServiceKitErrorDomain }
// NSError bridging: the error code within the given domain.
public var errorCode: Int { OWSErrorCode.noSuchSignalRecipient.rawValue }
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: localizedDescription]
}
public var localizedDescription: String {
OWSLocalizedString(
"ERROR_DESCRIPTION_UNREGISTERED_RECIPIENT",
comment: "Error message when attempting to send message"
)
}
@objc
public class func isNoSuchSignalRecipientError(_ error: Error?) -> Bool {
error is MessageSenderNoSuchSignalRecipientError
}
// MARK: - IsRetryableProvider
// No need to retry if the recipient is not registered.
public var isRetryableProvider: Bool { false }
}
// MARK: -
@objc
class MessageSenderErrorNoValidRecipients: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
@objc
public static var asNSError: NSError {
MessageSenderErrorNoValidRecipients() as Error as NSError
}
// NSError bridging: the domain of the error.
public static var errorDomain: String { OWSSignalServiceKitErrorDomain }
// NSError bridging: the error code within the given domain.
public var errorCode: Int { OWSErrorCode.messageSendNoValidRecipients.rawValue }
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: self.localizedDescription]
}
public var localizedDescription: String {
OWSLocalizedString(
"ERROR_DESCRIPTION_NO_VALID_RECIPIENTS",
comment: "Error indicating that an outgoing message had no valid recipients."
)
}
public var isRetryableProvider: Bool { false }
}
// MARK: -
class MessageSenderNoSessionForTransientMessageError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
// NSError bridging: the domain of the error.
public static var errorDomain: String { OWSSignalServiceKitErrorDomain }
// NSError bridging: the error code within the given domain.
public var errorCode: Int { OWSErrorCode.noSessionForTransientMessage.rawValue }
public var isRetryableProvider: Bool { false }
public var localizedDescription: String {
// These messages are never presented to the user, since these errors only
// occur to transient messages. We only specify an error to avoid an assert.
return OWSLocalizedString(
"ERROR_DESCRIPTION_UNKNOWN_ERROR",
comment: "Worst case generic error message"
)
}
}
// MARK: -
public class UntrustedIdentityError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
public let serviceId: ServiceId
init(serviceId: ServiceId) {
self.serviceId = serviceId
}
// NSError bridging: the domain of the error.
public static var errorDomain: String { OWSSignalServiceKitErrorDomain }
public static var errorCode: Int { OWSErrorCode.untrustedIdentity.rawValue }
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: self.localizedDescription]
}
public var localizedDescription: String {
let format = OWSLocalizedString(
"FAILED_SENDING_BECAUSE_UNTRUSTED_IDENTITY_KEY",
comment: "action sheet header when re-sending message which failed because of untrusted identity keys"
)
return String(format: format, databaseStorage.read { tx in
return contactsManager.displayName(for: SignalServiceAddress(serviceId), tx: tx).resolvedValue()
})
}
// NSError bridging: the error code within the given domain.
public var errorCode: Int { Self.errorCode }
/// Key will continue to be unaccepted, so no need to retry. It'll only
/// cause us to hit the Pre-Key request rate limit.
public var isRetryableProvider: Bool { false }
}
public class InvalidKeySignatureError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
public let serviceId: ServiceId
public let isTerminalFailure: Bool
init(serviceId: ServiceId, isTerminalFailure: Bool) {
self.serviceId = serviceId
self.isTerminalFailure = isTerminalFailure
}
// NSError bridging: the domain of the error.
public static var errorDomain: String { OWSSignalServiceKitErrorDomain }
public static var errorCode: Int { OWSErrorCode.invalidKeySignature.rawValue }
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: self.localizedDescription]
}
public var localizedDescription: String {
let format = OWSLocalizedString(
"FAILED_SENDING_BECAUSE_INVALID_KEY_SIGNATURE",
comment: "action sheet header when re-sending message which failed because of an invalid key signature"
)
return String(format: format, databaseStorage.read { tx in
return contactsManager.displayName(for: SignalServiceAddress(serviceId), tx: tx).resolvedValue()
})
}
// NSError bridging: the error code within the given domain.
public var errorCode: Int { Self.errorCode }
/// Key will continue to be invalidly signed, so no need to retry. It'll only
/// cause us to hit the Pre-Key request rate limit.
public var isRetryableProvider: Bool {
!isTerminalFailure
}
}
// MARK: -
class SignalServiceRateLimitedError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
// NSError bridging: the domain of the error.
@objc
public static var errorDomain: String { OWSSignalServiceKitErrorDomain }
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: self.localizedDescription]
}
public var localizedDescription: String {
OWSLocalizedString(
"FAILED_SENDING_BECAUSE_RATE_LIMIT",
comment: "action sheet header when re-sending message which failed because of too many attempts"
)
}
// NSError bridging: the error code within the given domain.
public var errorCode: Int { OWSErrorCode.signalServiceRateLimited.rawValue }
// We're already rate-limited. No need to exacerbate the problem.
public var isRetryableProvider: Bool { false }
}
// MARK: -
@objc
class SpamChallengeRequiredError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
// NSError bridging: the domain of the error.
@objc
public static var errorDomain: String { OWSSignalServiceKitErrorDomain }
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: self.localizedDescription]
}
public var localizedDescription: String {
OWSLocalizedString(
"ERROR_DESCRIPTION_SUSPECTED_SPAM",
comment: "Description for errors returned from the server due to suspected spam."
)
}
// NSError bridging: the error code within the given domain.
public var errorCode: Int { OWSErrorCode.serverRejectedSuspectedSpam.rawValue }
public var isRetryableProvider: Bool { false }
@objc
public class func isSpamChallengeRequiredError(_ error: Error) -> Bool {
error is SpamChallengeRequiredError
}
}
// MARK: -
@objc
class SpamChallengeResolvedError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
// NSError bridging: the domain of the error.
public static var errorDomain: String { OWSSignalServiceKitErrorDomain }
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: self.localizedDescription]
}
public var localizedDescription: String {
OWSLocalizedString(
"ERROR_DESCRIPTION_SUSPECTED_SPAM",
comment: "Description for errors returned from the server due to suspected spam."
)
}
// NSError bridging: the error code within the given domain.
public var errorCode: Int { OWSErrorCode.serverRejectedSuspectedSpam.rawValue }
public var isRetryableProvider: Bool { true }
@objc
public class func isSpamChallengeResolvedError(_ error: Error) -> Bool {
error is SpamChallengeResolvedError
}
}
// MARK: -
@objc
class OWSRetryableMessageSenderError: NSObject, Error, IsRetryableProvider {
@objc
public static var asNSError: NSError {
OWSRetryableMessageSenderError() as Error as NSError
}
// MARK: - IsRetryableProvider
public var isRetryableProvider: Bool { true }
}
// MARK: -
// NOTE: We typically prefer to use a more specific error.
@objc
class OWSUnretryableMessageSenderError: NSObject, Error, IsRetryableProvider {
@objc
public static var asNSError: NSError {
OWSUnretryableMessageSenderError() as Error as NSError
}
// MARK: - IsRetryableProvider
public var isRetryableProvider: Bool { false }
}
// MARK: -
@objc
public class AppExpiredError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
@objc
public static var asNSError: NSError {
AppExpiredError() as Error as NSError
}
// NSError bridging: the domain of the error.
@objc
public static var errorDomain: String { OWSSignalServiceKitErrorDomain }
// NSError bridging: the error code within the given domain.
@objc
public static var errorCode: Int { OWSErrorCode.appExpired.rawValue }
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: self.localizedDescription]
}
public var localizedDescription: String {
OWSLocalizedString("ERROR_SENDING_EXPIRED",
comment: "Error indicating a send failure due to an expired application.")
}
// NSError bridging: the error code within the given domain.
public var errorCode: Int { Self.errorCode }
public var isRetryableProvider: Bool { false }
}
// MARK: -
@objc
public class AppDeregisteredError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
@objc
public static var asNSError: NSError {
AppDeregisteredError() as Error as NSError
}
// NSError bridging: the domain of the error.
@objc
public static var errorDomain: String { OWSSignalServiceKitErrorDomain }
// NSError bridging: the error code within the given domain.
@objc
public static var errorCode: Int { OWSErrorCode.appDeregistered.rawValue }
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: self.localizedDescription]
}
public var localizedDescription: String {
DependenciesBridge.shared.tsAccountManager.registrationStateWithMaybeSneakyTransaction.isPrimaryDevice ?? true
? OWSLocalizedString("ERROR_SENDING_DEREGISTERED",
comment: "Error indicating a send failure due to a deregistered application.")
: OWSLocalizedString("ERROR_SENDING_DELINKED",
comment: "Error indicating a send failure due to a delinked application.")
}
// NSError bridging: the error code within the given domain.
public var errorCode: Int { Self.errorCode }
public var isRetryableProvider: Bool { false }
}
// MARK: -
@objc
class MessageDeletedBeforeSentError: NSObject, CustomNSError, IsRetryableProvider {
@objc
public static var asNSError: NSError {
MessageDeletedBeforeSentError() as Error as NSError
}
// NSError bridging: the domain of the error.
public static var errorDomain: String { OWSSignalServiceKitErrorDomain }
// NSError bridging: the error code within the given domain.
public var errorCode: Int { OWSErrorCode.messageDeletedBeforeSent.rawValue }
public var isRetryableProvider: Bool { false }
}
// MARK: -
class SenderKeyEphemeralError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
private let customLocalizedDescription: String
init(customLocalizedDescription: String) {
self.customLocalizedDescription = customLocalizedDescription
}
// NSError bridging: the domain of the error.
public static var errorDomain: String { OWSSignalServiceKitErrorDomain }
// NSError bridging: the error code within the given domain.
public var errorCode: Int { OWSErrorCode.senderKeyEphemeralFailure.rawValue }
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: self.localizedDescription]
}
public var localizedDescription: String { customLocalizedDescription }
public var isRetryableProvider: Bool { true }
}
// MARK: -
class SenderKeyUnavailableError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
private let customLocalizedDescription: String
init(customLocalizedDescription: String) {
self.customLocalizedDescription = customLocalizedDescription
}
// NSError bridging: the domain of the error.
public static var errorDomain: String { OWSSignalServiceKitErrorDomain }
public static var errorCode: Int { OWSErrorCode.senderKeyUnavailable.rawValue }
// NSError bridging: the error code within the given domain.
public var errorCode: Int { Self.errorCode }
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: self.localizedDescription]
}
public var localizedDescription: String { customLocalizedDescription }
/// These errors are retryable in the sense that a sent message can be
/// retried and be successful, just not with sender key. If any intended
/// recipient has previously failed with this error code in a prior send,
/// the next send attempt will restrict itself to fanout send and not use
/// sender key.
public var isRetryableProvider: Bool { true }
}
// MARK: -
class MessageSendUnauthorizedError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
// NSError bridging: the domain of the error.
public static var errorDomain: String { OWSSignalServiceKitErrorDomain }
// NSError bridging: the error code within the given domain.
public var errorCode: Int { OWSErrorCode.messageSendUnauthorized.rawValue }
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: self.localizedDescription]
}
public var localizedDescription: String {
OWSLocalizedString(
"ERROR_DESCRIPTION_SENDING_UNAUTHORIZED",
comment: "Error message when attempting to send message"
)
}
// No need to retry if we've been de-authed.
public var isRetryableProvider: Bool { false }
}
// MARK: -
class MessageSendEncryptionError: NSObject, CustomNSError, IsRetryableProvider {
public let serviceId: ServiceId
public let deviceId: UInt32
init(serviceId: ServiceId, deviceId: UInt32) {
self.serviceId = serviceId
self.deviceId = deviceId
}
// NSError bridging: the domain of the error.
public static var errorDomain: String { OWSSignalServiceKitErrorDomain }
// NSError bridging: the error code within the given domain.
public var errorCode: Int { OWSErrorCode.messageSendEncryptionFailure.rawValue }
public var isRetryableProvider: Bool { true }
}