157 lines
5.5 KiB
Swift
157 lines
5.5 KiB
Swift
//
|
|
// Copyright 2023 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
import LibSignalClient
|
|
|
|
/// Represents an incoming envelope that's been validated but not decrypted.
|
|
///
|
|
/// If your method accepts this type, then it can rely on the validation
|
|
/// performed in the initializer because that's the ONLY way to produce an
|
|
/// object of this type.
|
|
///
|
|
/// This type also converts values it validates into new types with
|
|
/// stricter, compile-time guarantees. For example, the destination of the
|
|
/// envelope is converted to the two-case `localIdentity` enum rather than
|
|
/// an arbitrary `ServiceId`. Similarly, the envelope's type is converted to
|
|
/// a `Kind` enum; this enum eliminates the need to repeatedly consider all
|
|
/// possible envelope types in downstream code.
|
|
class ValidatedIncomingEnvelope {
|
|
let timestamp: UInt64
|
|
let serverTimestamp: UInt64
|
|
let envelope: SSKProtoEnvelope
|
|
let kind: Kind
|
|
let localIdentity: OWSIdentity
|
|
let localServiceId: ServiceId
|
|
|
|
init(_ envelope: SSKProtoEnvelope, localIdentifiers: LocalIdentifiers) throws {
|
|
self.envelope = envelope
|
|
|
|
guard envelope.timestamp >= 1, SDS.fitsInInt64(envelope.timestamp) else {
|
|
throw OWSAssertionError("Invalid timestamp.")
|
|
}
|
|
self.timestamp = envelope.timestamp
|
|
|
|
guard envelope.hasServerTimestamp, SDS.fitsInInt64(envelope.serverTimestamp) else {
|
|
throw OWSAssertionError("Invalid serverTimestamp.")
|
|
}
|
|
self.serverTimestamp = envelope.serverTimestamp
|
|
|
|
let kind: Kind
|
|
switch envelope.type {
|
|
case .receipt:
|
|
kind = .serverReceipt
|
|
case .ciphertext:
|
|
kind = .identifiedSender(.whisper)
|
|
case .prekeyBundle:
|
|
kind = .identifiedSender(.preKey)
|
|
case .plaintextContent:
|
|
kind = .identifiedSender(.plaintext)
|
|
case .unidentifiedSender:
|
|
kind = .unidentifiedSender
|
|
case .unknown, .none:
|
|
throw OWSGenericError("Unsupported type.")
|
|
}
|
|
self.kind = kind
|
|
|
|
let localServiceId = try Self.localServiceId(for: envelope, localIdentifiers: localIdentifiers)
|
|
self.localServiceId = localServiceId
|
|
self.localIdentity = { () -> OWSIdentity in
|
|
switch localServiceId.kind {
|
|
case .aci: return .aci
|
|
case .pni: return .pni
|
|
}
|
|
}()
|
|
try Self.validateEnvelopeKind(kind, for: localIdentity)
|
|
}
|
|
|
|
enum Kind {
|
|
case serverReceipt
|
|
case identifiedSender(CiphertextMessage.MessageType)
|
|
case unidentifiedSender
|
|
}
|
|
|
|
// MARK: - GUID
|
|
|
|
var serverGuid: UUID? {
|
|
return Self.parseServerGuid(fromEnvelope: self.envelope)
|
|
}
|
|
|
|
static func parseServerGuid(fromEnvelope envelope: SSKProtoEnvelope) -> UUID? {
|
|
if let serverGuidBinary = envelope.serverGuidBinary {
|
|
return UUID(data: serverGuidBinary)
|
|
}
|
|
if let serverGuidString = envelope.serverGuid {
|
|
return UUID(uuidString: serverGuidString)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MARK: - Source
|
|
|
|
func validateSource<T: ServiceId>(_ type: T.Type) throws -> (T, DeviceId) {
|
|
guard
|
|
let sourceServiceId = T.parseFrom(
|
|
serviceIdBinary: envelope.sourceServiceIDBinary,
|
|
serviceIdString: envelope.sourceServiceID,
|
|
)
|
|
else {
|
|
throw OWSAssertionError("Invalid source.")
|
|
}
|
|
guard envelope.hasSourceDevice, let sourceDevice = DeviceId(validating: envelope.sourceDevice) else {
|
|
throw OWSAssertionError("Invalid source device.")
|
|
}
|
|
return (sourceServiceId, sourceDevice)
|
|
}
|
|
|
|
// MARK: - Destination
|
|
|
|
private static func localServiceId(
|
|
for envelope: SSKProtoEnvelope,
|
|
localIdentifiers: LocalIdentifiers,
|
|
) throws -> ServiceId {
|
|
let destinationServiceId: ServiceId
|
|
if envelope.destinationServiceID?.nilIfEmpty != nil || envelope.hasDestinationServiceIDBinary {
|
|
destinationServiceId = try ServiceId.parseFrom(
|
|
serviceIdBinary: envelope.destinationServiceIDBinary,
|
|
serviceIdString: envelope.destinationServiceID,
|
|
) ?? { () throws -> ServiceId in throw OWSGenericError("Couldn't parse destination.") }()
|
|
} else {
|
|
// Old, locally-persisted envelopes may not have a destination specified.
|
|
destinationServiceId = localIdentifiers.aci
|
|
}
|
|
switch destinationServiceId {
|
|
case localIdentifiers.aci, localIdentifiers.pni:
|
|
return destinationServiceId
|
|
default:
|
|
throw MessageProcessingError.wrongDestinationUuid
|
|
}
|
|
}
|
|
|
|
private static func validateEnvelopeKind(_ kind: Kind, for localIdentity: OWSIdentity) throws {
|
|
let canBeReceived: Bool = {
|
|
switch kind {
|
|
case .serverReceipt:
|
|
return true
|
|
case .identifiedSender(.whisper):
|
|
return localIdentity == .aci
|
|
case .identifiedSender(.preKey):
|
|
return true
|
|
case .identifiedSender(.senderKey):
|
|
return localIdentity == .aci
|
|
case .identifiedSender(.plaintext):
|
|
return localIdentity == .aci
|
|
case .identifiedSender:
|
|
return false
|
|
case .unidentifiedSender:
|
|
return localIdentity == .aci
|
|
}
|
|
}()
|
|
guard canBeReceived else {
|
|
throw MessageProcessingError.invalidMessageTypeForDestinationUuid
|
|
}
|
|
}
|
|
}
|