Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/SignalServiceKit/Account/ServiceId.swift
1 views
//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import Foundation
public import GRDB
public import LibSignalClient

extension Aci {
    /// Parses an ACI from its string representation.
    ///
    /// - Note: Call this only if you **expect** an `Aci` (or nil). If the
    /// result could be a `Pni`, you shouldn't call this method.
    public static func parseFrom(aciString: String?) -> Aci? {
        guard let aciString else { return nil }
        guard let serviceId = try? ServiceId.parseFrom(serviceIdString: aciString) else { return nil }
        guard let aci = serviceId as? Aci else {
            return nil
        }
        return aci
    }
}

extension Pni {
    public static func parseFrom(pniString: String?) -> Pni? {
        guard let pniString else { return nil }
        guard let pniUuid = UUID(uuidString: pniString) else {
            return nil
        }
        return Pni(fromUUID: pniUuid)
    }

    public static func parseFrom(ambiguousString: String?) -> Pni? {
        guard let ambiguousString else { return nil }
        // Give LibSignal the first pass at parsing a "PNI:"-prefixed value.
        return (try? Pni.parseFrom(serviceIdString: ambiguousString)) ?? parseFrom(pniString: ambiguousString)
    }
}

extension ServiceId {
    public enum ConcreteType {
        case aci(Aci)
        case pni(Pni)
    }

    public var concreteType: ConcreteType {
        switch kind {
        case .aci: return .aci(self as! Aci)
        case .pni: return .pni(self as! Pni)
        }
    }
}

extension ServiceId {
    public static func parseFrom(serviceIdBinary: Data?, serviceIdString: String?) -> Self? {
        if let serviceIdBinary {
            return try? Self.parseFrom(serviceIdBinary: serviceIdBinary)
        }
        if let serviceIdString {
            return try? Self.parseFrom(serviceIdString: serviceIdString)
        }
        return nil
    }
}

extension ProtocolAddress {
    public convenience init(_ serviceId: ServiceId, deviceId: DeviceId) {
        self.init(serviceId, deviceId: deviceId.uint32Value)
    }

    public var deviceIdObj: DeviceId {
        // LibSignal also enforces [1, 127], so this can't fail.
        return DeviceId(validating: self.deviceId)!
    }
}

public struct AtLeastOneServiceId {
    /// Non-Optional because we must have at least an ACI or a PNI.
    public let aciOrElsePni: ServiceId

    public let aci: Aci?
    public let pni: Pni?

    public init?(aci: Aci?, pni: Pni?) {
        guard let aciOrElsePni = aci ?? pni else {
            return nil
        }
        self.aciOrElsePni = aciOrElsePni
        self.aci = aci
        self.pni = pni
    }
}

/// An "address" that's written to disk in a DB record.
///
/// This is the exact value that exists in the database, meaning that
///
///   `self == self.normalizedValue?.persistableValue`
///
/// may not always be true.
public struct PersistableDatabaseRecordAddress: Equatable {
    public let serviceId: ServiceId?
    public let phoneNumber: String?

    public init(serviceId: ServiceId?, phoneNumber: String?) {
        self.serviceId = serviceId
        self.phoneNumber = phoneNumber
    }
}

/// A "normalized address" that's written to various DB records.
///
/// New DB record types should generally store a foreign key to
/// `SignalRecipient`, but existing types may store a ServiceId/E164 pair.
///
/// For these older types, we often want to avoid storing phone numbers in
/// cases where we already know the ACI. This type does that.
public struct NormalizedDatabaseRecordAddress {
    public let serviceId: ServiceId?
    public let phoneNumber: String?

    public init(aci: Aci) {
        self.serviceId = aci
        self.phoneNumber = nil
    }

    private init(phoneNumber: String, pni: Pni?) {
        self.serviceId = pni
        self.phoneNumber = phoneNumber
    }

    private init(phoneNumber: String?, pni: Pni) {
        self.serviceId = pni
        self.phoneNumber = phoneNumber
    }

    public init?(aci: Aci?, phoneNumber: String?, pni: Pni?) {
        if let aci {
            self.init(aci: aci)
        } else if let pni {
            self.init(phoneNumber: phoneNumber, pni: pni)
        } else if let phoneNumber {
            self.init(phoneNumber: phoneNumber, pni: pni)
        } else {
            return nil
        }
    }

    public init?(serviceId: ServiceId?, phoneNumber: String?) {
        self.init(aci: serviceId as? Aci, phoneNumber: phoneNumber, pni: serviceId as? Pni)
    }

    public init?(serviceIdString: String?, phoneNumber: String?) {
        let serviceId = serviceIdString.flatMap { try? ServiceId.parseFrom(serviceIdString: $0) }
        self.init(serviceId: serviceId, phoneNumber: phoneNumber)
    }

    public init?(address: SignalServiceAddress?) {
        self.init(serviceId: address?.serviceId, phoneNumber: address?.phoneNumber)
    }

    public var persistableValue: PersistableDatabaseRecordAddress {
        return PersistableDatabaseRecordAddress(serviceId: serviceId, phoneNumber: phoneNumber)
    }
}

@objc
public class ServiceIdObjC: NSObject, NSCopying {
    public var wrappedValue: ServiceId { owsFail("Subclasses must implement.") }

    override fileprivate init() { super.init() }

    public static func wrapValue(_ wrappedValue: ServiceId) -> ServiceIdObjC {
        switch wrappedValue.kind {
        case .aci:
            return AciObjC(wrappedValue as! Aci)
        case .pni:
            return PniObjC(wrappedValue as! Pni)
        }
    }

    @objc
    public static func parseFrom(serviceIdString: String?) -> ServiceIdObjC? {
        guard let serviceIdString, let wrappedValue = try? ServiceId.parseFrom(serviceIdString: serviceIdString) else {
            return nil
        }
        return wrapValue(wrappedValue)
    }

    @objc
    public var serviceIdString: String { wrappedValue.serviceIdString }

    @objc
    public var serviceIdBinary: Data { wrappedValue.serviceIdBinary }

    @objc
    public var serviceIdUppercaseString: String { wrappedValue.serviceIdUppercaseString }

    @objc
    public var rawUUID: UUID { wrappedValue.rawUUID }

    @objc
    override public var hash: Int { wrappedValue.hashValue }

    @objc
    override public func isEqual(_ object: Any?) -> Bool { wrappedValue == (object as? ServiceIdObjC)?.wrappedValue }

    @objc
    public func copy(with zone: NSZone? = nil) -> Any { self }

    @objc
    override public var description: String { wrappedValue.debugDescription }
}

@objc
public final class AciObjC: ServiceIdObjC {
    public let wrappedAciValue: Aci

    override public var wrappedValue: ServiceId { wrappedAciValue }

    public init(_ wrappedValue: Aci) {
        self.wrappedAciValue = wrappedValue
    }

    @objc
    public init(uuidValue: UUID) {
        self.wrappedAciValue = Aci(fromUUID: uuidValue)
    }

    @objc
    public init?(aciString: String?) {
        guard let aciValue = Aci.parseFrom(aciString: aciString) else {
            return nil
        }
        self.wrappedAciValue = aciValue
    }
}

@objc
public final class PniObjC: ServiceIdObjC {
    public let wrappedPniValue: Pni

    override public var wrappedValue: ServiceId { wrappedPniValue }

    public init(_ wrappedValue: Pni) {
        self.wrappedPniValue = wrappedValue
    }

    @objc
    public init(uuidValue: UUID) {
        self.wrappedPniValue = Pni(fromUUID: uuidValue)
    }
}

// MARK: - Codable

@propertyWrapper
public struct AciUuid: Codable, Equatable, Hashable, DatabaseValueConvertible {
    public let wrappedValue: Aci

    public init(wrappedValue: Aci) {
        self.wrappedValue = wrappedValue
    }

    public init(from decoder: Decoder) throws {
        self.wrappedValue = Aci(fromUUID: try decoder.singleValueContainer().decode(UUID.self))
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(self.wrappedValue.rawUUID)
    }

    public var databaseValue: DatabaseValue { wrappedValue.rawUUID.databaseValue }

    public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? {
        UUID.fromDatabaseValue(dbValue).map { Self(wrappedValue: Aci(fromUUID: $0)) }
    }
}

extension Aci {
    public var codableUuid: AciUuid { .init(wrappedValue: self) }
}

@propertyWrapper
public struct PniUuid: Codable, Equatable, Hashable, DatabaseValueConvertible {
    public let wrappedValue: Pni

    public init(wrappedValue: Pni) {
        self.wrappedValue = wrappedValue
    }

    public init(from decoder: Decoder) throws {
        self.wrappedValue = Pni(fromUUID: try decoder.singleValueContainer().decode(UUID.self))
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(self.wrappedValue.rawUUID)
    }

    public var databaseValue: DatabaseValue { wrappedValue.rawUUID.databaseValue }

    public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? {
        UUID.fromDatabaseValue(dbValue).map { Self(wrappedValue: Pni(fromUUID: $0)) }
    }
}

extension Pni {
    public var codableUuid: PniUuid { .init(wrappedValue: self) }
}

@propertyWrapper
public struct ServiceIdString: Codable, Hashable {
    public let wrappedValue: ServiceId

    public init(wrappedValue: ServiceId) {
        self.wrappedValue = wrappedValue
    }

    public init(from decoder: Decoder) throws {
        self.wrappedValue = try ServiceId.parseFrom(
            serviceIdString: try decoder.singleValueContainer().decode(String.self),
        )
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(self.wrappedValue.serviceIdString)
    }
}

@propertyWrapper
public struct ServiceIdUppercaseString<T: ServiceId>: Codable, Hashable {
    public let wrappedValue: T

    public init(wrappedValue: T) {
        self.wrappedValue = wrappedValue
    }

    public init(from decoder: Decoder) throws {
        self.wrappedValue = try T.parseFrom(
            serviceIdString: try decoder.singleValueContainer().decode(String.self),
        )
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(self.wrappedValue.serviceIdUppercaseString)
    }
}

extension ServiceId {
    public var codableUppercaseString: ServiceIdUppercaseString<ServiceId> { .init(wrappedValue: self) }
}

// MARK: - Unit Tests

#if TESTABLE_BUILD

extension Aci {
    public static func randomForTesting() -> Aci {
        Aci(fromUUID: UUID())
    }

    public static func constantForTesting(_ uuidString: String) -> Aci {
        try! ServiceId.parseFrom(serviceIdString: uuidString) as! Aci
    }
}

extension Pni {
    public static func randomForTesting() -> Pni {
        Pni(fromUUID: UUID())
    }

    public static func constantForTesting(_ serviceIdString: String) -> Pni {
        try! ServiceId.parseFrom(serviceIdString: serviceIdString) as! Pni
    }
}

#endif