diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 6e4f6dc62..0426787fc 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -785,6 +785,7 @@ FD8ECF852934508B00C0D1BB /* BatchResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF842934508B00C0D1BB /* BatchResponseSpec.swift */; }; FD8ECF8629346DA100C0D1BB /* HeaderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906127E411AF00CD579F /* HeaderSpec.swift */; }; FD8ECF8729346DB500C0D1BB /* RequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906327E4122F00CD579F /* RequestSpec.swift */; }; + FD8ECF892935AB7200C0D1BB /* SessionUtilError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */; }; FD90040F2818AB6D00ABAAF6 /* GetSnodePoolJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD90040E2818AB6D00ABAAF6 /* GetSnodePoolJob.swift */; }; FD9004122818ABDC00ABAAF6 /* Job.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B73F280402C4004C14C5 /* Job.swift */; }; FD9004142818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */; }; @@ -1905,6 +1906,7 @@ FD8ECF7E2934298100C0D1BB /* SharedConfigDump.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedConfigDump.swift; sourceTree = ""; }; FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigUserProfileSpec.swift; sourceTree = ""; }; FD8ECF842934508B00C0D1BB /* BatchResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchResponseSpec.swift; sourceTree = ""; }; + FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtilError.swift; sourceTree = ""; }; FD90040E2818AB6D00ABAAF6 /* GetSnodePoolJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetSnodePoolJob.swift; sourceTree = ""; }; FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = ""; }; FDA8EAFD280E8B78002B68E5 /* FailedMessageSendsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedMessageSendsJob.swift; sourceTree = ""; }; @@ -4027,6 +4029,7 @@ children = ( FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */, FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */, + FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */, ); path = LibSessionUtil; sourceTree = ""; @@ -5564,6 +5567,8 @@ FD245C59285065FC00B966DD /* ControlMessage.swift in Sources */, B8DE1FB626C22FCB0079C9CE /* CallMessage.swift in Sources */, FD245C50285065C700B966DD /* VisibleMessage+Quote.swift in Sources */, + FD8ECF892935AB7200C0D1BB /* SessionUtilError.swift in Sources */, + FD8ECF42292B340D00C0D1BB /* SOGSBatchRequest.swift in Sources */, FD5C7307284F103B0029977D /* MessageReceiver+MessageRequests.swift in Sources */, C3A71D0B2558989C0043A11F /* MessageWrapper.swift in Sources */, FDE77F6B280FEB28002CFC5D /* ControlMessageProcessRecord.swift in Sources */, diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index e55a5cb2f..d91851597 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -27,7 +27,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // MARK: - Lifecycle func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - SessionUtil.loadState() // TODO: Remove this (move to 'Configuration'? Or call directly as part of 'AppSetup'?) // These should be the first things we do (the startup process can fail without them) SetCurrentAppContext(MainAppContext()) verifyDBKeysAvailableBeforeBackgroundLaunch() diff --git a/SessionMessagingKit/Database/Models/SharedConfigDump.swift b/SessionMessagingKit/Database/Models/SharedConfigDump.swift index c9e8651df..c295b975f 100644 --- a/SessionMessagingKit/Database/Models/SharedConfigDump.swift +++ b/SessionMessagingKit/Database/Models/SharedConfigDump.swift @@ -13,7 +13,7 @@ internal struct ConfigDump: Codable, Equatable, Hashable, Identifiable, Fetchabl case data } - enum Variant: String, Codable, DatabaseValueConvertible { + enum Variant: String, Codable, DatabaseValueConvertible, CaseIterable { case userProfile } diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift index 94f798ab2..b60f692a3 100644 --- a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift @@ -5,74 +5,140 @@ import GRDB import SessionUtil import SessionUtilitiesKit -/*internal*/public enum SessionUtil { // TODO: Rename this to be cleaner? +/*internal*/public enum SessionUtil { + typealias ConfResult = (needsPush: Bool, needsDump: Bool) + + // MARK: - Configs + + private static var userProfileConfig: Atomic?> = Atomic(nil) + + // MARK: - Variables + + public static var needsSync: Bool { + return ConfigDump.Variant.allCases.contains { variant in + switch variant { + case .userProfile: + return (userProfileConfig.wrappedValue.map { config_needs_push($0) } ?? false) + } + } + } + + // MARK: - Loading + /*internal*/public static func loadState() { + SessionUtil.userProfileConfig.mutate { $0 = loadState(for: .userProfile) } + } + + private static func loadState(for variant: ConfigDump.Variant) -> UnsafeMutablePointer? { + // Load any let storedDump: Data? = Storage.shared - .read { db in try ConfigDump.fetchOne(db, id: .userProfile) }? + .read { db in try ConfigDump.fetchOne(db, id: variant) }? .data - var dump: UnsafePointer? = nil // TODO: Load from DB/Disk - let dumpLen: size_t = 0 - var conf: UnsafeMutablePointer? = nil -// var confSetup: UnsafeMutablePointer?>? = nil - var error: UnsafeMutablePointer? = nil - // TODO: Will need to manually release any unsafe pointers - let result = user_profile_init(&conf, dump, dumpLen, error) - - guard result == 0 else { return } // TODO: Throw error -// var conf: UnsafeMutablePointer? = confSetup?.pointee - - user_profile_set_name(conf, "TestName") // TODO: Confirm success - - let profileUrl: [CChar] = "http://example.org/omg-pic-123.bmp".bytes.map { CChar(bitPattern: $0) } - let profileKey: [CChar] = "secretNOTSECRET".bytes.map { CChar(bitPattern: $0) } - let profilePic: user_profile_pic = profileUrl.withUnsafeBufferPointer { profileUrlPtr in - profileKey.withUnsafeBufferPointer { profileKeyPtr in - user_profile_pic( - url: profileUrlPtr.baseAddress, - key: profileKeyPtr.baseAddress, - keylen: profileKey.count + return try? loadState(for: variant, cachedData: storedDump) + } + + internal static func loadState( + for variant: ConfigDump.Variant, + cachedData: Data? = nil + ) throws -> UnsafeMutablePointer? { + // Setup initial variables (including getting the memory address for any cached data) + var conf: UnsafeMutablePointer? = nil + let error: UnsafeMutablePointer? = nil + let cachedDump: (data: UnsafePointer, length: Int)? = cachedData?.withUnsafeBytes { unsafeBytes in + return unsafeBytes.baseAddress.map { + ( + $0.assumingMemoryBound(to: CChar.self), + unsafeBytes.count ) } } - user_profile_set_pic(conf, profilePic) // TODO: Confirm success - - if config_needs_push(conf) { - print("Needs Push!!!") + // No need to deallocate the `cachedDump.data` as it'll automatically be cleaned up by + // the `cachedData` lifecycle, but need to deallocate the `error` if it gets set + defer { + error?.deallocate() } - if config_needs_dump(conf) { - print("Needs Dump!!!") - } + // Try to create the object + let result: Int32 = { + switch variant { + case .userProfile: + return user_profile_init(&conf, cachedDump?.data, (cachedDump?.length ?? 0), error) + } + }() - var toPush: UnsafeMutablePointer? = nil - var pushLen: Int = 0 - let seqNo = config_push(conf, &toPush, &pushLen) + guard result == 0 else { + let errorString: String = (error.map { String(cString: $0) } ?? "unknown error") + SNLog("[SessionUtil Error] Unable to create \(variant.rawValue) config object: \(errorString)") + throw SessionUtilError.unableToCreateConfigObject + } - //var remoteAddr: [CChar] = remote.bytes.map { CChar(bitPattern: $0) } - //config_dump(conf, &dump1, &dump1len); + return conf + } + + internal static func saveState( + _ db: Database, + conf: UnsafeMutablePointer?, + for variant: ConfigDump.Variant + ) throws { + guard conf != nil else { throw SessionUtilError.nilConfigObject } - free(toPush) // TODO: Confirm + // If it doesn't need a dump then do nothing + guard config_needs_dump(conf) else { return } var dumpResult: UnsafeMutablePointer? = nil var dumpResultLen: Int = 0 - config_dump(conf, &dumpResult, &dumpResultLen) - print("RAWR") - let str = String(cString: dumpResult!) - let stryBytes = str.bytes - let hexStr = stryBytes.toHexString() - let data = Data(bytes: dumpResult!, count: dumpResultLen) -// dumpResult. -// Storage.shared.write { db in -// try ConfigDump(variant: .userProfile, data: <#T##Data#>) -// .save(db) -// } -// - print("RAWR2") + guard let dumpResult: UnsafeMutablePointer = dumpResult else { return } + + let dumpData: Data = Data(bytes: dumpResult, count: dumpResultLen) + dumpResult.deallocate() + + try ConfigDump( + variant: variant, + data: dumpData + ) + .save(db) + } + + // MARK: - UserProfile + + internal static func update( + conf: UnsafeMutablePointer?, + with profile: Profile + ) throws -> ConfResult { + guard conf != nil else { throw SessionUtilError.nilConfigObject } + + // Update the name + user_profile_set_name(conf, profile.name) + + let profilePic: user_profile_pic? = profile.profilePictureUrl? + .bytes + .map { CChar(bitPattern: $0) } + .withUnsafeBufferPointer { profileUrlPtr in + let profileKey: [CChar]? = profile.profileEncryptionKey? + .keyData + .bytes + .map { CChar(bitPattern: $0) } + + return profileKey?.withUnsafeBufferPointer { profileKeyPtr in + user_profile_pic( + url: profileUrlPtr.baseAddress, + key: profileKeyPtr.baseAddress, + keylen: (profileKey?.count ?? 0) + ) + } + } + + if let profilePic: user_profile_pic = profilePic { + user_profile_set_pic(conf, profilePic) + } - //String(cString: dumpResult!) + return ( + needsPush: config_needs_push(conf), + needsDump: config_needs_dump(conf) + ) } } diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtilError.swift b/SessionMessagingKit/LibSessionUtil/SessionUtilError.swift new file mode 100644 index 000000000..2425f7398 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/SessionUtilError.swift @@ -0,0 +1,8 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public enum SessionUtilError: Error { + case unableToCreateConfigObject + case nilConfigObject +} diff --git a/SessionUtilitiesKit/General/Collection+Utilities.swift b/SessionUtilitiesKit/General/Collection+Utilities.swift index 1c1954c26..234de4c1a 100644 --- a/SessionUtilitiesKit/General/Collection+Utilities.swift +++ b/SessionUtilitiesKit/General/Collection+Utilities.swift @@ -8,6 +8,17 @@ extension Collection { } } +public extension Collection { + /// This creates an UnsafeMutableBufferPointer to access data in memory directly. This result pointer provides no automated + /// memory management so after use you are responsible for handling the life cycle and need to call `deallocate()`. + func unsafeCopy() -> UnsafeMutableBufferPointer { + let copy = UnsafeMutableBufferPointer.allocate(capacity: self.underestimatedCount) + _ = copy.initialize(from: self) + return copy + } +} + + public extension Collection where Element == [CChar] { /// This creates an array of UnsafePointer types to access data of the C strings in memory. This array provides no automated /// memory management of it's children so after use you are responsible for handling the life cycle of the child elements and diff --git a/SignalUtilitiesKit/Utilities/AppSetup.swift b/SignalUtilitiesKit/Utilities/AppSetup.swift index cec53720e..ab33661ae 100644 --- a/SignalUtilitiesKit/Utilities/AppSetup.swift +++ b/SignalUtilitiesKit/Utilities/AppSetup.swift @@ -74,8 +74,13 @@ public enum AppSetup { ], onProgressUpdate: migrationProgressChanged, onComplete: { result, needsConfigSync in + // After the migrations have run but before the migration completion we load the + // SessionUtil state and update the 'needsConfigSync' flag based on whether the + // configs also need to be sync'ed + SessionUtil.loadState() + DispatchQueue.main.async { - migrationsCompletion(result, needsConfigSync) + migrationsCompletion(result, (needsConfigSync || SessionUtil.needsSync)) // The 'if' is only there to prevent the "variable never read" warning from showing if backgroundTask != nil { backgroundTask = nil }