| 
							
								 | 
							
							// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
 | 
						
						
						
						
							 | 
							
								 | 
							
							
 | 
						
						
						
						
							 | 
							
								 | 
							
							import Foundation
 | 
						
						
						
						
							 | 
							
								 | 
							
							import GRDB
 | 
						
						
						
						
							 | 
							
								 | 
							
							import SessionUtilitiesKit
 | 
						
						
						
						
							 | 
							
								 | 
							
							
 | 
						
						
						
						
							 | 
							
								 | 
							
							/// This lookup is created when the user interacts with a blinded id
 | 
						
						
						
						
							 | 
							
								 | 
							
							public struct BlindedIdLookup: Codable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
 | 
						
						
						
						
							 | 
							
								 | 
							
							    public static var databaseTableName: String { "blindedIdLookup" }
 | 
						
						
						
						
							 | 
							
								 | 
							
							    
 | 
						
						
						
						
							 | 
							
								 | 
							
							    public typealias Columns = CodingKeys
 | 
						
						
						
						
							 | 
							
								 | 
							
							    public enum CodingKeys: String, CodingKey, ColumnExpression {
 | 
						
						
						
						
							 | 
							
								 | 
							
							        case blindedId
 | 
						
						
						
						
							 | 
							
								 | 
							
							        case sessionId
 | 
						
						
						
						
							 | 
							
								 | 
							
							        case openGroupServer
 | 
						
						
						
						
							 | 
							
								 | 
							
							        case openGroupPublicKey
 | 
						
						
						
						
							 | 
							
								 | 
							
							    }
 | 
						
						
						
						
							 | 
							
								 | 
							
							    
 | 
						
						
						
						
							 | 
							
								 | 
							
							    public var id: String { blindedId }
 | 
						
						
						
						
							 | 
							
								 | 
							
							    
 | 
						
						
						
						
							 | 
							
								 | 
							
							    /// The blinded id for the user on this open group server
 | 
						
						
						
						
							 | 
							
								 | 
							
							    public let blindedId: String
 | 
						
						
						
						
							 | 
							
								 | 
							
							    
 | 
						
						
						
						
							 | 
							
								 | 
							
							    /// The standard sessionId which can be used to generate this blindedId on this open group server
 | 
						
						
						
						
							 | 
							
								 | 
							
							    ///
 | 
						
						
						
						
							 | 
							
								 | 
							
							    /// **Note:** This value will be null if the user owning the blinded id hasn’t accepted the message request
 | 
						
						
						
						
							 | 
							
								 | 
							
							    public let sessionId: String?
 | 
						
						
						
						
							 | 
							
								 | 
							
							    
 | 
						
						
						
						
							 | 
							
								 | 
							
							    /// The server for the Open Group server this blinded id belongs to
 | 
						
						
						
						
							 | 
							
								 | 
							
							    public let openGroupServer: String
 | 
						
						
						
						
							 | 
							
								 | 
							
							    
 | 
						
						
						
						
							 | 
							
								 | 
							
							    /// The public key for the Open Group server this blinded id belongs to
 | 
						
						
						
						
							 | 
							
								 | 
							
							    public let openGroupPublicKey: String
 | 
						
						
						
						
							 | 
							
								 | 
							
							    
 | 
						
						
						
						
							 | 
							
								 | 
							
							    // MARK: - Initialization
 | 
						
						
						
						
							 | 
							
								 | 
							
							    
 | 
						
						
						
						
							 | 
							
								 | 
							
							    public init(
 | 
						
						
						
						
							 | 
							
								 | 
							
							        blindedId: String,
 | 
						
						
						
						
							 | 
							
								 | 
							
							        sessionId: String? = nil,
 | 
						
						
						
						
							 | 
							
								 | 
							
							        openGroupServer: String,
 | 
						
						
						
						
							 | 
							
								 | 
							
							        openGroupPublicKey: String
 | 
						
						
						
						
							 | 
							
								 | 
							
							    ) {
 | 
						
						
						
						
							 | 
							
								 | 
							
							        self.blindedId = blindedId
 | 
						
						
						
						
							 | 
							
								 | 
							
							        self.sessionId = sessionId
 | 
						
						
						
						
							 | 
							
								 | 
							
							        self.openGroupServer = openGroupServer
 | 
						
						
						
						
							 | 
							
								 | 
							
							        self.openGroupPublicKey = openGroupPublicKey
 | 
						
						
						
						
							 | 
							
								 | 
							
							    }
 | 
						
						
						
						
							 | 
							
								 | 
							
							}
 | 
						
						
						
						
							 | 
							
								 | 
							
							
 | 
						
						
						
						
							 | 
							
								 | 
							
							// MARK: - Mutation
 | 
						
						
						
						
							 | 
							
								 | 
							
							
 | 
						
						
						
						
							 | 
							
								 | 
							
							public extension BlindedIdLookup {
 | 
						
						
						
						
							 | 
							
								 | 
							
							    func with(sessionId: String) -> BlindedIdLookup {
 | 
						
						
						
						
							 | 
							
								 | 
							
							        return BlindedIdLookup(
 | 
						
						
						
						
							 | 
							
								 | 
							
							            blindedId: self.blindedId,
 | 
						
						
						
						
							 | 
							
								 | 
							
							            sessionId: sessionId,
 | 
						
						
						
						
							 | 
							
								 | 
							
							            openGroupServer: self.openGroupServer,
 | 
						
						
						
						
							 | 
							
								 | 
							
							            openGroupPublicKey: self.openGroupPublicKey
 | 
						
						
						
						
							 | 
							
								 | 
							
							        )
 | 
						
						
						
						
							 | 
							
								 | 
							
							    }
 | 
						
						
						
						
							 | 
							
								 | 
							
							}
 | 
						
						
						
						
							 | 
							
								 | 
							
							
 | 
						
						
						
						
							 | 
							
								 | 
							
							// MARK: - GRDB Interactions
 | 
						
						
						
						
							 | 
							
								 | 
							
							
 | 
						
						
						
						
							 | 
							
								 | 
							
							public extension BlindedIdLookup {
 | 
						
						
						
						
							 | 
							
								 | 
							
							    /// Unfortunately the whole point of id-blinding is to make it hard to reverse-engineer a standard sessionId, as a result in order
 | 
						
						
						
						
							 | 
							
								 | 
							
							    /// to see if there is an unblinded contact for this blindedId we can only really generate blinded ids for each contact and check
 | 
						
						
						
						
							 | 
							
								 | 
							
							    /// if any match
 | 
						
						
						
						
							 | 
							
								 | 
							
							    ///
 | 
						
						
						
						
							 | 
							
								 | 
							
							    /// If we can't find a match this method will still store a lookup, just with no standard sessionId value (this gives us a method to
 | 
						
						
						
						
							 | 
							
								 | 
							
							    /// link back to the open group the blindedId originated from)
 | 
						
						
						
						
							 | 
							
								 | 
							
							    static func fetchOrCreate(
 | 
						
						
						
						
							 | 
							
								 | 
							
							        _ db: Database,
 | 
						
						
						
						
							 | 
							
								 | 
							
							        blindedId: String,
 | 
						
						
						
						
							 | 
							
								 | 
							
							        sessionId: String? = nil,
 | 
						
						
						
						
							 | 
							
								 | 
							
							        openGroupServer: String,
 | 
						
						
						
						
							 | 
							
								 | 
							
							        openGroupPublicKey: String,
 | 
						
						
						
						
							 | 
							
								 | 
							
							        isCheckingForOutbox: Bool,
 | 
						
						
						
						
							 | 
							
								 | 
							
							        using dependencies: Dependencies = Dependencies()
 | 
						
						
						
						
							 | 
							
								 | 
							
							    ) throws -> BlindedIdLookup {
 | 
						
						
						
						
							 | 
							
								 | 
							
							        var lookup: BlindedIdLookup = (try? BlindedIdLookup
 | 
						
						
						
						
							 | 
							
								 | 
							
							            .fetchOne(db, id: blindedId))
 | 
						
						
						
						
							 | 
							
								 | 
							
							            .defaulting(
 | 
						
						
						
						
							 | 
							
								 | 
							
							                to: BlindedIdLookup(
 | 
						
						
						
						
							 | 
							
								 | 
							
							                    blindedId: blindedId,
 | 
						
						
						
						
							 | 
							
								 | 
							
							                    openGroupServer: openGroupServer.lowercased(),
 | 
						
						
						
						
							 | 
							
								 | 
							
							                    openGroupPublicKey: openGroupPublicKey
 | 
						
						
						
						
							 | 
							
								 | 
							
							                )
 | 
						
						
						
						
							 | 
							
								 | 
							
							            )
 | 
						
						
						
						
							 | 
							
								 | 
							
							        
 | 
						
						
						
						
							 | 
							
								 | 
							
							        // If the lookup already has a resolved sessionId then just return it immediately
 | 
						
						
						
						
							 | 
							
								 | 
							
							        guard lookup.sessionId == nil else { return lookup }
 | 
						
						
						
						
							 | 
							
								 | 
							
							        
 | 
						
						
						
						
							 | 
							
								 | 
							
							        // If we we given a sessionId then validate it is correct and if so save it
 | 
						
						
						
						
							 | 
							
								 | 
							
							        if
 | 
						
						
						
						
							 | 
							
								 | 
							
							            let sessionId: String = sessionId,
 | 
						
						
						
						
							 | 
							
								 | 
							
							            dependencies.crypto.verify(
 | 
						
						
						
						
							 | 
							
								 | 
							
							                .sessionId(
 | 
						
						
						
						
							 | 
							
								 | 
							
							                    sessionId,
 | 
						
						
						
						
							 | 
							
								 | 
							
							                    matchesBlindedId: blindedId,
 | 
						
						
						
						
							 | 
							
								 | 
							
							                    serverPublicKey: openGroupPublicKey,
 | 
						
						
						
						
							 | 
							
								 | 
							
							                    using: dependencies
 | 
						
						
						
						
							 | 
							
								 | 
							
							                )
 | 
						
						
						
						
							 | 
							
								 | 
							
							            )
 | 
						
						
						
						
							 | 
							
								 | 
							
							        {
 | 
						
						
						
						
							 | 
							
								 | 
							
							            lookup = try lookup
 | 
						
						
						
						
							 | 
							
								 | 
							
							                .with(sessionId: sessionId)
 | 
						
						
						
						
							 | 
							
								 | 
							
							                .saved(db)
 | 
						
						
						
						
							 | 
							
								 | 
							
							            return lookup
 | 
						
						
						
						
							 | 
							
								 | 
							
							        }
 | 
						
						
						
						
							 | 
							
								 | 
							
							        
 | 
						
						
						
						
							 | 
							
								 | 
							
							        // We now need to try to match the blinded id to an existing contact, this can only be done by looping
 | 
						
						
						
						
							 | 
							
								 | 
							
							        // through all approved contacts and generating a blinded id for the provided open group for each to
 | 
						
						
						
						
							 | 
							
								 | 
							
							        // see if it matches the provided blindedId
 | 
						
						
						
						
							 | 
							
								 | 
							
							        let contactsThatApprovedMeCursor: RecordCursor<Contact> = try Contact
 | 
						
						
						
						
							 | 
							
								 | 
							
							            .filter(Contact.Columns.didApproveMe == true)
 | 
						
						
						
						
							 | 
							
								 | 
							
							            .fetchCursor(db)
 | 
						
						
						
						
							 | 
							
								 | 
							
							        
 | 
						
						
						
						
							 | 
							
								 | 
							
							        while let contact: Contact = try contactsThatApprovedMeCursor.next() {
 | 
						
						
						
						
							 | 
							
								 | 
							
							            guard
 | 
						
						
						
						
							 | 
							
								 | 
							
							                dependencies.crypto.verify(
 | 
						
						
						
						
							 | 
							
								 | 
							
							                    .sessionId(
 | 
						
						
						
						
							 | 
							
								 | 
							
							                        contact.id,
 | 
						
						
						
						
							 | 
							
								 | 
							
							                        matchesBlindedId: blindedId,
 | 
						
						
						
						
							 | 
							
								 | 
							
							                        serverPublicKey: openGroupPublicKey,
 | 
						
						
						
						
							 | 
							
								 | 
							
							                        using: dependencies
 | 
						
						
						
						
							 | 
							
								 | 
							
							                    )
 | 
						
						
						
						
							 | 
							
								 | 
							
							                )
 | 
						
						
						
						
							 | 
							
								 | 
							
							            else { continue }
 | 
						
						
						
						
							 | 
							
								 | 
							
							            
 | 
						
						
						
						
							 | 
							
								 | 
							
							            // We found a match so update the lookup and leave the loop
 | 
						
						
						
						
							 | 
							
								 | 
							
							            lookup = try lookup
 | 
						
						
						
						
							 | 
							
								 | 
							
							                .with(sessionId: contact.id)
 | 
						
						
						
						
							 | 
							
								 | 
							
							                .saved(db)
 | 
						
						
						
						
							 | 
							
								 | 
							
							            
 | 
						
						
						
						
							 | 
							
								 | 
							
							            // There is an edge-case where the contact might not have their 'isApproved' flag set to true
 | 
						
						
						
						
							 | 
							
								 | 
							
							            // but if we have a `BlindedIdLookup` for them and are performing the lookup from the outbox
 | 
						
						
						
						
							 | 
							
								 | 
							
							            // then that means we sent them a message request and the 'isApproved' flag should be true
 | 
						
						
						
						
							 | 
							
								 | 
							
							            if isCheckingForOutbox && !contact.isApproved {
 | 
						
						
						
						
							 | 
							
								 | 
							
							                try Contact
 | 
						
						
						
						
							 | 
							
								 | 
							
							                    .filter(id: contact.id)
 | 
						
						
						
						
							 | 
							
								 | 
							
							                    .updateAllAndConfig(db, Contact.Columns.isApproved.set(to: true))
 | 
						
						
						
						
							 | 
							
								 | 
							
							            }
 | 
						
						
						
						
							 | 
							
								 | 
							
							            
 | 
						
						
						
						
							 | 
							
								 | 
							
							            break
 | 
						
						
						
						
							 | 
							
								 | 
							
							        }
 | 
						
						
						
						
							 | 
							
								 | 
							
							        
 | 
						
						
						
						
							 | 
							
								 | 
							
							        // Finish if we have a result
 | 
						
						
						
						
							 | 
							
								 | 
							
							        guard lookup.sessionId == nil else { return lookup }
 | 
						
						
						
						
							 | 
							
								 | 
							
							        
 | 
						
						
						
						
							 | 
							
								 | 
							
							        // Lastly loop through existing id lookups (in case the user is looking at a different SOGS but once had
 | 
						
						
						
						
							 | 
							
								 | 
							
							        // a thread with this contact in a different SOGS and had cached the lookup) - we really should never hit
 | 
						
						
						
						
							 | 
							
								 | 
							
							        // this case since the contact approval status is sync'ed (the only situation I can think of is a config
 | 
						
						
						
						
							 | 
							
								 | 
							
							        // message hasn't been handled correctly?)
 | 
						
						
						
						
							 | 
							
								 | 
							
							        let blindedIdLookupCursor: RecordCursor<BlindedIdLookup> = try BlindedIdLookup
 | 
						
						
						
						
							 | 
							
								 | 
							
							            .filter(BlindedIdLookup.Columns.sessionId != nil)
 | 
						
						
						
						
							 | 
							
								 | 
							
							            .filter(BlindedIdLookup.Columns.openGroupServer != openGroupServer.lowercased())
 | 
						
						
						
						
							 | 
							
								 | 
							
							            .fetchCursor(db)
 | 
						
						
						
						
							 | 
							
								 | 
							
							        
 | 
						
						
						
						
							 | 
							
								 | 
							
							        while let otherLookup: BlindedIdLookup = try blindedIdLookupCursor.next() {
 | 
						
						
						
						
							 | 
							
								 | 
							
							            guard
 | 
						
						
						
						
							 | 
							
								 | 
							
							                let sessionId: String = otherLookup.sessionId,
 | 
						
						
						
						
							 | 
							
								 | 
							
							                dependencies.crypto.verify(
 | 
						
						
						
						
							 | 
							
								 | 
							
							                    .sessionId(
 | 
						
						
						
						
							 | 
							
								 | 
							
							                        sessionId,
 | 
						
						
						
						
							 | 
							
								 | 
							
							                        matchesBlindedId: blindedId,
 | 
						
						
						
						
							 | 
							
								 | 
							
							                        serverPublicKey: openGroupPublicKey,
 | 
						
						
						
						
							 | 
							
								 | 
							
							                        using: dependencies
 | 
						
						
						
						
							 | 
							
								 | 
							
							                    )
 | 
						
						
						
						
							 | 
							
								 | 
							
							                )
 | 
						
						
						
						
							 | 
							
								 | 
							
							            else { continue }
 | 
						
						
						
						
							 | 
							
								 | 
							
							            
 | 
						
						
						
						
							 | 
							
								 | 
							
							            // We found a match so update the lookup and leave the loop
 | 
						
						
						
						
							 | 
							
								 | 
							
							            lookup = try lookup
 | 
						
						
						
						
							 | 
							
								 | 
							
							                .with(sessionId: sessionId)
 | 
						
						
						
						
							 | 
							
								 | 
							
							                .saved(db)
 | 
						
						
						
						
							 | 
							
								 | 
							
							            break
 | 
						
						
						
						
							 | 
							
								 | 
							
							        }
 | 
						
						
						
						
							 | 
							
								 | 
							
							        
 | 
						
						
						
						
							 | 
							
								 | 
							
							        // Want to save the lookup even if it doesn't have a sessionId so it can be used when handling
 | 
						
						
						
						
							 | 
							
								 | 
							
							        // MessageRequestResponse messages
 | 
						
						
						
						
							 | 
							
								 | 
							
							        return try lookup
 | 
						
						
						
						
							 | 
							
								 | 
							
							            .saved(db)
 | 
						
						
						
						
							 | 
							
								 | 
							
							    }
 | 
						
						
						
						
							 | 
							
								 | 
							
							}
 |