Started adding logic and unit tests for group creation

Reworked the config store to better support different types of config objects
Added the logic to create a group (not final just yet)
This commit is contained in:
Morgan Pretty 2023-08-31 10:35:03 +10:00
parent f44b545265
commit f1075e9123
105 changed files with 3174 additions and 1101 deletions

@ -1 +1 @@
Subproject commit 8d9ce6e30153a785b13354c99a9a210d5e8fc1a7
Subproject commit 517a61a455d31cd9363198d1b3d02f20093a5811

File diff suppressed because it is too large Load Diff

View File

@ -283,13 +283,13 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate {
// MARK: - Call Message Handling
public func updateCallMessage(mode: EndCallMode) {
public func updateCallMessage(mode: EndCallMode, using dependencies: Dependencies = Dependencies()) {
guard let callInteractionId: Int64 = callInteractionId else { return }
let duration: TimeInterval = self.duration
let hasStartedConnecting: Bool = self.hasStartedConnecting
Storage.shared.writeAsync(
dependencies.storage.writeAsync(
updates: { db in
guard let interaction: Interaction = try? Interaction.fetchOne(db, id: callInteractionId) else {
return
@ -344,7 +344,8 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate {
threadId: interaction.threadId,
threadVariant: threadVariant,
includingOlder: false,
trySendReadReceipt: false
trySendReadReceipt: false,
using: dependencies
)
},
completion: { _, _ in

View File

@ -128,7 +128,10 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
}
}
public func reportCurrentCallEnded(reason: CXCallEndedReason?) {
public func reportCurrentCallEnded(
reason: CXCallEndedReason?,
using dependencies: Dependencies = Dependencies()
) {
guard Thread.isMainThread else {
DispatchQueue.main.async {
self.reportCurrentCallEnded(reason: reason)
@ -155,14 +158,14 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
self.provider?.reportCall(with: call.callId, endedAt: nil, reason: reason)
switch (reason) {
case .answeredElsewhere: call.updateCallMessage(mode: .answeredElsewhere)
case .unanswered: call.updateCallMessage(mode: .unanswered)
case .declinedElsewhere: call.updateCallMessage(mode: .local)
default: call.updateCallMessage(mode: .remote)
case .answeredElsewhere: call.updateCallMessage(mode: .answeredElsewhere, using: dependencies)
case .unanswered: call.updateCallMessage(mode: .unanswered, using: dependencies)
case .declinedElsewhere: call.updateCallMessage(mode: .local, using: dependencies)
default: call.updateCallMessage(mode: .remote, using: dependencies)
}
}
else {
call.updateCallMessage(mode: .local)
call.updateCallMessage(mode: .local, using: dependencies)
}
call.webRTCSession.dropConnection()

View File

@ -31,7 +31,7 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate
private lazy var data: [ArraySection<Section, Profile>] = [
ArraySection(model: .contacts, elements: contactProfiles)
]
private var selectedContacts: Set<String> = []
private var selectedProfiles: [String: Profile] = [:]
private var searchText: String = ""
// MARK: - Components
@ -211,7 +211,7 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate
leftAccessory: .profile(id: profile.id, profile: profile),
title: profile.displayName(),
rightAccessory: .radio(isSelected: { [weak self] in
self?.selectedContacts.contains(profile.id) == true
(self?.selectedProfiles[profile.id] != nil)
}),
styling: SessionCell.StyleInfo(backgroundStyle: .edgeToEdge),
accessibility: Accessibility(
@ -234,13 +234,13 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let profileId: String = data[indexPath.section].elements[indexPath.row].id
let profile: Profile = data[indexPath.section].elements[indexPath.row]
if !selectedContacts.contains(profileId) {
selectedContacts.insert(profileId)
if selectedProfiles[profile.id] == nil {
selectedProfiles[profile.id] = profile
}
else {
selectedContacts.remove(profileId)
selectedProfiles.removeValue(forKey: profile.id)
}
tableView.deselectRow(at: indexPath, animated: true)
@ -323,17 +323,17 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate
guard name.utf8CString.count < SessionUtil.libSessionMaxGroupNameByteLength else {
return showError(title: "vc_create_closed_group_group_name_too_long_error".localized())
}
guard selectedContacts.count >= 1 else {
guard selectedProfiles.count >= 1 else {
return showError(title: "GROUP_ERROR_NO_MEMBER_SELECTION".localized())
}
guard selectedContacts.count < 100 else { // Minus one because we're going to include self later
guard selectedProfiles.count < 100 else { // Minus one because we're going to include self later
return showError(title: "vc_create_closed_group_too_many_group_members_error".localized())
}
let selectedContacts = self.selectedContacts
let message: String? = (selectedContacts.count > 20 ? "GROUP_CREATION_PLEASE_WAIT".localized() : nil)
ModalActivityIndicatorViewController.present(fromViewController: navigationController!, message: message) { [weak self] _ in
let selectedProfiles: [(String, Profile?)] = self.selectedProfiles
.reduce(into: []) { result, next in result.append((next.key, next.value)) }
ModalActivityIndicatorViewController.present(fromViewController: navigationController!) { [weak self] _ in
MessageSender
.createClosedGroup(name: name, members: selectedContacts)
.createLegacyClosedGroup(name: name, members: selectedProfiles.map { $0.0 }.asSet())
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
.receive(on: DispatchQueue.main)
.sinkUntilComplete(

View File

@ -530,7 +530,8 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel<ThreadD
.update(
db,
sessionId: threadId,
disappearingMessagesConfig: updatedConfig
disappearingMessagesConfig: updatedConfig,
using: dependencies
)
case .legacyGroup:
@ -538,7 +539,17 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel<ThreadD
.update(
db,
legacyGroupPublicKey: threadId,
disappearingConfig: updatedConfig
disappearingConfig: updatedConfig,
using: dependencies
)
case .group:
try SessionUtil
.update(
db,
groupIdentityPublicKey: threadId,
disappearingConfig: updatedConfig,
using: dependencies
)
default: break

View File

@ -531,14 +531,15 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
confirmStyle: .danger,
cancelStyle: .alert_text
),
onTap: { [weak self] in
onTap: {
dependencies.storage.write { db in
try SessionThread.deleteOrLeave(
db,
threadId: threadId,
threadVariant: threadVariant,
groupLeaveType: .standard,
calledFromConfigHandling: false
calledFromConfigHandling: false,
using: dependencies
)
}
}

View File

@ -111,12 +111,15 @@ public struct SessionApp {
// MARK: - Functions
public static func resetAppData(onReset: (() -> ())? = nil) {
public static func resetAppData(
onReset: (() -> ())? = nil,
using dependencies: Dependencies = Dependencies()
) {
// This _should_ be wiped out below.
Logger.error("")
DDLog.flushLog()
SessionUtil.clearMemoryState()
SessionUtil.clearMemoryState(using: dependencies)
Storage.resetAllStorage()
ProfileManager.resetProfileStorage()
Attachment.resetAttachmentStorage()

View File

@ -568,7 +568,7 @@ class NotificationActionHandler {
.eraseToAnyPublisher()
}
return Storage.shared
return dependencies.storage
.writePublisher { db in
let interaction: Interaction = try Interaction(
threadId: threadId,
@ -589,7 +589,8 @@ class NotificationActionHandler {
db,
threadId: threadId,
threadVariant: thread.variant
)
),
using: dependencies
)
return try MessageSender.preparedSendData(
@ -646,8 +647,11 @@ class NotificationActionHandler {
.eraseToAnyPublisher()
}
private func markAsRead(threadId: String) -> AnyPublisher<Void, Error> {
return Storage.shared
private func markAsRead(
threadId: String,
using dependencies: Dependencies = Dependencies()
) -> AnyPublisher<Void, Error> {
return dependencies.storage
.writePublisher { db in
guard
let threadVariant: SessionThread.Variant = try SessionThread
@ -673,7 +677,8 @@ class NotificationActionHandler {
db,
threadId: threadId,
threadVariant: threadVariant
)
),
using: dependencies
)
}
.eraseToAnyPublisher()

View File

@ -84,12 +84,12 @@ enum Onboarding {
/// If the user returns to an earlier screen during Onboarding we might need to clear out a partially created
/// account (eg. returning from the PN setting screen to the seed entry screen when linking a device)
func unregister() {
func unregister(using dependencies: Dependencies = Dependencies()) {
// Clear the in-memory state from SessionUtil
SessionUtil.clearMemoryState()
SessionUtil.clearMemoryState(using: dependencies)
// Clear any data which gets set during Onboarding
Storage.shared.write { db in
dependencies.storage.write { db in
db[.hasViewedSeed] = false
try SessionThread.deleteAll(db)
@ -104,19 +104,17 @@ enum Onboarding {
profileNameRetrievalIdentifier.mutate { $0 = nil }
profileNameRetrievalPublisher.mutate { $0 = nil }
UserDefaults.standard[.hasSyncedInitialConfiguration] = false
dependencies.standardUserDefaults[.hasSyncedInitialConfiguration] = false
}
func preregister(with seed: Data, ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair) {
func preregister(
with seed: Data,
ed25519KeyPair: KeyPair,
x25519KeyPair: KeyPair,
using dependencies: Dependencies = Dependencies()
) {
let x25519PublicKey = x25519KeyPair.hexEncodedPublicKey
// Create the initial shared util state (won't have been created on
// launch due to lack of ed25519 key)
SessionUtil.loadState(
userPublicKey: x25519PublicKey,
ed25519SecretKey: ed25519KeyPair.secretKey
)
// Store the user identity information
Storage.shared.write { db in
try Identity.store(
@ -126,6 +124,10 @@ enum Onboarding {
x25519KeyPair: x25519KeyPair
)
// Create the initial shared util state (won't have been created on
// launch due to lack of ed25519 key)
SessionUtil.loadState(db, using: dependencies)
// No need to show the seed again if the user is restoring or linking
db[.hasViewedSeed] = (self == .recover || self == .link)
@ -140,7 +142,8 @@ enum Onboarding {
db,
Contact.Columns.isTrusted.set(to: true), // Always trust the current user
Contact.Columns.isApproved.set(to: true),
Contact.Columns.didApproveMe.set(to: true)
Contact.Columns.didApproveMe.set(to: true),
using: dependencies
)
/// Create the 'Note to Self' thread (not visible by default)
@ -154,7 +157,8 @@ enum Onboarding {
.filter(id: x25519PublicKey)
.updateAllAndConfig(
db,
SessionThread.Columns.shouldBeVisible.set(to: false)
SessionThread.Columns.shouldBeVisible.set(to: false),
using: dependencies
)
}
@ -162,28 +166,29 @@ enum Onboarding {
// home screen a configuration sync is triggered (yes, the logic is a
// bit weird). This is needed so that if the user registers and
// immediately links a device, there'll be a configuration in their swarm.
UserDefaults.standard[.hasSyncedInitialConfiguration] = (self == .register)
dependencies.standardUserDefaults[.hasSyncedInitialConfiguration] = (self == .register)
// Only continue if this isn't a new account
guard self != .register else { return }
// Fetch the
Onboarding.profileNamePublisher
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
.subscribe(on: DispatchQueue.global(qos: .userInitiated), using: dependencies)
.sinkUntilComplete()
}
func completeRegistration() {
func completeRegistration(using dependencies: Dependencies = Dependencies()) {
// Set the `lastNameUpdate` to the current date, so that we don't overwrite
// what the user set in the display name step with whatever we find in their
// swarm (otherwise the user could enter a display name and have it immediately
// overwritten due to the config request running slow)
Storage.shared.write { db in
dependencies.storage.write { db in
try Profile
.filter(id: getUserHexEncodedPublicKey(db))
.updateAllAndConfig(
db,
Profile.Columns.lastNameUpdate.set(to: Date().timeIntervalSince1970)
Profile.Columns.lastNameUpdate.set(to: Date().timeIntervalSince1970),
using: dependencies
)
}
@ -192,7 +197,7 @@ enum Onboarding {
// Now that we have registered get the Snode pool (just in case) - other non-blocking
// launch jobs will automatically be run because the app activation was triggered
GetSnodePoolJob.run()
GetSnodePoolJob.run(using: dependencies)
}
}
}

View File

@ -10,12 +10,14 @@ import SessionMessagingKit
import SessionUtilitiesKit
class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.NavButton, PrivacySettingsViewModel.Section, PrivacySettingsViewModel.Item> {
private let dependencies: Dependencies
private let shouldShowCloseButton: Bool
// MARK: - Initialization
init(shouldShowCloseButton: Bool = false) {
init(shouldShowCloseButton: Bool = false, using dependencies: Dependencies = Dependencies()) {
self.shouldShowCloseButton = shouldShowCloseButton
self.dependencies = dependencies
super.init()
}
@ -111,9 +113,9 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
}
.removeDuplicates()
.handleEvents(didFail: { SNLog("[PrivacySettingsViewModel] Observation failed with error: \($0)") })
.publisher(in: Storage.shared)
.publisher(in: dependencies.storage)
.withPrevious()
.map { (previous: State?, current: State) -> [SectionModel] in
.map { [dependencies] (previous: State?, current: State) -> [SectionModel] in
return [
SectionModel(
model: .screenSecurity,
@ -146,8 +148,12 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
return
}
Storage.shared.write { db in
try db.setAndUpdateConfig(.isScreenLockEnabled, to: !db[.isScreenLockEnabled])
dependencies.storage.write { db in
try db.setAndUpdateConfig(
.isScreenLockEnabled,
to: !db[.isScreenLockEnabled],
using: dependencies
)
}
}
)
@ -168,10 +174,11 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
)
),
onTap: { [weak self] in
Storage.shared.write { db in
dependencies.storage.write { db in
try db.setAndUpdateConfig(
.checkForCommunityMessageRequests,
to: !db[.checkForCommunityMessageRequests]
to: !db[.checkForCommunityMessageRequests],
using: dependencies
)
}
}
@ -193,8 +200,12 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
)
),
onTap: {
Storage.shared.write { db in
try db.setAndUpdateConfig(.areReadReceiptsEnabled, to: !db[.areReadReceiptsEnabled])
dependencies.storage.write { db in
try db.setAndUpdateConfig(
.areReadReceiptsEnabled,
to: !db[.areReadReceiptsEnabled],
using: dependencies
)
}
}
)
@ -247,8 +258,12 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
)
),
onTap: {
Storage.shared.write { db in
try db.setAndUpdateConfig(.typingIndicatorsEnabled, to: !db[.typingIndicatorsEnabled])
dependencies.storage.write { db in
try db.setAndUpdateConfig(
.typingIndicatorsEnabled,
to: !db[.typingIndicatorsEnabled],
using: dependencies
)
}
}
)
@ -269,8 +284,12 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
)
),
onTap: {
Storage.shared.write { db in
try db.setAndUpdateConfig(.areLinkPreviewsEnabled, to: !db[.areLinkPreviewsEnabled])
dependencies.storage.write { db in
try db.setAndUpdateConfig(
.areLinkPreviewsEnabled,
to: !db[.areLinkPreviewsEnabled],
using: dependencies
)
}
}
)
@ -303,8 +322,12 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
onConfirm: { _ in Permissions.requestMicrophonePermissionIfNeeded() }
),
onTap: {
Storage.shared.write { db in
try db.setAndUpdateConfig(.areCallsEnabled, to: !db[.areCallsEnabled])
dependencies.storage.write { db in
try db.setAndUpdateConfig(
.areCallsEnabled,
to: !db[.areCallsEnabled],
using: dependencies
)
}
}
)

View File

@ -200,7 +200,8 @@ enum MockDataGenerator {
_ = try! ClosedGroup(
threadId: randomGroupPublicKey,
name: groupName,
formationTimestamp: timestampNow
formationTimestamp: timestampNow,
approved: true
)
.saved(db)

View File

@ -2,11 +2,12 @@
import Foundation
import CallKit
import SessionUtilitiesKit
public protocol CallManagerProtocol {
var currentCall: CurrentCallProtocol? { get set }
func reportCurrentCallEnded(reason: CXCallEndedReason?)
func reportCurrentCallEnded(reason: CXCallEndedReason?, using dependencies: Dependencies)
func showCallUIForCall(caller: String, uuid: String, mode: CallMode, interactionId: Int64?)
func handleAnswerMessage(_ message: CallMessage)

View File

@ -3,6 +3,7 @@
import Foundation
import GRDB
import WebRTC
import SessionUtilitiesKit
public protocol CurrentCallProtocol {
var uuid: String { get }
@ -11,7 +12,7 @@ public protocol CurrentCallProtocol {
var hasStartedConnecting: Bool { get set }
var hasEnded: Bool { get set }
func updateCallMessage(mode: EndCallMode)
func updateCallMessage(mode: EndCallMode, using dependencies: Dependencies)
func didReceiveRemoteSDP(sdp: RTCSessionDescription)
func startSessionCall(_ db: Database)
}

View File

@ -33,7 +33,8 @@ public enum SNMessagingKit: MigratableTarget { // Just to make the external API
_013_SessionUtilChanges.self,
_014_GenerateInitialUserConfigDumps.self,
_015_BlockCommunityMessageRequests.self,
_016_DisappearingMessagesConfiguration.self
_016_DisappearingMessagesConfiguration.self,
_017_GroupsRebuildChanges.self
]
]
)

View File

@ -16,7 +16,7 @@ enum _001_InitialSetupMigration: Migration {
return .porter(wrapping: .unicode61())
}()
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
try db.create(table: Contact.self) { t in
t.column(.id, .text)
.notNull()

View File

@ -13,7 +13,7 @@ enum _002_SetupStandardJobs: Migration {
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.1
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
// Start by adding the jobs that don't have collections (in the jobs like these
// will be added via migrations)
try autoreleasepool {

View File

@ -12,7 +12,7 @@ enum _003_YDBToGRDBMigration: Migration {
static let needsConfigSync: Bool = true
static let minExpectedRunDuration: TimeInterval = 0.1
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
guard !SNUtilitiesKit.isRunningTests else { return Storage.update(progress: 1, for: self, in: target) }
SNLogNotTests("[Migration Error] Attempted to perform legacy migation")

View File

@ -12,7 +12,7 @@ enum _004_RemoveLegacyYDB: Migration {
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.1
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
Storage.update(progress: 1, for: self, in: target) // In case this is the last migration
}
}

View File

@ -11,7 +11,7 @@ enum _005_FixDeletedMessageReadState: Migration {
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.01
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
_ = try Interaction
.filter(
Interaction.Columns.variant == Interaction.Variant.standardIncomingDeleted ||

View File

@ -12,7 +12,7 @@ enum _006_FixHiddenModAdminSupport: Migration {
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.01
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
try db.alter(table: GroupMember.self) { t in
t.add(.isHidden, .boolean)
.notNull()

View File

@ -11,7 +11,7 @@ enum _007_HomeQueryOptimisationIndexes: Migration {
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.01
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
try db.create(
index: "interaction_on_wasRead_and_hasMention_and_threadId",
on: Interaction.databaseTableName,

View File

@ -11,7 +11,7 @@ enum _008_EmojiReacts: Migration {
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.01
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
try db.create(table: Reaction.self) { t in
t.column(.interactionId, .numeric)
.notNull()

View File

@ -10,7 +10,7 @@ enum _009_OpenGroupPermission: Migration {
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.01
static func migrate(_ db: GRDB.Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
try db.alter(table: OpenGroup.self) { t in
t.add(.permissions, .integer)
.defaults(to: OpenGroup.Permissions.all)

View File

@ -12,7 +12,7 @@ enum _010_AddThreadIdToFTS: Migration {
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 3
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
// Can't actually alter a virtual table in SQLite so we need to drop and recreate it,
// luckily this is actually pretty quick
if try db.tableExists(Interaction.fullTextSearchTableName) {

View File

@ -12,7 +12,7 @@ enum _011_AddPendingReadReceipts: Migration {
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.01
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
try db.create(table: PendingReadReceipt.self) { t in
t.column(.threadId, .text)
.notNull()

View File

@ -11,7 +11,7 @@ enum _012_AddFTSIfNeeded: Migration {
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.01
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
// Fix an issue that the fullTextSearchTable was dropped unintentionally and global search won't work.
// This issue only happens to internal test users.
if try db.tableExists(Interaction.fullTextSearchTableName) == false {

View File

@ -13,7 +13,7 @@ enum _013_SessionUtilChanges: Migration {
static let needsConfigSync: Bool = true
static let minExpectedRunDuration: TimeInterval = 0.4
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
// Add `markedAsUnread` to the thread table
try db.alter(table: SessionThread.self) { t in
t.add(.markedAsUnread, .boolean)
@ -187,7 +187,7 @@ enum _013_SessionUtilChanges: Migration {
)
// If we don't have an ed25519 key then no need to create cached dump data
let userPublicKey: String = getUserHexEncodedPublicKey(db)
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
/// Remove any hidden threads to avoid syncing them (they are basically shadow threads created by starting a conversation
/// but not sending a message so can just be cleared out)

View File

@ -12,7 +12,7 @@ enum _014_GenerateInitialUserConfigDumps: Migration {
static let needsConfigSync: Bool = true
static let minExpectedRunDuration: TimeInterval = 4.0
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
// If we have no ed25519 key then there is no need to create cached dump data
guard Identity.fetchUserEd25519KeyPair(db) != nil else {
Storage.update(progress: 1, for: self, in: target) // In case this is the last migration
@ -23,7 +23,7 @@ enum _014_GenerateInitialUserConfigDumps: Migration {
let userPublicKey: String = getUserHexEncodedPublicKey(db)
let timestampMs: Int64 = Int64(Date().timeIntervalSince1970 * 1000)
SessionUtil.loadState(db)
SessionUtil.loadState(db, using: dependencies)
// Retrieve all threads (we are going to base the config dump data on the active
// threads rather than anything else in the database)
@ -33,12 +33,12 @@ enum _014_GenerateInitialUserConfigDumps: Migration {
// MARK: - UserProfile Config Dump
try SessionUtil
try dependencies.caches[.sessionUtil]
.config(for: .userProfile, publicKey: userPublicKey)
.mutate { conf in
.mutate { config in
try SessionUtil.update(
profile: Profile.fetchOrCreateCurrentUser(db),
in: conf
in: config
)
try SessionUtil.updateNoteToSelf(
@ -47,13 +47,13 @@ enum _014_GenerateInitialUserConfigDumps: Migration {
return Int32(allThreads[userPublicKey]?.pinnedPriority ?? 0)
}(),
in: conf
in: config
)
if config_needs_dump(conf) {
if config.needsDump {
try SessionUtil
.createDump(
conf: conf,
config: config,
for: .userProfile,
publicKey: userPublicKey,
timestampMs: timestampMs
@ -64,9 +64,9 @@ enum _014_GenerateInitialUserConfigDumps: Migration {
// MARK: - Contact Config Dump
try SessionUtil
try dependencies.caches[.sessionUtil]
.config(for: .contacts, publicKey: userPublicKey)
.mutate { conf in
.mutate { config in
// Exclude Note to Self, community, group and outgoing blinded message requests
let validContactIds: [String] = allThreads
.values
@ -113,13 +113,13 @@ enum _014_GenerateInitialUserConfigDumps: Migration {
created: allThreads[data.contact.id]?.creationDateTimestamp
)
},
in: conf
in: config
)
if config_needs_dump(conf) {
if config.needsDump {
try SessionUtil
.createDump(
conf: conf,
config: config,
for: .contacts,
publicKey: userPublicKey,
timestampMs: timestampMs
@ -130,21 +130,21 @@ enum _014_GenerateInitialUserConfigDumps: Migration {
// MARK: - ConvoInfoVolatile Config Dump
try SessionUtil
try dependencies.caches[.sessionUtil]
.config(for: .convoInfoVolatile, publicKey: userPublicKey)
.mutate { conf in
.mutate { config in
let volatileThreadInfo: [SessionUtil.VolatileThreadInfo] = SessionUtil.VolatileThreadInfo
.fetchAll(db, ids: Array(allThreads.keys))
try SessionUtil.upsert(
convoInfoVolatileChanges: volatileThreadInfo,
in: conf
in: config
)
if config_needs_dump(conf) {
if config.needsDump {
try SessionUtil
.createDump(
conf: conf,
config: config,
for: .convoInfoVolatile,
publicKey: userPublicKey,
timestampMs: timestampMs
@ -155,16 +155,16 @@ enum _014_GenerateInitialUserConfigDumps: Migration {
// MARK: - UserGroups Config Dump
try SessionUtil
try dependencies.caches[.sessionUtil]
.config(for: .userGroups, publicKey: userPublicKey)
.mutate { conf in
.mutate { config in
let legacyGroupData: [SessionUtil.LegacyGroupInfo] = try SessionUtil.LegacyGroupInfo.fetchAll(db)
let communityData: [SessionUtil.OpenGroupUrlInfo] = try SessionUtil.OpenGroupUrlInfo
.fetchAll(db, ids: Array(allThreads.keys))
try SessionUtil.upsert(
legacyGroups: legacyGroupData,
in: conf
in: config
)
try SessionUtil.upsert(
communities: communityData
@ -174,13 +174,13 @@ enum _014_GenerateInitialUserConfigDumps: Migration {
priority: Int32(allThreads[urlInfo.threadId]?.pinnedPriority ?? 0)
)
},
in: conf
in: config
)
if config_needs_dump(conf) {
if config.needsDump {
try SessionUtil
.createDump(
conf: conf,
config: config,
for: .userGroups,
publicKey: userPublicKey,
timestampMs: timestampMs
@ -191,7 +191,7 @@ enum _014_GenerateInitialUserConfigDumps: Migration {
// MARK: - Threads
try SessionUtil.updatingThreads(db, Array(allThreads.values))
try SessionUtil.updatingThreads(db, Array(allThreads.values), using: dependencies)
// MARK: - Syncing

View File

@ -12,7 +12,7 @@ enum _015_BlockCommunityMessageRequests: Migration {
static let minExpectedRunDuration: TimeInterval = 0.01
static var requirements: [MigrationRequirement] = [.sessionUtilStateLoaded]
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
// Add the new 'Profile' properties
try db.alter(table: Profile.self) { t in
t.add(.blocksCommunityMessageRequests, .boolean)
@ -26,10 +26,10 @@ enum _015_BlockCommunityMessageRequests: Migration {
Identity.userExists(db),
(try Setting.exists(db, id: Setting.BoolKey.checkForCommunityMessageRequests.rawValue)) == false
{
let rawBlindedMessageRequestValue: Int32 = try SessionUtil
let rawBlindedMessageRequestValue: Int32 = try dependencies.caches[.sessionUtil]
.config(for: .userProfile, publicKey: getUserHexEncodedPublicKey(db))
.wrappedValue
.map { conf -> Int32 in try SessionUtil.rawBlindedMessageRequestValue(in: conf) }
.map { config -> Int32 in try SessionUtil.rawBlindedMessageRequestValue(in: config) }
.defaulting(to: -1)
// Use the value in the config if we happen to have one, otherwise use the default

View File

@ -11,7 +11,7 @@ enum _016_DisappearingMessagesConfiguration: Migration {
static let minExpectedRunDuration: TimeInterval = 0.1
static var requirements: [MigrationRequirement] = [.sessionUtilStateLoaded]
static func migrate(_ db: GRDB.Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
try db.alter(table: DisappearingMessagesConfiguration.self) { t in
t.add(.type, .integer)
t.add(.lastChangeTimestampMs, .integer)
@ -75,8 +75,8 @@ enum _016_DisappearingMessagesConfiguration: Migration {
}
// Update the configs so the settings are synced
_ = try SessionUtil.updatingDisappearingConfigsOneToOne(db, contactUpdate)
_ = try SessionUtil.batchUpdate(db, disappearingConfigs: legacyGroupUpdate)
_ = try SessionUtil.updatingDisappearingConfigsOneToOne(db, contactUpdate, using: dependencies)
_ = try SessionUtil.batchUpdate(db, disappearingConfigs: legacyGroupUpdate, using: dependencies)
Storage.update(progress: 1, for: self, in: target) // In case this is the last migration
}

View File

@ -11,7 +11,7 @@ enum _017_GroupsRebuildChanges: Migration {
static let minExpectedRunDuration: TimeInterval = 0.1
static var requirements: [MigrationRequirement] = [.sessionUtilStateLoaded]
static func migrate(_ db: GRDB.Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
try db.alter(table: ClosedGroup.self) { t in
t.add(.displayPictureUrl, .text)
t.add(.displayPictureFilename, .text)

View File

@ -259,14 +259,16 @@ public extension ClosedGroup {
db,
legacyGroupIds: threadVariants
.filter { $0.variant == .legacyGroup }
.map { $0.id }
.map { $0.id },
using: dependencies
)
try SessionUtil.remove(
db,
groupIds: threadVariants
.filter { $0.variant == .group }
.map { $0.id }
.map { $0.id },
using: dependencies
)
}
}

View File

@ -520,7 +520,8 @@ public extension Interaction {
threadId: String,
threadVariant: SessionThread.Variant,
includingOlder: Bool,
trySendReadReceipt: Bool
trySendReadReceipt: Bool,
using dependencies: Dependencies
) throws {
guard let interactionId: Int64 = interactionId else { return }
@ -537,7 +538,8 @@ public extension Interaction {
threadId: String,
threadVariant: SessionThread.Variant,
interactionInfo: [InteractionReadInfo],
lastReadTimestampMs: Int64
lastReadTimestampMs: Int64,
using dependencies: Dependencies
) throws {
// Add the 'DisappearingMessagesJob' if needed - this will update any expiring
// messages `expiresStartedAtMs` values in local database, and create seperate
@ -549,7 +551,8 @@ public extension Interaction {
interactionIds: interactionInfo.map { $0.id },
startedAtMs: TimeInterval(SnodeAPI.currentOffsetTimestampMs()),
threadId: threadId
)
),
using: dependencies
)
// Update the last read timestamp if needed
@ -557,7 +560,8 @@ public extension Interaction {
db,
threadId: threadId,
threadVariant: threadVariant,
lastReadTimestampMs: lastReadTimestampMs
lastReadTimestampMs: lastReadTimestampMs,
using: dependencies
)
// Clear out any notifications for the interactions we mark as read
@ -588,7 +592,8 @@ public extension Interaction {
interactionIds: interactionInfo
.filter { !$0.wasRead && $0.variant != .standardOutgoing }
.map { $0.id }
)
),
using: dependencies
)
}
}
@ -631,7 +636,8 @@ public extension Interaction {
wasRead: false
)
],
lastReadTimestampMs: timestampMs
lastReadTimestampMs: timestampMs,
using: dependencies
)
return
}
@ -654,7 +660,8 @@ public extension Interaction {
threadId: threadId,
threadVariant: threadVariant,
interactionInfo: [interactionInfo],
lastReadTimestampMs: interactionInfo.timestampMs
lastReadTimestampMs: interactionInfo.timestampMs,
using: dependencies
)
return
}
@ -668,7 +675,8 @@ public extension Interaction {
threadId: threadId,
threadVariant: threadVariant,
interactionInfo: interactionInfoToMarkAsRead,
lastReadTimestampMs: interactionInfo.timestampMs
lastReadTimestampMs: interactionInfo.timestampMs,
using: dependencies
)
}

View File

@ -271,12 +271,15 @@ public extension Profile {
///
/// **Note:** This method intentionally does **not** save the newly created Profile,
/// it will need to be explicitly saved after calling
static func fetchOrCreateCurrentUser(_ db: Database? = nil, using dependencies: Dependencies = Dependencies()) -> Profile {
let userPublicKey: String = getUserHexEncodedPublicKey(db)
static func fetchOrCreateCurrentUser(
_ db: Database? = nil,
using dependencies: Dependencies = Dependencies()
) -> Profile {
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
guard let db: Database = db else {
return dependencies.storage
.read { db in fetchOrCreateCurrentUser(db) }
.read { db in fetchOrCreateCurrentUser(db, using: dependencies) }
.defaulting(to: defaultFor(userPublicKey))
}

View File

@ -115,7 +115,7 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord,
public init(
id: String,
variant: Variant,
creationDateTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000),
creationDateTimestamp: TimeInterval? = nil,
shouldBeVisible: Bool = false,
isPinned: Bool = false,
messageDraft: String? = nil,
@ -123,11 +123,15 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord,
mutedUntilTimestamp: TimeInterval? = nil,
onlyNotifyForMentions: Bool = false,
markedAsUnread: Bool? = false,
pinnedPriority: Int32? = nil
pinnedPriority: Int32? = nil,
using dependencies: Dependencies = Dependencies()
) {
self.id = id
self.variant = variant
self.creationDateTimestamp = creationDateTimestamp
self.creationDateTimestamp = (
creationDateTimestamp ??
(TimeInterval(SnodeAPI.currentOffsetTimestampMs(using: dependencies)) / 1000)
)
self.shouldBeVisible = shouldBeVisible
self.messageDraft = messageDraft
self.notificationSound = notificationSound
@ -158,13 +162,15 @@ public extension SessionThread {
_ db: Database,
id: ID,
variant: Variant,
shouldBeVisible: Bool?
shouldBeVisible: Bool?,
using dependencies: Dependencies = Dependencies()
) throws -> SessionThread {
guard let existingThread: SessionThread = try? fetchOne(db, id: id) else {
return try SessionThread(
id: id,
variant: variant,
shouldBeVisible: (shouldBeVisible ?? false)
shouldBeVisible: (shouldBeVisible ?? false),
using: dependencies
).saved(db)
}
@ -179,7 +185,8 @@ public extension SessionThread {
.filter(id: id)
.updateAllAndConfig(
db,
SessionThread.Columns.shouldBeVisible.set(to: shouldBeVisible)
SessionThread.Columns.shouldBeVisible.set(to: shouldBeVisible),
using: dependencies
)
// Retrieve the updated thread and return it (we don't recursively call this method
@ -187,8 +194,12 @@ public extension SessionThread {
// would result in an infinite loop)
return (try fetchOne(db, id: id))
.defaulting(
to: try SessionThread(id: id, variant: variant, shouldBeVisible: desiredVisibility)
.saved(db)
to: try SessionThread(
id: id,
variant: variant,
shouldBeVisible: desiredVisibility,
using: dependencies
).saved(db)
)
}
@ -277,14 +288,16 @@ public extension SessionThread {
threadId: String,
threadVariant: Variant,
groupLeaveType: ClosedGroup.LeaveType,
calledFromConfigHandling: Bool
calledFromConfigHandling: Bool,
using dependencies: Dependencies = Dependencies()
) throws {
try deleteOrLeave(
db,
threadIds: [threadId],
threadVariant: threadVariant,
groupLeaveType: groupLeaveType,
calledFromConfigHandling: calledFromConfigHandling
calledFromConfigHandling: calledFromConfigHandling,
using: dependencies
)
}
@ -293,9 +306,10 @@ public extension SessionThread {
threadIds: [String],
threadVariant: Variant,
groupLeaveType: ClosedGroup.LeaveType,
calledFromConfigHandling: Bool
calledFromConfigHandling: Bool,
using dependencies: Dependencies = Dependencies()
) throws {
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db)
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
let remainingThreadIds: [String] = threadIds.filter { $0 != currentUserPublicKey }
switch (threadVariant, groupLeaveType) {
@ -312,7 +326,8 @@ public extension SessionThread {
.updateAllAndConfig(
db,
SessionThread.Columns.pinnedPriority.set(to: 0),
SessionThread.Columns.shouldBeVisible.set(to: false)
SessionThread.Columns.shouldBeVisible.set(to: false),
using: dependencies
)
return
}
@ -320,7 +335,7 @@ public extension SessionThread {
// If this wasn't called from config handling then we need to hide the conversation
if !calledFromConfigHandling {
try SessionUtil
.hide(db, contactIds: threadIds)
.hide(db, contactIds: threadIds, using: dependencies)
}
_ = try SessionThread
@ -333,7 +348,8 @@ public extension SessionThread {
.leave(
db,
groupPublicKey: threadId,
deleteThread: true
deleteThread: true,
using: dependencies
)
}
@ -342,7 +358,8 @@ public extension SessionThread {
db,
threadIds: threadIds,
removeGroupData: true,
calledFromConfigHandling: calledFromConfigHandling
calledFromConfigHandling: calledFromConfigHandling,
using: dependencies
)
case (.community, _):
@ -350,7 +367,8 @@ public extension SessionThread {
OpenGroupManager.shared.delete(
db,
openGroupId: threadId,
calledFromConfigHandling: calledFromConfigHandling
calledFromConfigHandling: calledFromConfigHandling,
using: dependencies
)
}
}

View File

@ -61,7 +61,8 @@ public enum ConfigMessageReceiveJob: JobExecutor {
try SessionUtil.handleConfigMessages(
db,
messages: sharedConfigMessages,
publicKey: (job.threadId ?? "")
publicKey: (job.threadId ?? ""),
using: dependencies
)
}
catch { lastError = error }

View File

@ -21,7 +21,9 @@ public enum ConfigurationSyncJob: JobExecutor {
deferred: @escaping (Job, Dependencies) -> (),
using dependencies: Dependencies
) {
guard Identity.userCompletedRequiredOnboarding() else { return success(job, true, dependencies) }
guard Identity.userCompletedRequiredOnboarding(using: dependencies) else {
return success(job, true, dependencies)
}
// It's possible for multiple ConfigSyncJob's with the same target (user/group) to try to run at the
// same time since as soon as one is started we will enqueue a second one, rather than adding dependencies
@ -55,7 +57,9 @@ public enum ConfigurationSyncJob: JobExecutor {
guard
let publicKey: String = job.threadId,
let pendingConfigChanges: [SessionUtil.OutgoingConfResult] = dependencies.storage
.read(using: dependencies, { db in try SessionUtil.pendingChanges(db, publicKey: publicKey) })
.read(using: dependencies, { db in
try SessionUtil.pendingChanges(db, publicKey: publicKey, using: dependencies)
})
else {
SNLog("[ConfigurationSyncJob] For \(job.threadId ?? "UnknownId") failed due to invalid data")
return failure(job, StorageError.generic, false, dependencies)
@ -129,7 +133,8 @@ public enum ConfigurationSyncJob: JobExecutor {
return SessionUtil.markingAsPushed(
message: change.message,
serverHash: sendMessageResponse.hash,
publicKey: publicKey
publicKey: publicKey,
using: dependencies
)
}
}

View File

@ -219,7 +219,8 @@ public final class OpenGroupManager {
.updateAllAndConfig(
db,
OpenGroup.Columns.isActive.set(to: true),
OpenGroup.Columns.sequenceNumber.set(to: 0)
OpenGroup.Columns.sequenceNumber.set(to: 0),
using: dependencies
)
}
@ -269,7 +270,8 @@ public final class OpenGroupManager {
db,
server: server,
rootToken: roomToken,
publicKey: publicKey
publicKey: publicKey,
using: dependencies
)
}
@ -369,7 +371,7 @@ public final class OpenGroupManager {
.deleteAll(db)
if !calledFromConfigHandling, let server: String = server, let roomToken: String = roomToken {
try? SessionUtil.remove(db, server: server, roomToken: roomToken)
try? SessionUtil.remove(db, server: server, roomToken: roomToken, using: dependencies)
}
}

View File

@ -11,7 +11,8 @@ extension MessageReceiver {
_ db: Database,
threadId: String,
threadVariant: SessionThread.Variant,
message: CallMessage
message: CallMessage,
using dependencies: Dependencies = Dependencies()
) throws {
// Only support calls from contact threads
guard threadVariant == .contact else { return }
@ -19,7 +20,7 @@ extension MessageReceiver {
switch message.kind {
case .preOffer: try MessageReceiver.handleNewCallMessage(db, message: message)
case .offer: MessageReceiver.handleOfferCallMessage(db, message: message)
case .answer: MessageReceiver.handleAnswerCallMessage(db, message: message)
case .answer: MessageReceiver.handleAnswerCallMessage(db, message: message, using: dependencies)
case .provisionalAnswer: break // TODO: Implement
case let .iceCandidates(sdpMLineIndexes, sdpMids):
@ -37,7 +38,7 @@ extension MessageReceiver {
}
currentWebRTCSession.handleICECandidates(candidates)
case .endCall: MessageReceiver.handleEndCallMessage(db, message: message)
case .endCall: MessageReceiver.handleEndCallMessage(db, message: message, using: dependencies)
}
}
@ -145,7 +146,11 @@ extension MessageReceiver {
currentCall.didReceiveRemoteSDP(sdp: sdpDescription)
}
private static func handleAnswerCallMessage(_ db: Database, message: CallMessage) {
private static func handleAnswerCallMessage(
_ db: Database,
message: CallMessage,
using dependencies: Dependencies
) {
SNLog("[Calls] Received answer message.")
guard
@ -161,7 +166,7 @@ extension MessageReceiver {
guard !currentCall.hasStartedConnecting else { return }
callManager.dismissAllCallUI()
callManager.reportCurrentCallEnded(reason: .answeredElsewhere)
callManager.reportCurrentCallEnded(reason: .answeredElsewhere, using: dependencies)
return
}
guard let sdp: String = message.sdps.first else { return }
@ -172,7 +177,11 @@ extension MessageReceiver {
callManager.handleAnswerMessage(message)
}
private static func handleEndCallMessage(_ db: Database, message: CallMessage) {
private static func handleEndCallMessage(
_ db: Database,
message: CallMessage,
using dependencies: Dependencies
) {
SNLog("[Calls] Received end call message.")
guard
@ -188,7 +197,8 @@ extension MessageReceiver {
reason: (sender == getUserHexEncodedPublicKey(db) ?
.declinedElsewhere :
.remoteEnded
)
),
using: dependencies
)
}
@ -227,7 +237,8 @@ extension MessageReceiver {
threadVariant: thread.variant,
timestampMs: (messageSentTimestamp * 1000),
userPublicKey: getUserHexEncodedPublicKey(db),
openGroup: nil
openGroup: nil,
using: dependencies
)
)
.inserted(db)
@ -258,7 +269,8 @@ extension MessageReceiver {
@discardableResult public static func insertCallInfoMessage(
_ db: Database,
for message: CallMessage,
state: CallMessage.MessageInfo.State? = nil
state: CallMessage.MessageInfo.State? = nil,
using dependencies: Dependencies = Dependencies()
) throws -> Interaction? {
guard
(try? Interaction
@ -300,7 +312,8 @@ extension MessageReceiver {
threadVariant: thread.variant,
timestampMs: (timestampMs * 1000),
userPublicKey: currentUserPublicKey,
openGroup: nil
openGroup: nil,
using: dependencies
)
).inserted(db)
}

View File

@ -10,7 +10,8 @@ extension MessageReceiver {
_ db: Database,
threadId: String,
threadVariant: SessionThread.Variant,
message: DataExtractionNotification
message: DataExtractionNotification,
using dependencies: Dependencies
) throws {
guard
threadVariant == .contact,
@ -38,7 +39,8 @@ extension MessageReceiver {
threadVariant: threadVariant,
timestampMs: (timestampMs * 1000),
userPublicKey: getUserHexEncodedPublicKey(db),
openGroup: nil
openGroup: nil,
using: dependencies
)
).inserted(db)
}

View File

@ -9,7 +9,8 @@ extension MessageReceiver {
_ db: Database,
threadId: String,
threadVariant: SessionThread.Variant,
message: ExpirationTimerUpdate
message: ExpirationTimerUpdate,
using dependencies: Dependencies
) throws {
guard !Features.useNewDisappearingMessagesConfig else { return }
guard
@ -85,7 +86,8 @@ extension MessageReceiver {
.update(
db,
sessionId: threadId,
disappearingMessagesConfig: remoteConfig
disappearingMessagesConfig: remoteConfig,
using: dependencies
)
case .legacyGroup:
@ -93,7 +95,8 @@ extension MessageReceiver {
.update(
db,
legacyGroupPublicKey: threadId,
disappearingConfig: remoteConfig
disappearingConfig: remoteConfig,
using: dependencies
)
default: break
@ -133,7 +136,8 @@ extension MessageReceiver {
threadVariant: threadVariant,
timestampMs: (timestampMs * 1000),
userPublicKey: currentUserPublicKey,
openGroup: nil
openGroup: nil,
using: dependencies
),
expiresInSeconds: (remoteConfig.isEnabled ? nil : localConfig.durationSeconds)
).inserted(db)
@ -144,7 +148,8 @@ extension MessageReceiver {
threadId: String,
threadVariant: SessionThread.Variant,
message: Message,
proto: SNProtoContent
proto: SNProtoContent,
using dependencies: Dependencies
) throws {
guard let sender: String = message.sender else { return }
@ -157,7 +162,8 @@ extension MessageReceiver {
.filter(id: sender)
.updateAllAndConfig(
db,
Contact.Columns.lastKnownClientVersion.set(to: lastKnownClientVersion)
Contact.Columns.lastKnownClientVersion.set(to: lastKnownClientVersion),
using: dependencies
)
guard
@ -230,7 +236,8 @@ extension MessageReceiver {
.update(
db,
sessionId: threadId,
disappearingMessagesConfig: remoteConfig
disappearingMessagesConfig: remoteConfig,
using: dependencies
)
case .legacyGroup:
@ -238,11 +245,18 @@ extension MessageReceiver {
.update(
db,
legacyGroupPublicKey: threadId,
disappearingConfig: remoteConfig
disappearingConfig: remoteConfig,
using: dependencies
)
case .group:
preconditionFailure()
try SessionUtil
.update(
db,
groupIdentityPublicKey: threadId,
disappearingConfig: remoteConfig,
using: dependencies
)
default: break
}

View File

@ -23,7 +23,8 @@ extension MessageReceiver {
db,
threadId: threadId,
threadVariant: threadVariant,
message: message
message: message,
using: dependencies
)
case .nameChange:
@ -31,7 +32,8 @@ extension MessageReceiver {
db,
threadId: threadId,
threadVariant: threadVariant,
message: message
message: message,
using: dependencies
)
case .membersAdded:
@ -39,7 +41,8 @@ extension MessageReceiver {
db,
threadId: threadId,
threadVariant: threadVariant,
message: message
message: message,
using: dependencies
)
case .membersRemoved:
@ -47,7 +50,8 @@ extension MessageReceiver {
db,
threadId: threadId,
threadVariant: threadVariant,
message: message
message: message,
using: dependencies
)
case .memberLeft:
@ -55,7 +59,8 @@ extension MessageReceiver {
db,
threadId: threadId,
threadVariant: threadVariant,
message: message
message: message,
using: dependencies
)
case .encryptionKeyPairRequest: break // Currently not used
@ -225,7 +230,9 @@ extension MessageReceiver {
latestKeyPairReceivedTimestamp: receivedTimestamp,
disappearingConfig: disappearingConfig,
members: members.asSet(),
admins: admins.asSet()
admins: admins.asSet(),
formationTimestamp: TimeInterval(formationTimestampMs * 1000),
using: dependencies
)
}
@ -260,7 +267,8 @@ extension MessageReceiver {
_ db: Database,
threadId: String,
threadVariant: SessionThread.Variant,
message: ClosedGroupControlMessage
message: ClosedGroupControlMessage,
using dependencies: Dependencies
) throws {
guard case let .encryptionKeyPair(explicitGroupPublicKey, wrappers) = message.kind else {
return
@ -319,7 +327,8 @@ extension MessageReceiver {
try? SessionUtil.update(
db,
legacyGroupPublicKey: groupPublicKey,
latestKeyPair: keyPair
latestKeyPair: keyPair,
using: dependencies
)
}
catch {
@ -337,7 +346,8 @@ extension MessageReceiver {
_ db: Database,
threadId: String,
threadVariant: SessionThread.Variant,
message: ClosedGroupControlMessage
message: ClosedGroupControlMessage,
using dependencies: Dependencies
) throws {
guard
let messageKind: ClosedGroupControlMessage.Kind = message.kind,
@ -356,7 +366,8 @@ extension MessageReceiver {
try? SessionUtil.update(
db,
legacyGroupPublicKey: threadId,
name: name
name: name,
using: dependencies
)
_ = try ClosedGroup
@ -373,7 +384,8 @@ extension MessageReceiver {
_ db: Database,
threadId: String,
threadVariant: SessionThread.Variant,
message: ClosedGroupControlMessage
message: ClosedGroupControlMessage,
using dependencies: Dependencies
) throws {
guard
let messageKind: ClosedGroupControlMessage.Kind = message.kind,
@ -407,7 +419,8 @@ extension MessageReceiver {
admins: allMembers
.filter { $0.role == .admin }
.map { $0.profileId }
.asSet()
.asSet(),
using: dependencies
)
// Create records for any new members
@ -462,7 +475,8 @@ extension MessageReceiver {
_ db: Database,
threadId: String,
threadVariant: SessionThread.Variant,
message: ClosedGroupControlMessage
message: ClosedGroupControlMessage,
using dependencies: Dependencies
) throws {
guard
let messageKind: ClosedGroupControlMessage.Kind = message.kind,
@ -514,7 +528,8 @@ extension MessageReceiver {
admins: allMembers
.filter { $0.role == .admin }
.map { $0.profileId }
.asSet()
.asSet(),
using: dependencies
)
// Delete the removed members
@ -550,7 +565,8 @@ extension MessageReceiver {
_ db: Database,
threadId: String,
threadVariant: SessionThread.Variant,
message: ClosedGroupControlMessage
message: ClosedGroupControlMessage,
using dependencies: Dependencies
) throws {
guard
let messageKind: ClosedGroupControlMessage.Kind = message.kind,
@ -591,7 +607,8 @@ extension MessageReceiver {
admins: allMembers
.filter { $0.role == .admin }
.map { $0.profileId }
.asSet()
.asSet(),
using: dependencies
)
// Delete the members to remove

View File

@ -106,7 +106,8 @@ extension MessageReceiver {
threadId: blindedIdLookup.blindedId,
threadVariant: .contact,
groupLeaveType: .forced,
calledFromConfigHandling: false
calledFromConfigHandling: false,
using: dependencies
)
}

View File

@ -10,7 +10,8 @@ extension MessageReceiver {
_ db: Database,
threadId: String,
threadVariant: SessionThread.Variant,
message: UnsendRequest
message: UnsendRequest,
using dependencies: Dependencies = Dependencies()
) throws {
let userPublicKey: String = getUserHexEncodedPublicKey(db)
@ -35,7 +36,8 @@ extension MessageReceiver {
threadId: interaction.threadId,
threadVariant: threadVariant,
includingOlder: false,
trySendReadReceipt: false
trySendReadReceipt: false,
using: dependencies
)
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: interaction.notificationIdentifiers)
@ -46,9 +48,10 @@ extension MessageReceiver {
SnodeAPI
.deleteMessages(
publicKey: author,
serverHashes: [serverHash]
serverHashes: [serverHash],
using: dependencies
)
.subscribe(on: DispatchQueue.global(qos: .background))
.subscribe(on: DispatchQueue.global(qos: .background), using: dependencies)
.sinkUntilComplete()
}

View File

@ -93,7 +93,7 @@ extension MessageReceiver {
guard
let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db),
let blindedKeyPair: KeyPair = try? dependencies.crypto.generate(
let blindedKeyPair: KeyPair = dependencies.crypto.generate(
.blindedKeyPair(
serverPublicKey: openGroup.publicKey,
edKeyPair: userEdKeyPair,
@ -132,7 +132,8 @@ extension MessageReceiver {
associatedWithProto: proto,
sender: sender,
messageSentTimestamp: messageSentTimestamp,
openGroup: maybeOpenGroup
openGroup: maybeOpenGroup,
using: dependencies
) {
return interactionId
}
@ -159,7 +160,8 @@ extension MessageReceiver {
threadVariant: thread.variant,
timestampMs: Int64(messageSentTimestamp * 1000),
userPublicKey: currentUserPublicKey,
openGroup: maybeOpenGroup
openGroup: maybeOpenGroup,
using: dependencies
)
),
hasMention: Interaction.isUserMentioned(
@ -208,7 +210,8 @@ extension MessageReceiver {
interactionId: existingInteractionId,
messageSentTimestamp: messageSentTimestamp,
variant: variant,
syncTarget: message.syncTarget
syncTarget: message.syncTarget,
using: dependencies
)
getExpirationForOutgoingDisappearingMessages(
@ -234,7 +237,8 @@ extension MessageReceiver {
interactionId: interactionId,
messageSentTimestamp: messageSentTimestamp,
variant: variant,
syncTarget: message.syncTarget
syncTarget: message.syncTarget,
using: dependencies
)
getExpirationForOutgoingDisappearingMessages(
@ -366,7 +370,8 @@ extension MessageReceiver {
associatedWithProto proto: SNProtoContent,
sender: String,
messageSentTimestamp: TimeInterval,
openGroup: OpenGroup?
openGroup: OpenGroup?,
using dependencies: Dependencies
) throws -> Int64? {
guard
let reaction: VisibleMessage.VMReaction = message.reaction,
@ -413,7 +418,8 @@ extension MessageReceiver {
threadVariant: thread.variant,
timestampMs: timestampMs,
userPublicKey: currentUserPublicKey,
openGroup: openGroup
openGroup: openGroup,
using: dependencies
)
// Don't notify if the reaction was added before the lastest read timestamp for
@ -444,7 +450,8 @@ extension MessageReceiver {
interactionId: Int64,
messageSentTimestamp: TimeInterval,
variant: Interaction.Variant,
syncTarget: String?
syncTarget: String?,
using dependencies: Dependencies
) throws {
guard variant == .standardOutgoing else { return }
@ -494,7 +501,8 @@ extension MessageReceiver {
threadId: thread.id,
threadVariant: thread.variant,
includingOlder: true,
trySendReadReceipt: false
trySendReadReceipt: false,
using: dependencies
)
// Process any PendingReadReceipt values

View File

@ -12,13 +12,13 @@ extension MessageSender {
thread: SessionThread,
group: ClosedGroup,
members: [GroupMember],
preparedNotificationsSubscription: HTTP.PreparedRequest<PushNotificationAPI.SubscribeResponse>,
preparedNotificationsSubscription: HTTP.PreparedRequest<PushNotificationAPI.SubscribeResponse>?,
currentUserPublicKey: String
)
public static func createGroup(
name: String,
displayPicture: SignalAttachment?,
members: Set<String>,
members: [(String, Profile?)],
using dependencies: Dependencies = Dependencies()
) -> AnyPublisher<SessionThread, Error> {
Just(())
@ -38,6 +38,7 @@ extension MessageSender {
dependencies.storage.write { db -> PreparedGroupData in
// Create and cache the libSession entries
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
let currentUserProfile: Profile = Profile.fetchOrCreateCurrentUser(db, using: dependencies)
let groupData: (identityKeyPair: KeyPair, group: ClosedGroup, members: [GroupMember]) = try SessionUtil.createGroup(
db,
name: name,
@ -45,10 +46,10 @@ extension MessageSender {
displayPictureFilename: displayPictureInfo?.filename,
displayPictureEncryptionKey: displayPictureInfo?.encryptionKey,
members: members,
admins: [currentUserPublicKey],
admins: [(currentUserPublicKey, currentUserProfile)],
using: dependencies
)
let preparedNotificationSubscription = try PushNotificationAPI
let preparedNotificationSubscription = try? PushNotificationAPI
.preparedSubscribe(
publicKey: groupData.group.id,
subkey: nil,
@ -58,7 +59,13 @@ extension MessageSender {
// Save the relevant objects to the database
let thread: SessionThread = try SessionThread
.fetchOrCreate(db, id: groupData.group.id, variant: .group, shouldBeVisible: true)
.fetchOrCreate(
db,
id: groupData.group.id,
variant: .group,
shouldBeVisible: true,
using: dependencies
)
try groupData.group.insert(db)
try groupData.members.forEach { try $0.insert(db) }
@ -86,8 +93,8 @@ extension MessageSender {
// Start polling
ClosedGroupPoller.shared.startIfNeeded(for: group.id, using: dependencies)
// Subscribe for push notifications
preparedNotificationSubscription
// Subscribe for push notifications (if PNs are enabled)
preparedNotificationSubscription?
.send(using: dependencies)
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
.sinkUntilComplete()

View File

@ -83,7 +83,9 @@ extension MessageSender {
latestKeyPairReceivedTimestamp: latestKeyPairReceivedTimestamp,
disappearingConfig: DisappearingMessagesConfiguration.defaultWith(legacyGroupPublicKey),
members: members,
admins: admins
admins: admins,
formationTimestamp: formationTimestamp,
using: dependencies
)
let memberSendData: [MessageSender.PreparedSendData] = try members
@ -249,7 +251,8 @@ extension MessageSender {
admins: allGroupMembers
.filter { $0.role == .admin }
.map { $0.profileId }
.asSet()
.asSet(),
using: dependencies
)
}
@ -271,7 +274,7 @@ extension MessageSender {
name: String,
using dependencies: Dependencies = Dependencies()
) -> AnyPublisher<Void, Error> {
return Storage.shared
return dependencies.storage
.writePublisher { db -> (String, ClosedGroup, [GroupMember], Set<String>) in
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
@ -318,7 +321,8 @@ extension MessageSender {
try? SessionUtil.update(
db,
legacyGroupPublicKey: closedGroup.threadId,
name: name
name: name,
using: dependencies
)
}
@ -430,7 +434,8 @@ extension MessageSender {
admins: allGroupMembers
.filter { $0.role == .admin }
.map { $0.profileId }
.asSet()
.asSet(),
using: dependencies
)
// Send the update to the group

View File

@ -200,7 +200,7 @@ public enum MessageReceiver {
// the config then the message will be dropped)
guard
!Message.requiresExistingConversation(message: message, threadVariant: threadVariant) ||
SessionUtil.conversationInConfig(db, threadId: threadId, threadVariant: threadVariant, visibleOnly: false)
SessionUtil.conversationInConfig(db, threadId: threadId, threadVariant: threadVariant, visibleOnly: false, using: dependencies)
else { throw MessageReceiverError.requiredThreadNotInConfig }
// Throw if the message is outdated and shouldn't be processed
@ -221,7 +221,8 @@ public enum MessageReceiver {
threadId: threadId,
threadVariant: threadVariant,
message: message,
proto: proto
proto: proto,
using: dependencies
)
switch message {
@ -254,7 +255,8 @@ public enum MessageReceiver {
db,
threadId: threadId,
threadVariant: threadVariant,
message: message
message: message,
using: dependencies
)
case let message as ExpirationTimerUpdate:
@ -262,7 +264,8 @@ public enum MessageReceiver {
db,
threadId: threadId,
threadVariant: threadVariant,
message: message
message: message,
using: dependencies
)
case let message as UnsendRequest:
@ -270,7 +273,8 @@ public enum MessageReceiver {
db,
threadId: threadId,
threadVariant: threadVariant,
message: message
message: message,
using: dependencies
)
case let message as CallMessage:
@ -278,7 +282,8 @@ public enum MessageReceiver {
db,
threadId: threadId,
threadVariant: threadVariant,
message: message
message: message,
using: dependencies
)
case let message as MessageRequestResponse:
@ -403,7 +408,8 @@ public enum MessageReceiver {
db,
threadId: threadId,
threadVariant: threadVariant,
visibleOnly: true
visibleOnly: true,
using: dependencies
)
let canPerformChange: Bool = SessionUtil.canPerformChange(
db,

View File

@ -183,7 +183,7 @@ public enum PushNotificationAPI {
using dependencies: Dependencies = Dependencies()
) throws -> HTTP.PreparedRequest<SubscribeResponse> {
guard
UserDefaults.standard[.isUsingFullAPNs],
dependencies.standardUserDefaults[.isUsingFullAPNs],
let token: String = dependencies.standardUserDefaults[.deviceToken]
else { throw HTTPError.invalidRequest }

View File

@ -60,7 +60,7 @@ public final class ClosedGroupPoller: Poller {
override func nextPollDelay(for publicKey: String, using dependencies: Dependencies) -> TimeInterval {
// Get the received date of the last message in the thread. If we don't have
// any messages yet, pick some reasonable fake time interval to use instead
let lastMessageDate: Date = Storage.shared
let lastMessageDate: Date = dependencies.storage
.read { db in
try Interaction
.filter(Interaction.Columns.threadId == publicKey)
@ -74,7 +74,7 @@ public final class ClosedGroupPoller: Poller {
return Date(timeIntervalSince1970: (TimeInterval(receivedAtTimestampMs) / 1000))
}
.defaulting(to: Date().addingTimeInterval(-5 * 60))
.defaulting(to: dependencies.dateNow.addingTimeInterval(-5 * 60))
let timeSinceLastMessage: TimeInterval = dependencies.dateNow.timeIntervalSince(lastMessageDate)
let minPollInterval: Double = ClosedGroupPoller.minPollInterval

View File

@ -67,7 +67,7 @@ public final class CurrentUserPoller: Poller {
else if let targetSnode: Snode = targetSnode.wrappedValue {
SNLog("Main Poller polling \(targetSnode) failed; dropping it and switching to next snode.")
self.targetSnode.mutate { $0 = nil }
SnodeAPI.dropSnodeFromSwarmIfNeeded(targetSnode, publicKey: publicKey)
SnodeAPI.dropSnodeFromSwarmIfNeeded(targetSnode, publicKey: publicKey, using: dependencies)
}
else {
SNLog("Polling failed due to having no target service node.")

View File

@ -67,7 +67,7 @@ public class Poller {
internal func startIfNeeded(for publicKey: String, using dependencies: Dependencies) {
// Run on the 'pollerQueue' to ensure any 'Atomic' access doesn't block the main thread
// on startup
Threading.pollerQueue.async { [weak self] in
Threading.pollerQueue.async(using: dependencies) { [weak self] in
guard self?.isPolling.wrappedValue[publicKey] != true else { return }
// Might be a race condition that the setUpPolling finishes too soon,
@ -225,7 +225,7 @@ public class Poller {
poller?.pollerName(for: publicKey) ??
"poller with public key \(publicKey)"
)
let configHashes: [String] = SessionUtil.configHashes(for: publicKey)
let configHashes: [String] = SessionUtil.configHashes(for: publicKey, using: dependencies)
// Fetch the messages
return SnodeAPI

View File

@ -33,16 +33,16 @@ internal extension SessionUtil {
static func handleContactsUpdate(
_ db: Database,
in conf: UnsafeMutablePointer<config_object>?,
mergeNeedsDump: Bool,
latestConfigSentTimestampMs: Int64
in config: Config?,
latestConfigSentTimestampMs: Int64,
using dependencies: Dependencies
) throws {
guard mergeNeedsDump else { return }
guard conf != nil else { throw SessionUtilError.nilConfigObject }
guard config.needsDump else { return }
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
// The current users contact data is handled separately so exclude it if it's present (as that's
// actually a bug)
let userPublicKey: String = getUserHexEncodedPublicKey(db)
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
let targetContactData: [String: ContactData] = try extractContacts(
from: conf,
latestConfigSentTimestampMs: latestConfigSentTimestampMs
@ -151,7 +151,8 @@ internal extension SessionUtil {
threadId: sessionId,
threadVariant: .contact,
groupLeaveType: .forced,
calledFromConfigHandling: true
calledFromConfigHandling: true,
using: dependencies
)
case (true, false):
@ -270,10 +271,11 @@ internal extension SessionUtil {
threadIds: combinedIds,
threadVariant: .contact,
groupLeaveType: .forced,
calledFromConfigHandling: true
calledFromConfigHandling: true,
using: dependencies
)
try SessionUtil.remove(db, volatileContactIds: combinedIds)
try SessionUtil.remove(db, volatileContactIds: combinedIds, using: dependencies)
}
}
@ -281,9 +283,9 @@ internal extension SessionUtil {
static func upsert(
contactData: [SyncedContactInfo],
in conf: UnsafeMutablePointer<config_object>?
in config: Config?
) throws {
guard conf != nil else { throw SessionUtilError.nilConfigObject }
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
// The current users contact data doesn't need to sync so exclude it, we also don't want to sync
// blinded message requests so exclude those as well
@ -305,7 +307,7 @@ internal extension SessionUtil {
guard contacts_get_or_construct(conf, &contact, &sessionId) else {
/// It looks like there are some situations where this object might not get created correctly (and
/// will throw due to the implicit unwrapping) as a result we put it in a guard and throw instead
SNLog("Unable to upsert contact to SessionUtil: \(SessionUtil.lastError(conf))")
SNLog("Unable to upsert contact to SessionUtil: \(config.lastError)")
throw SessionUtilError.getOrConstructFailedUnexpectedly
}
@ -374,12 +376,16 @@ internal extension SessionUtil {
// MARK: - Outgoing Changes
internal extension SessionUtil {
static func updatingContacts<T>(_ db: Database, _ updated: [T]) throws -> [T] {
static func updatingContacts<T>(
_ db: Database,
_ updated: [T],
using dependencies: Dependencies
) throws -> [T] {
guard let updatedContacts: [Contact] = updated as? [Contact] else { throw StorageError.generic }
// The current users contact data doesn't need to sync so exclude it, we also don't want to sync
// blinded message requests so exclude those as well
let userPublicKey: String = getUserHexEncodedPublicKey(db)
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
let targetContacts: [Contact] = updatedContacts
.filter {
$0.id != userPublicKey &&
@ -392,8 +398,11 @@ internal extension SessionUtil {
try SessionUtil.performAndPushChange(
db,
for: .contacts,
publicKey: userPublicKey
) { conf in
publicKey: userPublicKey,
using: dependencies
) { config in
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
// When inserting new contacts (or contacts with invalid profile data) we want
// to add any valid profile information we have so identify if any of the updated
// contacts are new/invalid, and if so, fetch any profile data we have for them
@ -424,14 +433,18 @@ internal extension SessionUtil {
profile: newProfiles[contact.id]
)
},
in: conf
in: config
)
}
return updated
}
static func updatingProfiles<T>(_ db: Database, _ updated: [T]) throws -> [T] {
static func updatingProfiles<T>(
_ db: Database,
_ updated: [T],
using dependencies: Dependencies
) throws -> [T] {
guard let updatedProfiles: [Profile] = updated as? [Profile] else { throw StorageError.generic }
// We should only sync profiles which are associated to contact data to avoid including profiles
@ -449,7 +462,7 @@ internal extension SessionUtil {
guard !existingContactIds.isEmpty else { return updated }
// Get the user public key (updating their profile is handled separately)
let userPublicKey: String = getUserHexEncodedPublicKey(db)
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
let targetProfiles: [Profile] = updatedProfiles
.filter {
$0.id != userPublicKey &&
@ -462,11 +475,12 @@ internal extension SessionUtil {
try SessionUtil.performAndPushChange(
db,
for: .userProfile,
publicKey: userPublicKey
) { conf in
publicKey: userPublicKey,
using: dependencies
) { config in
try SessionUtil.update(
profile: updatedUserProfile,
in: conf
in: config
)
}
}
@ -474,20 +488,25 @@ internal extension SessionUtil {
try SessionUtil.performAndPushChange(
db,
for: .contacts,
publicKey: userPublicKey
) { conf in
publicKey: userPublicKey,
using: dependencies
) { config in
try SessionUtil
.upsert(
contactData: targetProfiles
.map { SyncedContactInfo(id: $0.id, profile: $0) },
in: conf
in: config
)
}
return updated
}
static func updatingDisappearingConfigsOneToOne<T>(_ db: Database, _ updated: [T]) throws -> [T] {
static func updatingDisappearingConfigsOneToOne<T>(
_ db: Database,
_ updated: [T],
using dependencies: Dependencies
) throws -> [T] {
guard let updatedDisappearingConfigs: [DisappearingMessagesConfiguration] = updated as? [DisappearingMessagesConfiguration] else { throw StorageError.generic }
// Filter out any disappearing config changes related to groups
@ -509,7 +528,7 @@ internal extension SessionUtil {
guard !existingContactIds.isEmpty else { return updated }
// Get the user public key (updating note to self is handled separately)
let userPublicKey: String = getUserHexEncodedPublicKey(db)
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
let targetDisappearingConfigs: [DisappearingMessagesConfiguration] = targetUpdatedConfigs
.filter {
$0.id != userPublicKey &&
@ -522,11 +541,12 @@ internal extension SessionUtil {
try SessionUtil.performAndPushChange(
db,
for: .userProfile,
publicKey: userPublicKey
) { conf in
publicKey: userPublicKey,
using: dependencies
) { config in
try SessionUtil.updateNoteToSelf(
disappearingMessagesConfig: updatedUserDisappearingConfig,
in: conf
in: config
)
}
}
@ -534,13 +554,14 @@ internal extension SessionUtil {
try SessionUtil.performAndPushChange(
db,
for: .contacts,
publicKey: userPublicKey
) { conf in
publicKey: userPublicKey,
using: dependencies
) { config in
try SessionUtil
.upsert(
contactData: targetDisappearingConfigs
.map { SyncedContactInfo(id: $0.id, disappearingMessagesConfig: $0) },
in: conf
in: config
)
}
@ -551,12 +572,17 @@ internal extension SessionUtil {
// MARK: - External Outgoing Changes
public extension SessionUtil {
static func hide(_ db: Database, contactIds: [String]) throws {
static func hide(
_ db: Database,
contactIds: [String],
using dependencies: Dependencies
) throws {
try SessionUtil.performAndPushChange(
db,
for: .contacts,
publicKey: getUserHexEncodedPublicKey(db)
) { conf in
publicKey: getUserHexEncodedPublicKey(db, using: dependencies),
using: dependencies
) { config in
// Mark the contacts as hidden
try SessionUtil.upsert(
contactData: contactIds
@ -566,19 +592,26 @@ public extension SessionUtil {
priority: SessionUtil.hiddenPriority
)
},
in: conf
in: config
)
}
}
static func remove(_ db: Database, contactIds: [String]) throws {
static func remove(
_ db: Database,
contactIds: [String],
using dependencies: Dependencies
) throws {
guard !contactIds.isEmpty else { return }
try SessionUtil.performAndPushChange(
db,
for: .contacts,
publicKey: getUserHexEncodedPublicKey(db)
) { conf in
publicKey: getUserHexEncodedPublicKey(db, using: dependencies),
using: dependencies
) { config in
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
contactIds.forEach { sessionId in
var cSessionId: [CChar] = sessionId.cArray.nullTerminated()
@ -591,20 +624,22 @@ public extension SessionUtil {
static func update(
_ db: Database,
sessionId: String,
disappearingMessagesConfig: DisappearingMessagesConfiguration
disappearingMessagesConfig: DisappearingMessagesConfiguration,
using dependencies: Dependencies
) throws {
let userPublicKey: String = getUserHexEncodedPublicKey(db)
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
switch sessionId {
case userPublicKey:
try SessionUtil.performAndPushChange(
db,
for: .userProfile,
publicKey: userPublicKey
) { conf in
publicKey: userPublicKey,
using: dependencies
) { config in
try SessionUtil.updateNoteToSelf(
disappearingMessagesConfig: disappearingMessagesConfig,
in: conf
in: config
)
}
@ -612,8 +647,9 @@ public extension SessionUtil {
try SessionUtil.performAndPushChange(
db,
for: .contacts,
publicKey: userPublicKey
) { conf in
publicKey: userPublicKey,
using: dependencies
) { config in
try SessionUtil
.upsert(
contactData: [
@ -622,7 +658,7 @@ public extension SessionUtil {
disappearingMessagesConfig: disappearingMessagesConfig
)
],
in: conf
in: config
)
}
}

View File

@ -16,11 +16,11 @@ internal extension SessionUtil {
static func handleConvoInfoVolatileUpdate(
_ db: Database,
in conf: UnsafeMutablePointer<config_object>?,
mergeNeedsDump: Bool
in config: Config?,
using dependencies: Dependencies
) throws {
guard mergeNeedsDump else { return }
guard conf != nil else { throw SessionUtilError.nilConfigObject }
guard config.needsDump else { return }
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
// Get the volatile thread info from the conf and local conversations
let volatileThreadInfo: [VolatileThreadInfo] = try extractConvoVolatileInfo(from: conf)
@ -101,15 +101,15 @@ internal extension SessionUtil {
try upsert(
convoInfoVolatileChanges: newerLocalChanges,
in: conf
in: config
)
}
static func upsert(
convoInfoVolatileChanges: [VolatileThreadInfo],
in conf: UnsafeMutablePointer<config_object>?
in config: Config?
) throws {
guard conf != nil else { throw SessionUtilError.nilConfigObject }
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
// Exclude any invalid thread info
let validChanges: [VolatileThreadInfo] = convoInfoVolatileChanges
@ -135,7 +135,7 @@ internal extension SessionUtil {
guard convo_info_volatile_get_or_construct_1to1(conf, &oneToOne, &cThreadId) else {
/// It looks like there are some situations where this object might not get created correctly (and
/// will throw due to the implicit unwrapping) as a result we put it in a guard and throw instead
SNLog("Unable to upsert contact volatile info to SessionUtil: \(SessionUtil.lastError(conf))")
SNLog("Unable to upsert contact volatile info to SessionUtil: \(config.lastError)")
throw SessionUtilError.getOrConstructFailedUnexpectedly
}
@ -156,7 +156,7 @@ internal extension SessionUtil {
guard convo_info_volatile_get_or_construct_legacy_group(conf, &legacyGroup, &cThreadId) else {
/// It looks like there are some situations where this object might not get created correctly (and
/// will throw due to the implicit unwrapping) as a result we put it in a guard and throw instead
SNLog("Unable to upsert legacy group volatile info to SessionUtil: \(SessionUtil.lastError(conf))")
SNLog("Unable to upsert legacy group volatile info to SessionUtil: \(config.lastError)")
throw SessionUtilError.getOrConstructFailedUnexpectedly
}
@ -186,7 +186,7 @@ internal extension SessionUtil {
guard convo_info_volatile_get_or_construct_community(conf, &community, &cBaseUrl, &cRoomToken, &cPubkey) else {
/// It looks like there are some situations where this object might not get created correctly (and
/// will throw due to the implicit unwrapping) as a result we put it in a guard and throw instead
SNLog("Unable to upsert community volatile info to SessionUtil: \(SessionUtil.lastError(conf))")
SNLog("Unable to upsert community volatile info to SessionUtil: \(config.lastError)")
throw SessionUtilError.getOrConstructFailedUnexpectedly
}
@ -208,7 +208,8 @@ internal extension SessionUtil {
static func updateMarkedAsUnreadState(
_ db: Database,
threads: [SessionThread]
threads: [SessionThread],
using dependencies: Dependencies
) throws {
// If we have no updated threads then no need to continue
guard !threads.isEmpty else { return }
@ -227,21 +228,29 @@ internal extension SessionUtil {
try SessionUtil.performAndPushChange(
db,
for: .convoInfoVolatile,
publicKey: getUserHexEncodedPublicKey(db)
) { conf in
publicKey: getUserHexEncodedPublicKey(db, using: dependencies),
using: dependencies
) { config in
try upsert(
convoInfoVolatileChanges: changes,
in: conf
in: config
)
}
}
static func remove(_ db: Database, volatileContactIds: [String]) throws {
static func remove(
_ db: Database,
volatileContactIds: [String],
using dependencies: Dependencies
) throws {
try SessionUtil.performAndPushChange(
db,
for: .convoInfoVolatile,
publicKey: getUserHexEncodedPublicKey(db)
) { conf in
publicKey: getUserHexEncodedPublicKey(db, using: dependencies),
using: dependencies
) { config in
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
volatileContactIds.forEach { contactId in
var cSessionId: [CChar] = contactId.cArray.nullTerminated()
@ -251,12 +260,19 @@ internal extension SessionUtil {
}
}
static func remove(_ db: Database, volatileLegacyGroupIds: [String]) throws {
static func remove(
_ db: Database,
volatileLegacyGroupIds: [String],
using dependencies: Dependencies
) throws {
try SessionUtil.performAndPushChange(
db,
for: .convoInfoVolatile,
publicKey: getUserHexEncodedPublicKey(db)
) { conf in
publicKey: getUserHexEncodedPublicKey(db, using: dependencies),
using: dependencies
) { config in
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
volatileLegacyGroupIds.forEach { legacyGroupId in
var cLegacyGroupId: [CChar] = legacyGroupId.cArray.nullTerminated()
@ -266,12 +282,35 @@ internal extension SessionUtil {
}
}
static func remove(_ db: Database, volatileCommunityInfo: [OpenGroupUrlInfo]) throws {
static func remove(
_ db: Database,
volatileGroupIds: [String],
using dependencies: Dependencies
) throws {
try SessionUtil.performAndPushChange(
db,
for: .convoInfoVolatile,
publicKey: getUserHexEncodedPublicKey(db)
) { conf in
publicKey: getUserHexEncodedPublicKey(db, using: dependencies),
using: dependencies
) { config in
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
}
}
static func remove(
_ db: Database,
volatileCommunityInfo: [OpenGroupUrlInfo],
using dependencies: Dependencies
) throws {
try SessionUtil.performAndPushChange(
db,
for: .convoInfoVolatile,
publicKey: getUserHexEncodedPublicKey(db, using: dependencies),
using: dependencies
) { config in
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
volatileCommunityInfo.forEach { urlInfo in
var cBaseUrl: [CChar] = urlInfo.server.cArray.nullTerminated()
var cRoom: [CChar] = urlInfo.roomToken.cArray.nullTerminated()
@ -290,13 +329,15 @@ public extension SessionUtil {
_ db: Database,
threadId: String,
threadVariant: SessionThread.Variant,
lastReadTimestampMs: Int64
lastReadTimestampMs: Int64,
using dependencies: Dependencies
) throws {
try SessionUtil.performAndPushChange(
db,
for: .convoInfoVolatile,
publicKey: getUserHexEncodedPublicKey(db)
) { conf in
publicKey: getUserHexEncodedPublicKey(db, using: dependencies),
using: dependencies
) { config in
try upsert(
convoInfoVolatileChanges: [
VolatileThreadInfo(
@ -308,7 +349,7 @@ public extension SessionUtil {
changes: [.lastReadTimestampMs(lastReadTimestampMs)]
)
],
in: conf
in: config
)
}
}
@ -318,12 +359,15 @@ public extension SessionUtil {
threadVariant: SessionThread.Variant,
timestampMs: Int64,
userPublicKey: String,
openGroup: OpenGroup?
openGroup: OpenGroup?,
using dependencies: Dependencies
) -> Bool {
return SessionUtil
return dependencies.caches[.sessionUtil]
.config(for: .convoInfoVolatile, publicKey: userPublicKey)
.wrappedValue
.map { conf in
.map { config in
guard case .object(let conf) = config else { return false }
switch threadVariant {
case .contact:
var cThreadId: [CChar] = threadId.cArray.nullTerminated()

View File

@ -21,21 +21,25 @@ internal extension SessionUtil {
static func handleGroupInfoUpdate(
_ db: Database,
in conf: UnsafeMutablePointer<config_object>?,
mergeNeedsDump: Bool,
latestConfigSentTimestampMs: Int64
in config: Config?,
latestConfigSentTimestampMs: Int64,
using dependencies: Dependencies
) throws {
typealias GroupData = (profileName: String, profilePictureUrl: String?, profilePictureKey: Data?)
guard mergeNeedsDump else { return }
guard conf != nil else { throw SessionUtilError.nilConfigObject }
guard config.needsDump else { return }
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
}
}
// MARK: - Outgoing Changes
internal extension SessionUtil {
static func updatingGroupInfo<T>(_ db: Database, _ updated: [T]) throws -> [T] {
static func updatingGroupInfo<T>(
_ db: Database,
_ updated: [T],
using dependencies: Dependencies
) throws -> [T] {
guard let updatedGroups: [ClosedGroup] = updated as? [ClosedGroup] else { throw StorageError.generic }
// Exclude legacy groups as they aren't managed via SessionUtil
@ -50,8 +54,11 @@ internal extension SessionUtil {
try SessionUtil.performAndPushChange(
db,
for: .groupInfo,
publicKey: group.threadId
) { conf in
publicKey: group.threadId,
using: dependencies
) { config in
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
// Update the name
var updatedName: [CChar] = group.name.cArray.nullTerminated()
groups_info_set_name(conf, &updatedName)
@ -67,7 +74,11 @@ internal extension SessionUtil {
return updated
}
static func updatingDisappearingConfigsGroups<T>(_ db: Database, _ updated: [T]) throws -> [T] {
static func updatingDisappearingConfigsGroups<T>(
_ db: Database,
_ updated: [T],
using dependencies: Dependencies
) throws -> [T] {
guard let updatedDisappearingConfigs: [DisappearingMessagesConfiguration] = updated as? [DisappearingMessagesConfiguration] else { throw StorageError.generic }
// Filter out any disappearing config changes not related to updated groups
@ -95,8 +106,11 @@ internal extension SessionUtil {
try SessionUtil.performAndPushChange(
db,
for: .groupInfo,
publicKey: groupIdentityPublicKey
) { conf in
publicKey: groupIdentityPublicKey,
using: dependencies
) { config in
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
groups_info_set_expiry_timer(conf, Int32(updatedConfig.durationSeconds))
}
}
@ -112,13 +126,17 @@ public extension SessionUtil {
_ db: Database,
groupIdentityPublicKey: String,
name: String? = nil,
disappearingConfig: DisappearingMessagesConfiguration? = nil
disappearingConfig: DisappearingMessagesConfiguration? = nil,
using dependencies: Dependencies
) throws {
try SessionUtil.performAndPushChange(
db,
for: .groupInfo,
publicKey: groupIdentityPublicKey
) { conf in
publicKey: groupIdentityPublicKey,
using: dependencies
) { config in
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
if let name: String = name {
groups_info_set_name(conf, name.toLibSession())
}

View File

@ -14,12 +14,12 @@ internal extension SessionUtil {
static func handleGroupKeysUpdate(
_ db: Database,
in conf: UnsafeMutablePointer<config_object>?,
mergeNeedsDump: Bool,
latestConfigSentTimestampMs: Int64
in config: Config?,
latestConfigSentTimestampMs: Int64,
using dependencies: Dependencies
) throws {
guard mergeNeedsDump else { return }
guard conf != nil else { throw SessionUtilError.nilConfigObject }
guard config.needsDump else { return }
guard case .groupKeys(let conf) = config else { throw SessionUtilError.invalidConfigObject }
}
}

View File

@ -14,12 +14,12 @@ internal extension SessionUtil {
static func handleGroupMembersUpdate(
_ db: Database,
in conf: UnsafeMutablePointer<config_object>?,
mergeNeedsDump: Bool,
latestConfigSentTimestampMs: Int64
in config: Config?,
latestConfigSentTimestampMs: Int64,
using dependencies: Dependencies
) throws {
guard mergeNeedsDump else { return }
guard conf != nil else { throw SessionUtilError.nilConfigObject }
guard config.needsDump else { return }
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
}
}

View File

@ -47,36 +47,44 @@ internal extension SessionUtil {
return (priority >= SessionUtil.visiblePriority)
}
static func pushChangesIfNeeded(
_ db: Database,
for variant: ConfigDump.Variant,
publicKey: String,
using dependencies: Dependencies
) throws {
try performAndPushChange(db, for: variant, publicKey: publicKey, using: dependencies) { _ in }
}
static func performAndPushChange(
_ db: Database,
for variant: ConfigDump.Variant,
publicKey: String,
change: (UnsafeMutablePointer<config_object>?) throws -> ()
using dependencies: Dependencies,
change: (Config?) throws -> ()
) throws {
// Since we are doing direct memory manipulation we are using an `Atomic`
// type which has blocking access in it's `mutate` closure
let needsPush: Bool
do {
needsPush = try SessionUtil
needsPush = try dependencies.caches[.sessionUtil]
.config(for: variant, publicKey: publicKey)
.mutate { conf in
guard conf != nil else { throw SessionUtilError.nilConfigObject }
.mutate { config in
// Peform the change
try change(conf)
try change(config)
// If we don't need to dump the data the we can finish early
guard config_needs_dump(conf) else { return config_needs_push(conf) }
guard config.needsDump else { return config.needsPush }
try SessionUtil.createDump(
conf: conf,
config: config,
for: variant,
publicKey: publicKey,
timestampMs: SnodeAPI.currentOffsetTimestampMs()
)?.save(db)
return config_needs_push(conf)
return config.needsPush
}
}
catch {
@ -92,7 +100,11 @@ internal extension SessionUtil {
}
}
@discardableResult static func updatingThreads<T>(_ db: Database, _ updated: [T]) throws -> [T] {
@discardableResult static func updatingThreads<T>(
_ db: Database,
_ updated: [T],
using dependencies: Dependencies
) throws -> [T] {
guard let updatedThreads: [SessionThread] = updated as? [SessionThread] else {
throw StorageError.generic
}
@ -108,7 +120,7 @@ internal extension SessionUtil {
.reduce(into: [:]) { result, next in result[next.threadId] = next }
// Update the unread state for the threads first (just in case that's what changed)
try SessionUtil.updateMarkedAsUnreadState(db, threads: updatedThreads)
try SessionUtil.updateMarkedAsUnreadState(db, threads: updatedThreads, using: dependencies)
// Then update the `hidden` and `priority` values
try groupedThreads.forEach { variant, threads in
@ -120,8 +132,9 @@ internal extension SessionUtil {
try SessionUtil.performAndPushChange(
db,
for: .userProfile,
publicKey: userPublicKey
) { conf in
publicKey: userPublicKey,
using: dependencies
) { config in
try SessionUtil.updateNoteToSelf(
priority: {
guard noteToSelf.shouldBeVisible else { return SessionUtil.hiddenPriority }
@ -130,7 +143,7 @@ internal extension SessionUtil {
.map { Int32($0 == 0 ? SessionUtil.visiblePriority : max($0, 1)) }
.defaulting(to: SessionUtil.visiblePriority)
}(),
in: conf
in: config
)
}
}
@ -143,8 +156,9 @@ internal extension SessionUtil {
try SessionUtil.performAndPushChange(
db,
for: .contacts,
publicKey: userPublicKey
) { conf in
publicKey: userPublicKey,
using: dependencies
) { config in
try SessionUtil.upsert(
contactData: remainingThreads
.map { thread in
@ -159,7 +173,7 @@ internal extension SessionUtil {
}()
)
},
in: conf
in: config
)
}
@ -167,8 +181,9 @@ internal extension SessionUtil {
try SessionUtil.performAndPushChange(
db,
for: .userGroups,
publicKey: userPublicKey
) { conf in
publicKey: userPublicKey,
using: dependencies
) { config in
try SessionUtil.upsert(
communities: threads
.compactMap { thread -> CommunityInfo? in
@ -181,7 +196,7 @@ internal extension SessionUtil {
)
}
},
in: conf
in: config
)
}
@ -189,8 +204,9 @@ internal extension SessionUtil {
try SessionUtil.performAndPushChange(
db,
for: .userGroups,
publicKey: userPublicKey
) { conf in
publicKey: userPublicKey,
using: dependencies
) { config in
try SessionUtil.upsert(
legacyGroups: threads
.map { thread in
@ -201,7 +217,7 @@ internal extension SessionUtil {
.defaulting(to: SessionUtil.visiblePriority)
)
},
in: conf
in: config
)
}
@ -209,8 +225,9 @@ internal extension SessionUtil {
try SessionUtil.performAndPushChange(
db,
for: .userGroups,
publicKey: userPublicKey
) { conf in
publicKey: userPublicKey,
using: dependencies
) { config in
try SessionUtil.upsert(
groups: threads
.map { thread in
@ -221,7 +238,7 @@ internal extension SessionUtil {
.defaulting(to: SessionUtil.visiblePriority)
)
},
in: conf
in: config
)
}
}
@ -230,23 +247,31 @@ internal extension SessionUtil {
return updated
}
static func hasSetting(_ db: Database, forKey key: String) throws -> Bool {
static func hasSetting(
_ db: Database,
forKey key: String,
using dependencies: Dependencies
) throws -> Bool {
let userPublicKey: String = getUserHexEncodedPublicKey(db)
// Currently the only synced setting is 'checkForCommunityMessageRequests'
switch key {
case Setting.BoolKey.checkForCommunityMessageRequests.rawValue:
return try SessionUtil
return try dependencies.caches[.sessionUtil]
.config(for: .userProfile, publicKey: userPublicKey)
.wrappedValue
.map { conf -> Bool in (try SessionUtil.rawBlindedMessageRequestValue(in: conf) >= 0) }
.map { config -> Bool in (try SessionUtil.rawBlindedMessageRequestValue(in: config) >= 0) }
.defaulting(to: false)
default: return false
}
}
static func updatingSetting(_ db: Database, _ updated: Setting?) throws {
static func updatingSetting(
_ db: Database,
_ updated: Setting?,
using dependencies: Dependencies
) throws {
// Don't current support any nullable settings
guard let updatedSetting: Setting = updated else { return }
@ -258,11 +283,12 @@ internal extension SessionUtil {
try SessionUtil.performAndPushChange(
db,
for: .userProfile,
publicKey: userPublicKey
) { conf in
publicKey: userPublicKey,
using: dependencies
) { config in
try SessionUtil.updateSettings(
checkForCommunityMessageRequests: updatedSetting.unsafeValue(as: Bool.self),
in: conf
in: config
)
}
@ -402,7 +428,8 @@ public extension SessionUtil {
_ db: Database? = nil,
threadId: String,
threadVariant: SessionThread.Variant,
visibleOnly: Bool
visibleOnly: Bool,
using dependencies: Dependencies = Dependencies()
) -> Bool {
let userPublicKey: String = getUserHexEncodedPublicKey(db)
let configVariant: ConfigDump.Variant = {
@ -412,10 +439,12 @@ public extension SessionUtil {
}
}()
return SessionUtil
return dependencies.caches[.sessionUtil]
.config(for: configVariant, publicKey: userPublicKey)
.wrappedValue
.map { conf in
.map { config in
guard case .object(let conf) = config else { return false }
var cThreadId: [CChar] = threadId.cArray.nullTerminated()
switch threadVariant {

View File

@ -16,13 +16,13 @@ internal extension SessionUtil {
displayPictureUrl: String?,
displayPictureFilename: String?,
displayPictureEncryptionKey: Data?,
members: Set<String>,
admins: Set<String>,
members: [(id: String, profile: Profile?)],
admins: [(id: String, profile: Profile?)],
using dependencies: Dependencies
) throws -> (identityKeyPair: KeyPair, group: ClosedGroup, members: [GroupMember]) {
guard
let groupIdentityKeyPair: KeyPair = dependencies.crypto.generate(.ed25519KeyPair()),
let userED25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db)
let userED25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db, using: dependencies)
else { throw MessageSenderError.noKeyPair }
// There will probably be custom init functions, will need a way to save the conf into
@ -30,9 +30,12 @@ internal extension SessionUtil {
var secretKey: [UInt8] = userED25519KeyPair.secretKey
var groupIdentityPublicKey: [UInt8] = groupIdentityKeyPair.publicKey
var groupIdentityPrivateKey: [UInt8] = groupIdentityKeyPair.secretKey
let groupIdentityPublicKeyString: String = groupIdentityKeyPair.publicKey.toHexString()
let creationTimestamp: TimeInterval = TimeInterval(SnodeAPI.currentOffsetTimestampMs() * 1000)
let groupId: SessionId = SessionId(.group, publicKey: groupIdentityKeyPair.publicKey)
let creationTimestamp: TimeInterval = TimeInterval(
SnodeAPI.currentOffsetTimestampMs(using: dependencies) / 1000
)
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
let currentUserProfile: Profile? = Profile.fetchOrCreateCurrentUser(db, using: dependencies)
// Create the new config objects
var groupKeysConf: UnsafeMutablePointer<config_group_keys>? = nil
@ -55,38 +58,111 @@ internal extension SessionUtil {
0,
&error
).orThrow(error: error)
try groups_keys_init(
&groupKeysConf,
&secretKey,
&groupIdentityPublicKey,
&groupIdentityPrivateKey,
groupInfoConf,
groupMembersConf,
nil,
0,
&error
).orThrow(error: error)
guard
let keysConf: UnsafeMutablePointer<config_group_keys> = groupKeysConf,
let infoConf: UnsafeMutablePointer<config_object> = groupInfoConf,
let membersConf: UnsafeMutablePointer<config_object> = groupMembersConf
else {
SNLog("[SessionUtil Error] Group config objects were null")
throw SessionUtilError.unableToCreateConfigObject
}
// Set the initial values in the confs
var groupName: [CChar] = name.cArray.nullTerminated()
groups_info_set_name(groupInfoConf, &groupName)
groups_info_set_created(groupInfoConf, Int64(floor(creationTimestamp)))
if var displayPictureUrl: String = displayPictureUrl, var displayPictureEncryptionKey: Data = displayPictureEncryptionKey {
if
let displayPictureUrl: String = displayPictureUrl,
let displayPictureEncryptionKey: Data = displayPictureEncryptionKey
{
var displayPic: user_profile_pic = user_profile_pic()
displayPic.url = displayPictureUrl.toLibSession()
displayPic.key = displayPictureEncryptionKey.toLibSession()
groups_info_set_pic(groupInfoConf, displayPic)
}
// Create dumps for the configs
try [.groupKeys, .groupInfo, .groupMembers].forEach { variant in
try SessionUtil.pushChangesIfNeeded(db, for: variant, publicKey: groupIdentityPublicKeyString)
// Store the members/admins in the group (reduce to ensure we don't accidentally insert duplicates)
let finalMembers: [String: (profile: Profile?, isAdmin: Bool)] = members
.map { ($0.id, $0.profile, false) }
.appending(contentsOf: admins.map { ($0.id, $0.profile, true) })
.appending((currentUserPublicKey, currentUserProfile, true))
.reduce(into: [:]) { result, next in result[next.0] = (profile: next.1, isAdmin: next.2)}
try finalMembers.forEach { memberId, info in
var profilePic: user_profile_pic = user_profile_pic()
if
let picUrl: String = info.profile?.profilePictureUrl,
let picKey: Data = info.profile?.profileEncryptionKey
{
profilePic.url = picUrl.toLibSession()
profilePic.key = picKey.toLibSession()
}
try CExceptionHelper.performSafely {
var member: config_group_member = config_group_member(
session_id: memberId.toLibSession(),
name: (info.profile?.name ?? "").toLibSession(),
profile_pic: profilePic,
admin: info.isAdmin,
invited: 0,
promoted: 0
)
groups_members_set(membersConf, &member)
}
}
// Load them into memory
let groupState: [ConfigDump.Variant: Config] = [
.groupKeys: .groupKeys(keysConf, info: infoConf, members: membersConf),
.groupInfo: .object(infoConf),
.groupMembers: .object(membersConf),
]
dependencies.caches.mutate(cache: .sessionUtil) { cache in
groupState.forEach { variant, config in
cache.setConfig(for: variant, publicKey: groupId.hexString, to: config)
}
}
// Create and save dumps for the configs
try groupState.forEach { variant, config in
try SessionUtil.createDump(
config: config,
for: variant,
publicKey: groupId.hexString,
timestampMs: Int64(floor(creationTimestamp * 1000))
)?.save(db)
}
// Add the new group to the USER_GROUPS config message
try SessionUtil.add(
db,
groupIdentityPublicKey: groupIdentityPublicKeyString,
groupIdentityPublicKey: groupId.hexString,
groupIdentityPrivateKey: Data(groupIdentityPrivateKey),
name: name,
tag: nil,
subkey: nil
subkey: nil,
joinedAt: Int64(floor(creationTimestamp)),
using: dependencies
)
return (
groupIdentityKeyPair,
ClosedGroup(
threadId: groupIdentityPublicKeyString,
threadId: groupId.hexString,
name: name,
formationTimestamp: creationTimestamp,
displayPictureUrl: displayPictureUrl,
@ -96,25 +172,14 @@ internal extension SessionUtil {
groupIdentityPrivateKey: Data(groupIdentityPrivateKey),
approved: true
),
members
.map { memberId -> GroupMember in
GroupMember(
groupId: groupIdentityPublicKeyString,
profileId: memberId,
role: .standard,
isHidden: false
)
}
.appending(
contentsOf: admins.map { memberId -> GroupMember in
GroupMember(
groupId: groupIdentityPublicKeyString,
profileId: memberId,
role: .admin,
isHidden: false
)
}
finalMembers.map { memberId, info -> GroupMember in
GroupMember(
groupId: groupId.hexString,
profileId: memberId,
role: (info.isAdmin ? .admin : .standard),
isHidden: false
)
}
)
}
@ -131,6 +196,7 @@ internal extension SessionUtil {
) throws -> (group: ClosedGroup, members: [GroupMember]) {
// TODO: This!!!
preconditionFailure()
}
}
private extension Int32 {

View File

@ -25,19 +25,19 @@ internal extension SessionUtil {
// MARK: - Incoming Changes
static func handleGroupsUpdate(
static func handleUserGroupsUpdate(
_ db: Database,
in conf: UnsafeMutablePointer<config_object>?,
mergeNeedsDump: Bool,
in config: Config?,
latestConfigSentTimestampMs: Int64,
using dependencies: Dependencies = Dependencies()
using dependencies: Dependencies
) throws {
guard mergeNeedsDump else { return }
guard conf != nil else { throw SessionUtilError.nilConfigObject }
guard config.needsDump else { return }
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
var infiniteLoopGuard: Int = 0
var communities: [PrioritisedData<OpenGroupUrlInfo>] = []
var legacyGroups: [LegacyGroupInfo] = []
var groups: [GroupInfo] = []
var community: ugroups_community_info = ugroups_community_info()
var legacyGroup: ugroups_legacy_group_info = ugroups_legacy_group_info()
let groupsIterator: OpaquePointer = user_groups_iterator_new(conf)
@ -193,7 +193,8 @@ internal extension SessionUtil {
threadIds: Array(communityIdsToRemove),
threadVariant: .community,
groupLeaveType: .forced,
calledFromConfigHandling: true
calledFromConfigHandling: true,
using: dependencies
)
}
@ -223,7 +224,7 @@ internal extension SessionUtil {
if !existingLegacyGroupIds.contains(group.id) {
// Add a new group if it doesn't already exist
try MessageReceiver.handleNewClosedGroup(
try MessageReceiver.handleNewLegacyClosedGroup(
db,
groupPublicKey: group.id,
name: name,
@ -380,7 +381,8 @@ internal extension SessionUtil {
threadIds: Array(legacyGroupIdsToRemove),
threadVariant: .legacyGroup,
groupLeaveType: .forced,
calledFromConfigHandling: true
calledFromConfigHandling: true,
using: dependencies
)
}
@ -409,9 +411,9 @@ internal extension SessionUtil {
static func upsert(
legacyGroups: [LegacyGroupInfo],
in conf: UnsafeMutablePointer<config_object>?
in config: Config?
) throws {
guard conf != nil else { throw SessionUtilError.nilConfigObject }
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
guard !legacyGroups.isEmpty else { return }
try legacyGroups
@ -420,7 +422,7 @@ internal extension SessionUtil {
guard let userGroup: UnsafeMutablePointer<ugroups_legacy_group_info> = user_groups_get_or_construct_legacy_group(conf, &cGroupId) else {
/// It looks like there are some situations where this object might not get created correctly (and
/// will throw due to the implicit unwrapping) as a result we put it in a guard and throw instead
SNLog("Unable to upsert legacy group conversation to SessionUtil: \(SessionUtil.lastError(conf))")
SNLog("Unable to upsert legacy group conversation to SessionUtil: \(config.lastError)")
throw SessionUtilError.getOrConstructFailedUnexpectedly
}
@ -515,10 +517,19 @@ internal extension SessionUtil {
}
static func upsert(
communities: [CommunityInfo],
in conf: UnsafeMutablePointer<config_object>?
groups: [GroupInfo],
in config: Config?
) throws {
guard conf != nil else { throw SessionUtilError.nilConfigObject }
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
guard !groups.isEmpty else { return }
}
static func upsert(
communities: [CommunityInfo],
in config: Config?
) throws {
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
guard !communities.isEmpty else { return }
try communities
@ -531,7 +542,7 @@ internal extension SessionUtil {
guard user_groups_get_or_construct_community(conf, &userCommunity, &cBaseUrl, &cRoom, &cPubkey) else {
/// It looks like there are some situations where this object might not get created correctly (and
/// will throw due to the implicit unwrapping) as a result we put it in a guard and throw instead
SNLog("Unable to upsert community conversation to SessionUtil: \(SessionUtil.lastError(conf))")
SNLog("Unable to upsert community conversation to SessionUtil: \(config.lastError)")
throw SessionUtilError.getOrConstructFailedUnexpectedly
}
@ -551,13 +562,15 @@ public extension SessionUtil {
_ db: Database,
server: String,
rootToken: String,
publicKey: String
publicKey: String,
using dependencies: Dependencies
) throws {
try SessionUtil.performAndPushChange(
db,
for: .userGroups,
publicKey: getUserHexEncodedPublicKey(db)
) { conf in
publicKey: getUserHexEncodedPublicKey(db, using: dependencies),
using: dependencies
) { config in
try SessionUtil.upsert(
communities: [
CommunityInfo(
@ -569,17 +582,25 @@ public extension SessionUtil {
)
)
],
in: conf
in: config
)
}
}
static func remove(_ db: Database, server: String, roomToken: String) throws {
static func remove(
_ db: Database,
server: String,
roomToken: String,
using dependencies: Dependencies
) throws {
try SessionUtil.performAndPushChange(
db,
for: .userGroups,
publicKey: getUserHexEncodedPublicKey(db)
) { conf in
publicKey: getUserHexEncodedPublicKey(db, using: dependencies),
using: dependencies
) { config in
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
var cBaseUrl: [CChar] = server.cArray.nullTerminated()
var cRoom: [CChar] = roomToken.cArray.nullTerminated()
@ -597,7 +618,8 @@ public extension SessionUtil {
roomToken: roomToken,
publicKey: ""
)
]
],
using: dependencies
)
}
@ -612,14 +634,17 @@ public extension SessionUtil {
latestKeyPairReceivedTimestamp: TimeInterval,
disappearingConfig: DisappearingMessagesConfiguration,
members: Set<String>,
admins: Set<String>
admins: Set<String>,
formationTimestamp: TimeInterval,
using dependencies: Dependencies
) throws {
try SessionUtil.performAndPushChange(
db,
for: .userGroups,
publicKey: getUserHexEncodedPublicKey(db)
) { conf in
guard conf != nil else { throw SessionUtilError.nilConfigObject }
publicKey: getUserHexEncodedPublicKey(db, using: dependencies),
using: dependencies
) { config in
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
var cGroupId: [CChar] = legacyGroupPublicKey.cArray.nullTerminated()
let userGroup: UnsafeMutablePointer<ugroups_legacy_group_info>? = user_groups_get_legacy_group(conf, &cGroupId)
@ -661,10 +686,11 @@ public extension SessionUtil {
role: .admin,
isHidden: false
)
}
},
joinedAt: Int64(floor(formationTimestamp))
)
],
in: conf
in: config
)
}
}
@ -676,13 +702,15 @@ public extension SessionUtil {
latestKeyPair: ClosedGroupKeyPair? = nil,
disappearingConfig: DisappearingMessagesConfiguration? = nil,
members: Set<String>? = nil,
admins: Set<String>? = nil
admins: Set<String>? = nil,
using dependencies: Dependencies
) throws {
try SessionUtil.performAndPushChange(
db,
for: .userGroups,
publicKey: getUserHexEncodedPublicKey(db)
) { conf in
publicKey: getUserHexEncodedPublicKey(db, using: dependencies),
using: dependencies
) { config in
try SessionUtil.upsert(
legacyGroups: [
LegacyGroupInfo(
@ -710,20 +738,22 @@ public extension SessionUtil {
}
)
],
in: conf
in: config
)
}
}
static func batchUpdate(
_ db: Database,
disappearingConfigs: [DisappearingMessagesConfiguration]
disappearingConfigs: [DisappearingMessagesConfiguration],
using dependencies: Dependencies
) throws {
try SessionUtil.performAndPushChange(
db,
for: .userGroups,
publicKey: getUserHexEncodedPublicKey(db)
) { conf in
publicKey: getUserHexEncodedPublicKey(db, using: dependencies),
using: dependencies
) { config in
try SessionUtil.upsert(
legacyGroups: disappearingConfigs.map {
LegacyGroupInfo(
@ -731,19 +761,26 @@ public extension SessionUtil {
disappearingConfig: $0
)
},
in: conf
in: config
)
}
}
static func remove(_ db: Database, legacyGroupIds: [String]) throws {
static func remove(
_ db: Database,
legacyGroupIds: [String],
using dependencies: Dependencies
) throws {
guard !legacyGroupIds.isEmpty else { return }
try SessionUtil.performAndPushChange(
db,
for: .userGroups,
publicKey: getUserHexEncodedPublicKey(db)
) { conf in
publicKey: getUserHexEncodedPublicKey(db, using: dependencies),
using: dependencies
) { config in
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
legacyGroupIds.forEach { threadId in
var cGroupId: [CChar] = threadId.cArray.nullTerminated()
@ -753,7 +790,7 @@ public extension SessionUtil {
}
// Remove the volatile info as well
try SessionUtil.remove(db, volatileLegacyGroupIds: legacyGroupIds)
try SessionUtil.remove(db, volatileLegacyGroupIds: legacyGroupIds, using: dependencies)
}
// MARK: -- Group Changes
@ -764,15 +801,17 @@ public extension SessionUtil {
groupIdentityPrivateKey: Data?,
name: String,
tag: Data?,
subkey: Data?
subkey: Data?,
joinedAt: Int64,
using dependencies: Dependencies
) throws {
try SessionUtil.performAndPushChange(
db,
for: .userGroups,
publicKey: getUserHexEncodedPublicKey(db)
) { conf in
guard conf != nil else { throw SessionUtilError.nilConfigObject }
var cGroupId: [CChar] = groupIdentityPublicKey.cArray.nullTerminated()
publicKey: getUserHexEncodedPublicKey(db, using: dependencies),
using: dependencies
) { config in
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
}
}
@ -782,13 +821,15 @@ public extension SessionUtil {
groupIdentityPrivateKey: Data? = nil,
name: String? = nil,
tag: Data? = nil,
subkey: Data? = nil
subkey: Data? = nil,
using dependencies: Dependencies
) throws {
try SessionUtil.performAndPushChange(
db,
for: .userGroups,
publicKey: getUserHexEncodedPublicKey(db)
) { conf in
publicKey: getUserHexEncodedPublicKey(db, using: dependencies),
using: dependencies
) { config in
try SessionUtil.upsert(
groups: [
GroupInfo(
@ -799,14 +840,28 @@ public extension SessionUtil {
subkey: subkey
)
],
in: conf
in: config
)
}
}
static func remove(_ db: Database, groupIds: [String]) throws {
static func remove(
_ db: Database,
groupIds: [String],
using dependencies: Dependencies
) throws {
guard !groupIds.isEmpty else { return }
try SessionUtil.performAndPushChange(
db,
for: .userGroups,
publicKey: getUserHexEncodedPublicKey(db, using: dependencies),
using: dependencies
) { config in
}
// Remove the volatile info as well
try SessionUtil.remove(db, volatileGroupIds: groupIds, using: dependencies)
}
}

View File

@ -20,15 +20,14 @@ internal extension SessionUtil {
static func handleUserProfileUpdate(
_ db: Database,
in conf: UnsafeMutablePointer<config_object>?,
mergeNeedsDump: Bool,
in config: Config?,
latestConfigSentTimestampMs: Int64,
using dependencies: Dependencies = Dependencies()
using dependencies: Dependencies
) throws {
typealias ProfileData = (profileName: String, profilePictureUrl: String?, profilePictureKey: Data?)
guard mergeNeedsDump else { return }
guard conf != nil else { throw SessionUtilError.nilConfigObject }
guard config.needsDump else { return }
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
// A profile must have a name so if this is null then it's invalid and can be ignored
guard let profileNamePtr: UnsafePointer<CChar> = user_profile_get_name(conf) else { return }
@ -114,7 +113,8 @@ internal extension SessionUtil {
threadId: userPublicKey,
threadVariant: .contact,
groupLeaveType: .forced,
calledFromConfigHandling: true
calledFromConfigHandling: true,
using: dependencies
)
}
}
@ -178,9 +178,9 @@ internal extension SessionUtil {
static func update(
profile: Profile,
in conf: UnsafeMutablePointer<config_object>?
in config: Config?
) throws {
guard conf != nil else { throw SessionUtilError.nilConfigObject }
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
// Update the name
var updatedName: [CChar] = profile.name.cArray.nullTerminated()
@ -196,9 +196,9 @@ internal extension SessionUtil {
static func updateNoteToSelf(
priority: Int32? = nil,
disappearingMessagesConfig: DisappearingMessagesConfiguration? = nil,
in conf: UnsafeMutablePointer<config_object>?
in config: Config?
) throws {
guard conf != nil else { throw SessionUtilError.nilConfigObject }
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
if let priority: Int32 = priority {
user_profile_set_nts_priority(conf, priority)
@ -211,9 +211,9 @@ internal extension SessionUtil {
static func updateSettings(
checkForCommunityMessageRequests: Bool? = nil,
in conf: UnsafeMutablePointer<config_object>?
in config: Config?
) throws {
guard conf != nil else { throw SessionUtilError.nilConfigObject }
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
if let blindedMessageRequests: Bool = checkForCommunityMessageRequests {
user_profile_set_blinded_msgreqs(conf, (blindedMessageRequests ? 1 : 0))
@ -224,8 +224,8 @@ internal extension SessionUtil {
// MARK: - Direct Values
extension SessionUtil {
static func rawBlindedMessageRequestValue(in conf: UnsafeMutablePointer<config_object>?) throws -> Int32 {
guard conf != nil else { throw SessionUtilError.nilConfigObject }
static func rawBlindedMessageRequestValue(in config: Config?) throws -> Int32 {
guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject }
return user_profile_get_blinded_msgreqs(conf)
}

View File

@ -52,15 +52,17 @@ public extension QueryInterfaceRequest where RowDecoder: FetchableRecord & Table
@discardableResult
func updateAllAndConfig(
_ db: Database,
_ assignments: ConfigColumnAssignment...
_ assignments: ConfigColumnAssignment...,
using dependencies: Dependencies = Dependencies()
) throws -> Int {
return try updateAllAndConfig(db, assignments)
return try updateAllAndConfig(db, assignments, using: dependencies)
}
@discardableResult
func updateAllAndConfig(
_ db: Database,
_ assignments: [ConfigColumnAssignment]
_ assignments: [ConfigColumnAssignment],
using dependencies: Dependencies = Dependencies()
) throws -> Int {
let targetAssignments: [ColumnAssignment] = assignments.map { $0.assignment }
@ -69,7 +71,7 @@ public extension QueryInterfaceRequest where RowDecoder: FetchableRecord & Table
return try self.updateAll(db, targetAssignments)
}
return try self.updateAndFetchAllAndUpdateConfig(db, assignments).count
return try self.updateAndFetchAllAndUpdateConfig(db, assignments, using: dependencies).count
}
// MARK: -- updateAndFetchAll
@ -77,15 +79,17 @@ public extension QueryInterfaceRequest where RowDecoder: FetchableRecord & Table
@discardableResult
func updateAndFetchAllAndUpdateConfig(
_ db: Database,
_ assignments: ConfigColumnAssignment...
_ assignments: ConfigColumnAssignment...,
using dependencies: Dependencies = Dependencies()
) throws -> [RowDecoder] {
return try updateAndFetchAllAndUpdateConfig(db, assignments)
return try updateAndFetchAllAndUpdateConfig(db, assignments, using: dependencies)
}
@discardableResult
func updateAndFetchAllAndUpdateConfig(
_ db: Database,
_ assignments: [ConfigColumnAssignment]
_ assignments: [ConfigColumnAssignment],
using dependencies: Dependencies = Dependencies()
) throws -> [RowDecoder] {
// First perform the actual updates
let updatedData: [RowDecoder] = try self.updateAndFetchAll(db, assignments.map { $0.assignment })
@ -107,20 +111,20 @@ public extension QueryInterfaceRequest where RowDecoder: FetchableRecord & Table
// Update the config dump state where needed
switch self {
case is QueryInterfaceRequest<Contact>:
return try SessionUtil.updatingContacts(db, updatedData)
return try SessionUtil.updatingContacts(db, updatedData, using: dependencies)
case is QueryInterfaceRequest<Profile>:
return try SessionUtil.updatingProfiles(db, updatedData)
return try SessionUtil.updatingProfiles(db, updatedData, using: dependencies)
case is QueryInterfaceRequest<SessionThread>:
return try SessionUtil.updatingThreads(db, updatedData)
return try SessionUtil.updatingThreads(db, updatedData, using: dependencies)
case is QueryInterfaceRequest<ClosedGroup>:
return try SessionUtil.updatingGroupInfo(db, updatedData)
return try SessionUtil.updatingGroupInfo(db, updatedData, using: dependencies)
case is QueryInterfaceRequest<DisappearingMessagesConfiguration>:
let oneToOneUpdates: [RowDecoder] = try SessionUtil.updatingDisappearingConfigsOneToOne(db, updatedData)
let groupUpdates: [RowDecoder] = try SessionUtil.updatingDisappearingConfigsGroups(db, updatedData)
let oneToOneUpdates: [RowDecoder] = try SessionUtil.updatingDisappearingConfigsOneToOne(db, updatedData, using: dependencies)
let groupUpdates: [RowDecoder] = try SessionUtil.updatingDisappearingConfigsGroups(db, updatedData, using: dependencies)
return (oneToOneUpdates + groupUpdates)

View File

@ -5,39 +5,103 @@ import GRDB
import SessionUtilitiesKit
public extension Database {
func setAndUpdateConfig(_ key: Setting.BoolKey, to newValue: Bool) throws {
try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue))
func setAndUpdateConfig(
_ key: Setting.BoolKey,
to newValue: Bool,
using dependencies: Dependencies
) throws {
try updateConfigIfNeeded(
self,
key: key.rawValue,
updatedSetting: self.setting(key: key, to: newValue),
using: dependencies
)
}
func setAndUpdateConfig(_ key: Setting.DoubleKey, to newValue: Double?) throws {
try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue))
func setAndUpdateConfig(
_ key: Setting.DoubleKey,
to newValue: Double?,
using dependencies: Dependencies
) throws {
try updateConfigIfNeeded(
self,
key: key.rawValue,
updatedSetting: self.setting(key: key, to: newValue),
using: dependencies
)
}
func setAndUpdateConfig(_ key: Setting.IntKey, to newValue: Int?) throws {
try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue))
func setAndUpdateConfig(
_ key: Setting.IntKey,
to newValue: Int?,
using dependencies: Dependencies
) throws {
try updateConfigIfNeeded(
self,
key: key.rawValue,
updatedSetting: self.setting(key: key, to: newValue),
using: dependencies
)
}
func setAndUpdateConfig(_ key: Setting.StringKey, to newValue: String?) throws {
try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue))
func setAndUpdateConfig(
_ key: Setting.StringKey,
to newValue: String?,
using dependencies: Dependencies
) throws {
try updateConfigIfNeeded(
self,
key: key.rawValue,
updatedSetting: self.setting(key: key, to: newValue),
using: dependencies
)
}
func setAndUpdateConfig<T: EnumIntSetting>(_ key: Setting.EnumKey, to newValue: T?) throws {
try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue))
func setAndUpdateConfig<T: EnumIntSetting>(
_ key: Setting.EnumKey,
to newValue: T?,
using dependencies: Dependencies
) throws {
try updateConfigIfNeeded(
self,
key: key.rawValue,
updatedSetting: self.setting(key: key, to: newValue),
using: dependencies
)
}
func setAndUpdateConfig<T: EnumStringSetting>(_ key: Setting.EnumKey, to newValue: T?) throws {
try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue))
func setAndUpdateConfig<T: EnumStringSetting>(
_ key: Setting.EnumKey,
to newValue: T?,
using dependencies: Dependencies
) throws {
try updateConfigIfNeeded(
self,
key: key.rawValue,
updatedSetting: self.setting(key: key, to: newValue),
using: dependencies
)
}
/// Value will be stored as a timestamp in seconds since 1970
func setAndUpdateConfig(_ key: Setting.DateKey, to newValue: Date?) throws {
try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue))
func setAndUpdateConfig(
_ key: Setting.DateKey,
to newValue: Date?,
using dependencies: Dependencies
) throws {
try updateConfigIfNeeded(
self,
key: key.rawValue,
updatedSetting: self.setting(key: key, to: newValue),
using: dependencies
)
}
private func updateConfigIfNeeded(
_ db: Database,
key: String,
updatedSetting: Setting?
updatedSetting: Setting?,
using dependencies: Dependencies
) throws {
// Before we do anything custom make sure the setting should trigger a change
guard SessionUtil.syncedSettings.contains(key) else { return }
@ -53,6 +117,6 @@ public extension Database {
}
}
try SessionUtil.updatingSetting(db, updatedSetting)
try SessionUtil.updatingSetting(db, updatedSetting, using: dependencies)
}
}

View File

@ -29,62 +29,35 @@ public enum SessionUtil {
let obsoleteHashes: [String]
}
// MARK: - Configs
fileprivate static var configStore: Atomic<[ConfigKey: Atomic<UnsafeMutablePointer<config_object>?>]> = Atomic([:])
public static func config(for variant: ConfigDump.Variant, publicKey: String) -> Atomic<UnsafeMutablePointer<config_object>?> {
let key: ConfigKey = ConfigKey(variant: variant, publicKey: publicKey)
return (
SessionUtil.configStore.wrappedValue[key] ??
Atomic(nil)
)
}
// MARK: - Variables
internal static func syncDedupeId(_ publicKey: String) -> String {
return "EnqueueConfigurationSyncJob-\(publicKey)"
}
/// Returns `true` if there is a config which needs to be pushed, but returns `false` if the configs are all up to date or haven't been
/// loaded yet (eg. fresh install)
public static var needsSync: Bool {
configStore
.wrappedValue
.contains { _, atomicConf in
guard atomicConf.wrappedValue != nil else { return false }
return config_needs_push(atomicConf.wrappedValue)
}
}
public static var libSessionVersion: String { String(cString: LIBSESSION_UTIL_VERSION_STR) }
internal static func lastError(_ conf: UnsafeMutablePointer<config_object>?) -> String {
return (conf?.pointee.last_error.map { String(cString: $0) } ?? "Unknown")
}
// MARK: - Loading
public static func clearMemoryState() {
SessionUtil.configStore.mutate { confStore in
confStore.removeAll()
public static func clearMemoryState(using dependencies: Dependencies) {
dependencies.caches.mutate(cache: .sessionUtil) { cache in
cache.removeAll()
}
}
public static func loadState(_ db: Database, using dependencies: Dependencies = Dependencies()) {
public static func loadState(_ db: Database, using dependencies: Dependencies) {
// Ensure we have the ed25519 key and that we haven't already loaded the state before
// we continue
guard
let ed25519SecretKey: [UInt8] = Identity.fetchUserEd25519KeyPair(db)?.secretKey,
SessionUtil.configStore.wrappedValue.isEmpty
dependencies.caches[.sessionUtil].isEmpty
else { return }
// Retrieve the existing dumps from the database
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
let existingDumps: Set<ConfigDump> = ((try? ConfigDump.fetchSet(db)) ?? [])
.sorted { lhs, rhs in lhs.variant.processingOrder < rhs.variant.processingOrder }
.asSet()
let existingDumpVariants: Set<ConfigDump.Variant> = existingDumps
.map { $0.variant }
.asSet()
@ -98,53 +71,51 @@ public enum SessionUtil {
.defaulting(to: [:])
// Create the 'config_object' records for each dump
SessionUtil.configStore.mutate { confStore in
dependencies.caches.mutate(cache: .sessionUtil) { cache in
existingDumps.forEach { dump in
confStore[ConfigKey(variant: dump.variant, publicKey: dump.publicKey)] = Atomic(
try? SessionUtil.loadState(
cache.setConfig(
for: dump.variant,
publicKey: dump.publicKey,
to: try? SessionUtil.loadState(
for: dump.variant,
publicKey: dump.publicKey,
userEd25519SecretKey: ed25519SecretKey,
groupEd25519SecretKey: groupsByKey[dump.publicKey].map { Array($0) },
cachedData: dump.data
cachedData: dump.data,
using: dependencies
)
)
}
missingRequiredVariants.forEach { variant in
confStore[ConfigKey(variant: variant, publicKey: currentUserPublicKey)] = Atomic(
try? SessionUtil.loadState(
cache.setConfig(
for: variant,
publicKey: currentUserPublicKey,
to: try? SessionUtil.loadState(
for: variant,
publicKey: currentUserPublicKey,
userEd25519SecretKey: ed25519SecretKey,
groupEd25519SecretKey: nil,
cachedData: nil
cachedData: nil,
using: dependencies
)
)
}
}
}
public static func storeState(
_ configs: [ConfigDump.Variant: UnsafeMutablePointer<config_object>?],
publicKey: String
) {
SessionUtil.configStore.mutate { confStore in
configs.forEach { variant, conf in
confStore[ConfigKey(variant: variant, publicKey: publicKey)] = Atomic(conf)
}
}
}
private static func loadState(
for variant: ConfigDump.Variant,
publicKey: String,
userEd25519SecretKey: [UInt8],
groupEd25519SecretKey: [UInt8]?,
cachedData: Data?
) throws -> UnsafeMutablePointer<config_object>? {
cachedData: Data?,
using dependencies: Dependencies
) throws -> Config {
// Setup initial variables (including getting the memory address for any cached data)
var conf: UnsafeMutablePointer<config_object>? = nil
var keysConf: UnsafeMutablePointer<config_group_keys>? = nil
var secretKey: [UInt8]? = userEd25519SecretKey
var error: [CChar] = [CChar](repeating: 0, count: 256)
let cachedDump: (data: UnsafePointer<UInt8>, length: Int)? = cachedData?.withUnsafeBytes { unsafeBytes in
return unsafeBytes.baseAddress.map {
@ -156,61 +127,143 @@ public enum SessionUtil {
}
// Try to create the object
var secretKey: [UInt8]? = userEd25519SecretKey
let result: Int32 = {
return try {
switch variant {
case .userProfile:
return user_profile_init(&conf, &secretKey, cachedDump?.data, (cachedDump?.length ?? 0), &error)
return try user_profile_init(
&conf,
&secretKey,
cachedDump?.data,
(cachedDump?.length ?? 0),
&error
)
.returning(
Config.from(conf),
orThrow: "Unable to create \(variant.rawValue) config object",
error: error
)
case .contacts:
return contacts_init(&conf, &secretKey, cachedDump?.data, (cachedDump?.length ?? 0), &error)
return try contacts_init(
&conf,
&secretKey,
cachedDump?.data,
(cachedDump?.length ?? 0),
&error
)
.returning(
Config.from(conf),
orThrow: "Unable to create \(variant.rawValue) config object",
error: error
)
case .convoInfoVolatile:
return convo_info_volatile_init(&conf, &secretKey, cachedDump?.data, (cachedDump?.length ?? 0), &error)
return try convo_info_volatile_init(
&conf,
&secretKey,
cachedDump?.data,
(cachedDump?.length ?? 0),
&error
)
.returning(
Config.from(conf),
orThrow: "Unable to create \(variant.rawValue) config object",
error: error
)
case .userGroups:
return user_groups_init(&conf, &secretKey, cachedDump?.data, (cachedDump?.length ?? 0), &error)
return try user_groups_init(
&conf,
&secretKey,
cachedDump?.data,
(cachedDump?.length ?? 0),
&error
)
.returning(
Config.from(conf),
orThrow: "Unable to create \(variant.rawValue) config object",
error: error
)
case .groupInfo:
return groups_info_init(&conf, &secretKey, &secretKey, cachedDump?.data, (cachedDump?.length ?? 0), &error)
return try groups_info_init(
&conf,
&secretKey,
&secretKey,
cachedDump?.data,
(cachedDump?.length ?? 0),
&error
)
.returning(
Config.from(conf),
orThrow: "Unable to create \(variant.rawValue) config object",
error: error
)
case .groupMembers:
return groups_members_init(&conf, &secretKey, &secretKey, cachedDump?.data, (cachedDump?.length ?? 0), &error)
return try groups_members_init(
&conf,
&secretKey,
&secretKey,
cachedDump?.data,
(cachedDump?.length ?? 0),
&error
)
.returning(
Config.from(conf),
orThrow: "Unable to create \(variant.rawValue) config object",
error: error
)
case .groupKeys:
preconditionFailure()
var identityPublicKey: [UInt8] = Array(Data(hex: publicKey))
var adminSecretKey: [UInt8]? = groupEd25519SecretKey
let infoConfig: Config? = dependencies.caches[.sessionUtil]
.config(for: .groupInfo, publicKey: publicKey)
.wrappedValue
let membersConfig: Config? = dependencies.caches[.sessionUtil]
.config(for: .groupMembers, publicKey: publicKey)
.wrappedValue
guard
case .object(let infoConf) = infoConfig,
case .object(let membersConf) = membersConfig
else {
SNLog("[SessionUtil Error] Unable to create \(variant.rawValue) config object: Group info and member config states not loaded")
throw SessionUtilError.unableToCreateConfigObject
}
return try groups_keys_init(
&keysConf,
&secretKey,
&identityPublicKey,
&adminSecretKey,
infoConf,
membersConf,
cachedDump?.data,
(cachedDump?.length ?? 0),
&error
)
.returning(
Config.from(keysConf, info: infoConf, members: membersConf),
orThrow: "Unable to create \(variant.rawValue) config object",
error: error
)
}
}()
guard result == 0 else {
SNLog("[SessionUtil Error] Unable to create \(variant.rawValue) config object: \(String(cString: error))")
throw SessionUtilError.unableToCreateConfigObject
}
return conf
}
internal static func createDump(
conf: UnsafeMutablePointer<config_object>?,
config: Config?,
for variant: ConfigDump.Variant,
publicKey: String,
timestampMs: Int64
) throws -> ConfigDump? {
guard conf != nil else { throw SessionUtilError.nilConfigObject }
// If it doesn't need a dump then do nothing
guard config_needs_dump(conf) else { return nil }
var dumpResult: UnsafeMutablePointer<UInt8>? = nil
var dumpResultLen: Int = 0
try CExceptionHelper.performSafely {
config_dump(conf, &dumpResult, &dumpResultLen)
}
guard let dumpResult: UnsafeMutablePointer<UInt8> = dumpResult else { return nil }
let dumpData: Data = Data(bytes: dumpResult, count: dumpResultLen)
dumpResult.deallocate()
guard
config.needsDump,
let dumpData: Data = try config?.dump()
else { return nil }
return ConfigDump(
variant: variant,
@ -224,7 +277,8 @@ public enum SessionUtil {
public static func pendingChanges(
_ db: Database,
publicKey: String
publicKey: String,
using dependencies: Dependencies
) throws -> [OutgoingConfResult] {
guard Identity.userExists(db) else { throw SessionUtilError.userDoesNotExist }
@ -245,59 +299,53 @@ public enum SessionUtil {
// data yet (to deal with first launch cases)
return try existingDumpVariants
.compactMap { variant -> OutgoingConfResult? in
try SessionUtil
try dependencies.caches[.sessionUtil]
.config(for: variant, publicKey: publicKey)
.wrappedValue
.map { conf in
.map { config -> OutgoingConfResult? in
// Check if the config needs to be pushed
guard config_needs_push(conf) else { return nil }
guard config.needsPush else { return nil }
var cPushData: UnsafeMutablePointer<config_push_data>!
var result: (data: Data, seqNo: Int64, obsoleteHashes: [String])!
let configCountInfo: String = {
var result: String = "Invalid"
try? CExceptionHelper.performSafely {
switch variant {
case .userProfile: result = "1 profile"
case .contacts: result = "\(contacts_size(conf)) contacts"
case .userGroups: result = "\(user_groups_size(conf)) group conversations"
case .convoInfoVolatile: result = "\(convo_info_volatile_size(conf)) volatile conversations"
switch (config, variant) {
case (_, .userProfile): result = "1 profile"
case (.object(let conf), .contacts):
result = "\(contacts_size(conf)) contacts"
case (.object(let conf), .userGroups):
result = "\(user_groups_size(conf)) group conversations"
case (.object(let conf), .convoInfoVolatile):
result = "\(convo_info_volatile_size(conf)) volatile conversations"
case (_, .groupInfo): result = "1 group info"
case (.object(let conf), .groupMembers): result = ""
case (_, .groupKeys): result = ""
default: break
}
}
return result
}()
do {
try CExceptionHelper.performSafely {
cPushData = config_push(conf)
}
}
do { result = try config.push() }
catch {
SNLog("[libSession] Failed to generate push data for \(variant) config data, size: \(configCountInfo), error: \(error)")
throw error
}
let pushData: Data = Data(
bytes: cPushData.pointee.config,
count: cPushData.pointee.config_len
)
let obsoleteHashes: [String] = [String](
pointer: cPushData.pointee.obsolete,
count: cPushData.pointee.obsolete_len,
defaultValue: []
)
let seqNo: Int64 = cPushData.pointee.seqno
cPushData.deallocate()
return OutgoingConfResult(
message: SharedConfigMessage(
kind: variant.configMessageKind,
seqNo: seqNo,
data: pushData
seqNo: result.seqNo,
data: result.data
),
namespace: variant.namespace,
obsoleteHashes: obsoleteHashes
obsoleteHashes: result.obsoleteHashes
)
}
}
@ -306,22 +354,22 @@ public enum SessionUtil {
public static func markingAsPushed(
message: SharedConfigMessage,
serverHash: String,
publicKey: String
publicKey: String,
using dependencies: Dependencies
) -> ConfigDump? {
return SessionUtil
return dependencies.caches[.sessionUtil]
.config(for: message.kind.configDumpVariant, publicKey: publicKey)
.mutate { conf in
guard conf != nil else { return nil }
.mutate { config -> ConfigDump? in
guard config != nil else { return nil }
// Mark the config as pushed
var cHash: [CChar] = serverHash.cArray.nullTerminated()
config_confirm_pushed(conf, message.seqNo, &cHash)
config?.confirmPushed(seqNo: message.seqNo, hash: serverHash)
// Update the result to indicate whether the config needs to be dumped
guard config_needs_dump(conf) else { return nil }
guard config.needsPush else { return nil }
return try? SessionUtil.createDump(
conf: conf,
config: config,
for: message.kind.configDumpVariant,
publicKey: publicKey,
timestampMs: (message.sentTimestamp.map { Int64($0) } ?? 0)
@ -329,8 +377,11 @@ public enum SessionUtil {
}
}
public static func configHashes(for publicKey: String) -> [String] {
return Storage.shared
public static func configHashes(
for publicKey: String,
using dependencies: Dependencies
) -> [String] {
return dependencies.storage
.read { db -> Set<ConfigDump.Variant> in
guard Identity.userExists(db) else { return [] }
@ -343,21 +394,11 @@ public enum SessionUtil {
.defaulting(to: [])
.map { variant -> [String] in
/// Extract all existing hashes for any dumps associated with the given `publicKey`
guard
let conf = SessionUtil
.config(for: variant, publicKey: publicKey)
.wrappedValue,
let hashList: UnsafeMutablePointer<config_string_list> = config_current_hashes(conf)
else { return [] }
let result: [String] = [String](
pointer: hashList.pointee.value,
count: hashList.pointee.len,
defaultValue: []
)
hashList.deallocate()
return result
dependencies.caches[.sessionUtil]
.config(for: variant, publicKey: publicKey)
.wrappedValue
.map { $0.currentHashes() }
.defaulting(to: [])
}
.reduce([], +)
}
@ -367,7 +408,8 @@ public enum SessionUtil {
public static func handleConfigMessages(
_ db: Database,
messages: [SharedConfigMessage],
publicKey: String
publicKey: String,
using dependencies: Dependencies = Dependencies()
) throws {
guard !messages.isEmpty else { return }
guard !publicKey.isEmpty else { throw MessageReceiverError.noThread }
@ -379,20 +421,11 @@ public enum SessionUtil {
.sorted { lhs, rhs in lhs.key.processingOrder < rhs.key.processingOrder }
.reduce(false) { prevNeedsPush, next -> Bool in
let latestConfigSentTimestampMs: Int64 = Int64(next.value.compactMap { $0.sentTimestamp }.max() ?? 0)
let needsPush: Bool = try SessionUtil
let needsPush: Bool = try dependencies.caches[.sessionUtil]
.config(for: next.key, publicKey: publicKey)
.mutate { conf in
.mutate { config in
// Merge the messages
var mergeHashes: [UnsafePointer<CChar>?] = next.value
.map { message in (message.serverHash ?? "").cArray.nullTerminated() }
.unsafeCopy()
var mergeData: [UnsafePointer<UInt8>?] = next.value
.map { message -> [UInt8] in message.data.bytes }
.unsafeCopy()
var mergeSize: [Int] = next.value.map { $0.data.count }
config_merge(conf, &mergeHashes, &mergeData, &mergeSize, next.value.count)
mergeHashes.forEach { $0?.deallocate() }
mergeData.forEach { $0?.deallocate() }
config?.merge(next.value)
// Apply the updated states to the database
do {
@ -400,56 +433,56 @@ public enum SessionUtil {
case .userProfile:
try SessionUtil.handleUserProfileUpdate(
db,
in: conf,
mergeNeedsDump: config_needs_dump(conf),
latestConfigSentTimestampMs: latestConfigSentTimestampMs
in: config,
latestConfigSentTimestampMs: latestConfigSentTimestampMs,
using: dependencies
)
case .contacts:
try SessionUtil.handleContactsUpdate(
db,
in: conf,
mergeNeedsDump: config_needs_dump(conf),
latestConfigSentTimestampMs: latestConfigSentTimestampMs
in: config,
latestConfigSentTimestampMs: latestConfigSentTimestampMs,
using: dependencies
)
case .convoInfoVolatile:
try SessionUtil.handleConvoInfoVolatileUpdate(
db,
in: conf,
mergeNeedsDump: config_needs_dump(conf)
in: config,
using: dependencies
)
case .userGroups:
try SessionUtil.handleGroupsUpdate(
try SessionUtil.handleUserGroupsUpdate(
db,
in: conf,
mergeNeedsDump: config_needs_dump(conf),
latestConfigSentTimestampMs: latestConfigSentTimestampMs
in: config,
latestConfigSentTimestampMs: latestConfigSentTimestampMs,
using: dependencies
)
case .groupInfo:
try SessionUtil.handleGroupInfoUpdate(
db,
in: conf,
mergeNeedsDump: config_needs_dump(conf),
latestConfigSentTimestampMs: latestConfigSentTimestampMs
in: config,
latestConfigSentTimestampMs: latestConfigSentTimestampMs,
using: dependencies
)
case .groupMembers:
try SessionUtil.handleGroupMembersUpdate(
db,
in: conf,
mergeNeedsDump: config_needs_dump(conf),
latestConfigSentTimestampMs: latestConfigSentTimestampMs
in: config,
latestConfigSentTimestampMs: latestConfigSentTimestampMs,
using: dependencies
)
case .groupKeys:
try SessionUtil.handleGroupKeysUpdate(
db,
in: conf,
mergeNeedsDump: config_needs_dump(conf),
latestConfigSentTimestampMs: latestConfigSentTimestampMs
in: config,
latestConfigSentTimestampMs: latestConfigSentTimestampMs,
using: dependencies
)
}
}
@ -460,7 +493,7 @@ public enum SessionUtil {
// Need to check if the config needs to be dumped (this might have changed
// after handling the merge changes)
guard config_needs_dump(conf) else {
guard config.needsDump else {
try ConfigDump
.filter(
ConfigDump.Columns.variant == next.key &&
@ -471,17 +504,17 @@ public enum SessionUtil {
ConfigDump.Columns.timestampMs.set(to: latestConfigSentTimestampMs)
)
return config_needs_push(conf)
return config.needsPush
}
try SessionUtil.createDump(
conf: conf,
config: config,
for: next.key,
publicKey: publicKey,
timestampMs: latestConfigSentTimestampMs
)?.save(db)
return config_needs_push(conf)
return config.needsPush
}
// Update the 'needsPush' state as needed
@ -498,15 +531,6 @@ public enum SessionUtil {
}
}
// MARK: - Internal Convenience
fileprivate extension SessionUtil {
struct ConfigKey: Hashable {
let variant: ConfigDump.Variant
let publicKey: String
}
}
// MARK: - Convenience
public extension SessionUtil {
@ -543,3 +567,83 @@ public extension SessionUtil {
return String(cString: cFullUrl)
}
}
// MARK: - Convenience
private extension Int32 {
func returning(_ config: SessionUtil.Config?, orThrow description: String, error: [CChar]) throws -> SessionUtil.Config {
guard self == 0, let config: SessionUtil.Config = config else {
SNLog("[SessionUtil Error] \(description): \(String(cString: error))")
throw SessionUtilError.unableToCreateConfigObject
}
return config
}
}
// MARK: - SessionUtil Cache
public extension SessionUtil {
class Cache: SessionUtilCacheType {
public struct Key: Hashable {
let variant: ConfigDump.Variant
let publicKey: String
}
private var configStore: [SessionUtil.Cache.Key: Atomic<SessionUtil.Config?>] = [:]
public var isEmpty: Bool { configStore.isEmpty }
/// Returns `true` if there is a config which needs to be pushed, but returns `false` if the configs are all up to date or haven't been
/// loaded yet (eg. fresh install)
public var needsSync: Bool { configStore.contains { _, atomicConf in atomicConf.needsPush } }
// MARK: - Functions
public func setConfig(for variant: ConfigDump.Variant, publicKey: String, to config: SessionUtil.Config?) {
configStore[Key(variant: variant, publicKey: publicKey)] = Atomic(config)
}
public func config(
for variant: ConfigDump.Variant,
publicKey: String
) -> Atomic<Config?> {
return (
configStore[Key(variant: variant, publicKey: publicKey)] ??
Atomic(nil)
)
}
public func removeAll() {
configStore.removeAll()
}
}
}
public extension Cache {
static let sessionUtil: CacheInfo.Config<SessionUtilCacheType, SessionUtilImmutableCacheType> = CacheInfo.create(
createInstance: { SessionUtil.Cache() },
mutableInstance: { $0 },
immutableInstance: { $0 }
)
}
// MARK: - SessionUtilCacheType
/// This is a read-only version of the `SessionUtil.Cache` designed to avoid unintentionally mutating the instance in a
/// non-thread-safe way
public protocol SessionUtilImmutableCacheType: ImmutableCacheType {
var isEmpty: Bool { get }
var needsSync: Bool { get }
func config(for variant: ConfigDump.Variant, publicKey: String) -> Atomic<SessionUtil.Config?>
}
public protocol SessionUtilCacheType: SessionUtilImmutableCacheType, MutableCacheType {
var isEmpty: Bool { get }
var needsSync: Bool { get }
func setConfig(for variant: ConfigDump.Variant, publicKey: String, to config: SessionUtil.Config?)
func config(for variant: ConfigDump.Variant, publicKey: String) -> Atomic<SessionUtil.Config?>
func removeAll()
}

View File

@ -4,7 +4,7 @@ import Foundation
public enum SessionUtilError: Error {
case unableToCreateConfigObject
case nilConfigObject
case invalidConfigObject
case userDoesNotExist
case getOrConstructFailedUnexpectedly
case processingLoopLimitReached

View File

@ -0,0 +1,254 @@
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import SessionUtil
import SessionUtilitiesKit
public extension SessionUtil {
enum Config {
case object(UnsafeMutablePointer<config_object>)
case groupKeys(
UnsafeMutablePointer<config_group_keys>,
info: UnsafeMutablePointer<config_object>,
members: UnsafeMutablePointer<config_object>
)
// MARK: - Variables
var needsPush: Bool {
switch self {
case .object(let conf): return config_needs_push(conf)
case .groupKeys(let conf, _, _):
var pushResult: UnsafePointer<UInt8>? = nil
var pushResultLen: Int = 0
return groups_keys_pending_config(conf, &pushResult, &pushResultLen)
}
}
var needsDump: Bool {
switch self {
case .object(let conf): return config_needs_push(conf)
case .groupKeys(let conf, _, _): return groups_keys_needs_dump(conf)
}
}
var lastError: String {
switch self {
case .object(let conf): return String(cString: conf.pointee.last_error)
case .groupKeys(let conf, _, _): return String(cString: conf.pointee.last_error)
}
}
// MARK: - Functions
static func from(_ conf: UnsafeMutablePointer<config_object>?) -> Config? {
return conf.map { .object($0) }
}
static func from(
_ conf: UnsafeMutablePointer<config_group_keys>?,
info: UnsafeMutablePointer<config_object>,
members: UnsafeMutablePointer<config_object>
) -> Config? {
return conf.map { .groupKeys($0, info: info, members: members) }
}
func push() throws -> (data: Data, seqNo: Int64, obsoleteHashes: [String]) {
switch self {
case .object(let conf):
var cPushData: UnsafeMutablePointer<config_push_data>!
try CExceptionHelper.performSafely {
cPushData = config_push(conf)
}
let pushData: Data = Data(
bytes: cPushData.pointee.config,
count: cPushData.pointee.config_len
)
let obsoleteHashes: [String] = [String](
pointer: cPushData.pointee.obsolete,
count: cPushData.pointee.obsolete_len,
defaultValue: []
)
let seqNo: Int64 = cPushData.pointee.seqno
cPushData.deallocate()
return (pushData, seqNo, obsoleteHashes)
case .groupKeys(let conf, _, _):
var pushResult: UnsafePointer<UInt8>!
var pushResultLen: Int = 0
guard groups_keys_pending_config(conf, &pushResult, &pushResultLen) else {
return (Data(), 0, [])
}
return (Data(bytes: pushResult, count: pushResultLen), 0, [])
}
}
func confirmPushed(
seqNo: Int64,
hash: String
) {
var cHash: [CChar] = hash.cArray.nullTerminated()
switch self {
case .object(let conf): return config_confirm_pushed(conf, seqNo, &cHash)
case .groupKeys: return // No need to do anything here
}
}
func dump() throws -> Data? {
var dumpResult: UnsafeMutablePointer<UInt8>? = nil
var dumpResultLen: Int = 0
switch self {
case .object(let conf):
try CExceptionHelper.performSafely {
config_dump(conf, &dumpResult, &dumpResultLen)
}
case .groupKeys(let conf, _, _):
try CExceptionHelper.performSafely {
groups_keys_dump(conf, &dumpResult, &dumpResultLen)
}
}
guard let dumpResult: UnsafeMutablePointer<UInt8> = dumpResult else { return nil }
let dumpData: Data = Data(bytes: dumpResult, count: dumpResultLen)
dumpResult.deallocate()
return dumpData
}
func currentHashes() -> [String] {
switch self {
case .object(let conf):
guard let hashList: UnsafeMutablePointer<config_string_list> = config_current_hashes(conf) else {
return []
}
let result: [String] = [String](
pointer: hashList.pointee.value,
count: hashList.pointee.len,
defaultValue: []
)
hashList.deallocate()
return result
case .groupKeys(var conf): return []
}
}
@discardableResult func merge(_ messages: [SharedConfigMessage]) -> Int {
switch self {
case .object(let conf):
var mergeHashes: [UnsafePointer<CChar>?] = messages
.map { message in (message.serverHash ?? "").cArray.nullTerminated() }
.unsafeCopy()
var mergeData: [UnsafePointer<UInt8>?] = messages
.map { message -> [UInt8] in message.data.bytes }
.unsafeCopy()
var mergeSize: [Int] = messages.map { $0.data.count }
let numMerged: Int32 = config_merge(
conf,
&mergeHashes,
&mergeData,
&mergeSize,
messages.count
)
mergeHashes.forEach { $0?.deallocate() }
mergeData.forEach { $0?.deallocate() }
return Int(numMerged)
case .groupKeys(let conf, let infoConf, let membersConf):
return messages
.map { message -> Bool in
var data: [UInt8] = Array(message.data)
return groups_keys_load_message(
conf,
&data,
data.count,
Int64(message.sentTimestamp ?? 0),
infoConf,
membersConf
)
}
.filter { $0 }
.count
}
}
}
}
// MARK: - Optional Convenience
public extension Optional where Wrapped == SessionUtil.Config {
// MARK: - Variables
var needsPush: Bool {
switch self {
case .some(let config): return config.needsPush
case .none: return false
}
}
var needsDump: Bool {
switch self {
case .some(let config): return config.needsDump
case .none: return false
}
}
var lastError: String {
switch self {
case .some(let config): return config.lastError
case .none: return "Nil Config"
}
}
// MARK: - Functions
func confirmPushed(seqNo: Int64, hash: String) {
switch self {
case .some(let config): return config.confirmPushed(seqNo: seqNo, hash: hash)
case .none: return
}
}
func dump() throws -> Data? {
switch self {
case .some(let config): return try config.dump()
case .none: return nil
}
}
func currentHashes() -> [String] {
switch self {
case .some(let config): return config.currentHashes()
case .none: return []
}
}
func merge(_ messages: [SharedConfigMessage]) {
switch self {
case .some(let config): config.merge(messages)
case .none: return
}
}
}
// MARK: - Atomic Convenience
public extension Atomic where Value == Optional<SessionUtil.Config> {
var needsPush: Bool { return wrappedValue.needsPush }
var needsDump: Bool { return wrappedValue.needsDump }
}

View File

@ -268,21 +268,22 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat
}
/// This method marks a thread as read and depending on the target may also update the interactions within a thread as read
public func markAsRead(target: ReadTarget) {
public func markAsRead(target: ReadTarget, using dependencies: Dependencies = Dependencies()) {
// Store the logic to mark a thread as read (to paths need to run this)
let threadId: String = self.threadId
let threadWasMarkedUnread: Bool? = self.threadWasMarkedUnread
let markThreadAsReadIfNeeded: () -> () = {
let markThreadAsReadIfNeeded: (Dependencies) -> () = { dependencies in
// Only make this change if needed (want to avoid triggering a thread update
// if not needed)
guard threadWasMarkedUnread == true else { return }
Storage.shared.writeAsync { db in
dependencies.storage.writeAsync { db in
try SessionThread
.filter(id: threadId)
.updateAllAndConfig(
db,
SessionThread.Columns.markedAsUnread.set(to: false)
SessionThread.Columns.markedAsUnread.set(to: false),
using: dependencies
)
}
}
@ -290,7 +291,7 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat
// Determine what we want to mark as read
switch target {
// Only mark the thread as read
case .thread: markThreadAsReadIfNeeded()
case .thread: markThreadAsReadIfNeeded(dependencies)
// We want to mark both the thread and interactions as read
case .threadAndInteractions(let interactionId):
@ -299,7 +300,7 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat
let targetInteractionId: Int64 = (interactionId ?? self.interactionId)
else {
// No unread interactions so just mark the thread as read if needed
markThreadAsReadIfNeeded()
markThreadAsReadIfNeeded(dependencies)
return
}
@ -308,8 +309,8 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat
let threadIsBlocked: Bool? = self.threadIsBlocked
let threadIsMessageRequest: Bool? = self.threadIsMessageRequest
Storage.shared.writeAsync { db in
markThreadAsReadIfNeeded()
dependencies.storage.writeAsync { db in
markThreadAsReadIfNeeded(dependencies)
try Interaction.markAsRead(
db,
@ -323,24 +324,26 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat
threadVariant: threadVariant,
isBlocked: threadIsBlocked,
isMessageRequest: threadIsMessageRequest
)
),
using: dependencies
)
}
}
}
/// This method will mark a thread as read
public func markAsUnread() {
public func markAsUnread(using dependencies: Dependencies = Dependencies()) {
guard self.threadWasMarkedUnread != true else { return }
let threadId: String = self.threadId
Storage.shared.writeAsync { db in
dependencies.storage.writeAsync { db in
try SessionThread
.filter(id: threadId)
.updateAllAndConfig(
db,
SessionThread.Columns.markedAsUnread.set(to: true)
SessionThread.Columns.markedAsUnread.set(to: true),
using: dependencies
)
}
}

View File

@ -12,8 +12,11 @@ public extension Identity {
/// One case which can happen is if the app crashed during onboarding the user can be left in an invalid
/// state (ie. with no display name) - the user would be asked to enter one on a subsequent launch to
/// resolve the invalid state
static func userCompletedRequiredOnboarding(_ db: Database? = nil) -> Bool {
Identity.userExists(db) &&
!Profile.fetchOrCreateCurrentUser(db).name.isEmpty
static func userCompletedRequiredOnboarding(
_ db: Database? = nil,
using dependencies: Dependencies = Dependencies()
) -> Bool {
Identity.userExists(db, using: dependencies) &&
!Profile.fetchOrCreateCurrentUser(db, using: dependencies).name.isEmpty
}
}

View File

@ -378,7 +378,7 @@ class MessageSendJobSpec: QuickSpec {
)
expect(mockJobRunner)
.to(call(.exactly(times: 1), matchingParameters: true) {
.to(call(.exactly(times: 1), matchingParameters: .all) {
$0.insert(
any(),
job: Job(

View File

@ -17,6 +17,7 @@ extension LibSessionSpec {
context("GROUP_INFO") {
// MARK: - generates config correctly
it("generates config correctly") {
let userSeed: Data = Data(hex: "0123456789abcdef0123456789abcdef")
let seed: Data = Data(
hex: "0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210"
)

View File

@ -1,24 +1,103 @@
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import Foundation
import GRDB
import Sodium
import SessionUtil
import SessionUtilitiesKit
import SessionMessagingKit
import Quick
import Nimble
@testable import SessionMessagingKit
class SessionUtilSpec: QuickSpec {
// MARK: - Spec
override func spec() {
var mockStorage: Storage!
var mockCrypto: MockCrypto!
var mockCaches: MockCaches!
var mockGeneralCache: MockGeneralCache!
var mockSessionUtilCache: MockSessionUtilCache!
var dependencies: Dependencies!
var createGroupOutput: (identityKeyPair: KeyPair, group: ClosedGroup, members: [GroupMember])!
var userGroupsConfig: SessionUtil.Config!
describe("SessionUtil") {
// MARK: - Parsing URLs
// MARK: - Configuration
beforeEach {
mockStorage = SynchronousStorage(
customWriter: try! DatabaseQueue(),
customMigrationTargets: [
SNUtilitiesKit.self,
SNMessagingKit.self
]
)
mockCrypto = MockCrypto()
mockCaches = MockCaches()
mockGeneralCache = MockGeneralCache()
mockSessionUtilCache = MockSessionUtilCache()
dependencies = Dependencies(
storage: mockStorage,
crypto: mockCrypto,
caches: mockCaches,
dateNow: Date(timeIntervalSince1970: 1234567890),
forceSynchronous: true
)
mockCaches[.general] = mockGeneralCache
mockCaches[.sessionUtil] = mockSessionUtilCache
mockStorage.write { db in
try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db)
try Identity(variant: .x25519PrivateKey, data: Data(hex: TestConstants.privateKey)).insert(db)
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db)
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db)
}
mockCrypto
.when { [dependencies = dependencies!] crypto in
crypto.generate(
.ed25519KeyPair(
seed: any(),
using: dependencies
)
)
}
.thenReturn(
KeyPair(
publicKey: Data.data(
fromHex: "cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece"
)!.bytes,
secretKey: Data.data(
fromHex: "0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210" +
"cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece"
)!.bytes
)
)
mockGeneralCache.when { $0.encodedPublicKey }.thenReturn("05\(TestConstants.publicKey)")
mockSessionUtilCache
.when { $0.setConfig(for: any(), publicKey: any(), to: any()) }
.thenReturn(())
}
afterEach {
mockStorage = nil
mockCrypto = nil
mockCaches = nil
mockGeneralCache = nil
dependencies = nil
createGroupOutput = nil
userGroupsConfig = nil
}
// MARK: - when parsing a community url
context("when parsing a community url") {
// MARK: -- handles the example urls correctly
it("handles the example urls correctly") {
let validUrls: [String] = [
[
@ -82,6 +161,7 @@ class SessionUtilSpec: QuickSpec {
expect(processedPublicKeys).to(equal(expectedPublicKeys))
}
// MARK: -- handles the r prefix if present
it("handles the r prefix if present") {
let info = SessionUtil.parseCommunity(
url: [
@ -95,6 +175,7 @@ class SessionUtilSpec: QuickSpec {
expect(info?.publicKey).to(equal("658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"))
}
// MARK: -- fails if no scheme is provided
it("fails if no scheme is provided") {
let info = SessionUtil.parseCommunity(
url: [
@ -108,6 +189,7 @@ class SessionUtilSpec: QuickSpec {
expect(info?.publicKey).to(beNil())
}
// MARK: -- fails if there is no room
it("fails if there is no room") {
let info = SessionUtil.parseCommunity(
url: [
@ -121,6 +203,7 @@ class SessionUtilSpec: QuickSpec {
expect(info?.publicKey).to(beNil())
}
// MARK: -- fails if there is no public key parameter
it("fails if there is no public key parameter") {
let info = SessionUtil.parseCommunity(
url: "https://sessionopengroup.co/r/main"
@ -131,6 +214,7 @@ class SessionUtilSpec: QuickSpec {
expect(info?.publicKey).to(beNil())
}
// MARK: -- fails if the public key parameter is not 64 characters
it("fails if the public key parameter is not 64 characters") {
let info = SessionUtil.parseCommunity(
url: [
@ -144,6 +228,7 @@ class SessionUtilSpec: QuickSpec {
expect(info?.publicKey).to(beNil())
}
// MARK: -- fails if the public key parameter is not a hex string
it("fails if the public key parameter is not a hex string") {
let info = SessionUtil.parseCommunity(
url: [
@ -157,6 +242,7 @@ class SessionUtilSpec: QuickSpec {
expect(info?.publicKey).to(beNil())
}
// MARK: -- maintains the same TLS
it("maintains the same TLS") {
let server1 = SessionUtil.parseCommunity(
url: [
@ -175,6 +261,7 @@ class SessionUtilSpec: QuickSpec {
expect(server2).to(equal("https://sessionopengroup.co"))
}
// MARK: -- maintains the same port
it("maintains the same port") {
let server1 = SessionUtil.parseCommunity(
url: [
@ -194,19 +281,380 @@ class SessionUtilSpec: QuickSpec {
}
}
// MARK: - Generating URLs
// MARK: - when generating a url
context("when generating a url") {
// MARK: -- generates the url correctly
it("generates the url correctly") {
expect(SessionUtil.communityUrlFor(server: "server", roomToken: "room", publicKey: "f8fec9b701000000ffffffff0400008000000000000000000000000000000000"))
.to(equal("server/room?public_key=f8fec9b701000000ffffffff0400008000000000000000000000000000000000"))
}
// MARK: -- maintains the casing provided
it("maintains the casing provided") {
expect(SessionUtil.communityUrlFor(server: "SeRVer", roomToken: "RoOM", publicKey: "f8fec9b701000000ffffffff0400008000000000000000000000000000000000"))
.to(equal("SeRVer/RoOM?public_key=f8fec9b701000000ffffffff0400008000000000000000000000000000000000"))
}
}
// MARK: - when creating a group
context("when creating a group") {
beforeEach {
var userGroupsConf: UnsafeMutablePointer<config_object>!
var secretKey: [UInt8] = Array(Data(hex: TestConstants.edSecretKey))
_ = user_groups_init(&userGroupsConf, &secretKey, nil, 0, nil)
userGroupsConfig = .object(userGroupsConf)
mockSessionUtilCache
.when { $0.config(for: .userGroups, publicKey: any()) }
.thenReturn(Atomic(userGroupsConfig))
}
// MARK: -- throws when there is no user ed25519 keyPair
it("throws when there is no user ed25519 keyPair") {
var resultError: Error? = nil
mockStorage.write { db in
try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
try Identity.filter(id: .ed25519SecretKey).deleteAll(db)
do {
_ = try SessionUtil.createGroup(
db,
name: "Testname",
displayPictureUrl: nil,
displayPictureFilename: nil,
displayPictureEncryptionKey: nil,
members: [],
admins: [],
using: dependencies
)
}
catch { resultError = error }
}
expect(resultError).to(matchError(MessageSenderError.noKeyPair))
}
// MARK: -- throws when it fails to generate a new identity ed25519 keyPair
it("throws when it fails to generate a new identity ed25519 keyPair") {
var resultError: Error? = nil
mockCrypto
.when { [dependencies = dependencies!] crypto in
crypto.generate(
.ed25519KeyPair(
seed: any(),
using: dependencies
)
)
}
.thenReturn(nil)
mockStorage.write { db in
do {
_ = try SessionUtil.createGroup(
db,
name: "Testname",
displayPictureUrl: nil,
displayPictureFilename: nil,
displayPictureEncryptionKey: nil,
members: [],
admins: [],
using: dependencies
)
}
catch { resultError = error }
}
expect(resultError).to(matchError(MessageSenderError.noKeyPair))
}
// MARK: -- throws when given an invalid member id
it("throws when given an invalid member id") {
var resultError: Error? = nil
mockStorage.write { db in
do {
_ = try SessionUtil.createGroup(
db,
name: "Testname",
displayPictureUrl: nil,
displayPictureFilename: nil,
displayPictureEncryptionKey: nil,
members: [(
id: "123456",
profile: Profile(
id: "123456",
name: "",
lastNameUpdate: 0,
lastProfilePictureUpdate: 0,
lastBlocksCommunityMessageRequests: 0
)
)],
admins: [],
using: dependencies
)
}
catch { resultError = error }
}
expect(resultError).to(matchError(
NSError(
domain: "cpp_exception",
code: -2,
userInfo: [
NSLocalizedDescriptionKey: "Invalid session ID: expected 66 hex digits starting with 05; got 123456"
]
)
))
}
// MARK: -- returns the correct identity keyPair
it("returns the correct identity keyPair") {
createGroupOutput = mockStorage.write(using: dependencies) { db in
try SessionUtil.createGroup(
db,
name: "Testname",
displayPictureUrl: nil,
displayPictureFilename: nil,
displayPictureEncryptionKey: nil,
members: [],
admins: [],
using: dependencies
)
}
expect(createGroupOutput.identityKeyPair.publicKey.toHexString())
.to(equal("cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece"))
expect(createGroupOutput.identityKeyPair.secretKey.toHexString())
.to(equal(
"0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210" +
"cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece"
))
}
// MARK: -- returns a closed group with the correct data set
it("returns a closed group with the correct data set") {
createGroupOutput = mockStorage.write(using: dependencies) { db in
try SessionUtil.createGroup(
db,
name: "Testname",
displayPictureUrl: "TestUrl",
displayPictureFilename: "TestFilename",
displayPictureEncryptionKey: Data([1, 2, 3]),
members: [],
admins: [],
using: dependencies
)
}
expect(createGroupOutput.group.threadId)
.to(equal("03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece"))
expect(createGroupOutput.group.groupIdentityPrivateKey?.toHexString())
.to(equal(
"0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210" +
"cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece"
))
expect(createGroupOutput.group.name).to(equal("Testname"))
expect(createGroupOutput.group.displayPictureUrl).to(equal("TestUrl"))
expect(createGroupOutput.group.displayPictureFilename).to(equal("TestFilename"))
expect(createGroupOutput.group.displayPictureEncryptionKey).to(equal(Data([1, 2, 3])))
expect(createGroupOutput.group.formationTimestamp).to(equal(1234567890))
expect(createGroupOutput.group.approved).to(beTrue())
}
// MARK: -- returns the members setup correctly
it("returns the members setup correctly") {
createGroupOutput = mockStorage.write(using: dependencies) { db in
try SessionUtil.createGroup(
db,
name: "Testname",
displayPictureUrl: nil,
displayPictureFilename: nil,
displayPictureEncryptionKey: nil,
members: [(
id: "051111111111111111111111111111111111111111111111111111111111111111",
profile: Profile(
id: "051111111111111111111111111111111111111111111111111111111111111111",
name: "TestName",
lastNameUpdate: 0,
profilePictureUrl: "testUrl",
profileEncryptionKey: Data([1, 2, 3]),
lastProfilePictureUpdate: 0,
lastBlocksCommunityMessageRequests: 0
)
)],
admins: [(
id: "05\(TestConstants.publicKey)",
profile: Profile(
id: "05\(TestConstants.publicKey)",
name: "TestName2",
lastNameUpdate: 0,
lastProfilePictureUpdate: 0,
lastBlocksCommunityMessageRequests: 0
)
)],
using: dependencies
)
}
expect(createGroupOutput.members.count).to(equal(2))
expect(createGroupOutput.members.map { $0.groupId })
.to(equal([
"03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece",
"03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece",
]))
expect(createGroupOutput.members.map { $0.profileId }.asSet())
.to(equal([
"051111111111111111111111111111111111111111111111111111111111111111",
"05\(TestConstants.publicKey)"
]))
expect(createGroupOutput.members.map { $0.role }.asSet())
.to(equal([
.standard,
.admin
]))
expect(createGroupOutput.members.map { $0.isHidden }.asSet())
.to(equal([
false,
false
]))
}
// MARK: -- adds the current user as an admin when not provided
it("adds the current user as an admin when not provided") {
createGroupOutput = mockStorage.write(using: dependencies) { db in
try SessionUtil.createGroup(
db,
name: "Testname",
displayPictureUrl: nil,
displayPictureFilename: nil,
displayPictureEncryptionKey: nil,
members: [(
id: "051111111111111111111111111111111111111111111111111111111111111111",
profile: Profile(
id: "051111111111111111111111111111111111111111111111111111111111111111",
name: "TestName",
lastNameUpdate: 0,
lastProfilePictureUpdate: 0,
lastBlocksCommunityMessageRequests: 0
)
)],
admins: [],
using: dependencies
)
}
expect(createGroupOutput.members.map { $0.groupId })
.to(contain("03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece"))
expect(createGroupOutput.members.map { $0.profileId })
.to(contain("05\(TestConstants.publicKey)"))
expect(createGroupOutput.members.map { $0.role }).to(contain(.admin))
}
// MARK: -- handles members without profile data correctly
it("handles members without profile data correctly") {
createGroupOutput = mockStorage.write(using: dependencies) { db in
try SessionUtil.createGroup(
db,
name: "Testname",
displayPictureUrl: nil,
displayPictureFilename: nil,
displayPictureEncryptionKey: nil,
members: [(
id: "051111111111111111111111111111111111111111111111111111111111111111",
profile: nil
)],
admins: [],
using: dependencies
)
}
expect(createGroupOutput.members.count).to(equal(2))
expect(createGroupOutput.members.map { $0.groupId })
.to(contain("03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece"))
expect(createGroupOutput.members.map { $0.profileId })
.to(contain("051111111111111111111111111111111111111111111111111111111111111111"))
expect(createGroupOutput.members.map { $0.role }).to(contain(.standard))
}
// MARK: -- stores the config states in the cache correctly
it("stores the config states in the cache correctly") {
createGroupOutput = mockStorage.write(using: dependencies) { db in
try SessionUtil.createGroup(
db,
name: "Testname",
displayPictureUrl: nil,
displayPictureFilename: nil,
displayPictureEncryptionKey: nil,
members: [(
id: "051111111111111111111111111111111111111111111111111111111111111111",
profile: nil
)],
admins: [],
using: dependencies
)
}
expect(mockSessionUtilCache).to(call(.exactly(times: 3)) {
$0.setConfig(for: any(), publicKey: any(), to: any())
})
expect(mockSessionUtilCache)
.to(call(matchingParameters: .atLeast(2)) {
$0.setConfig(
for: .groupInfo,
publicKey: "03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece",
to: any()
)
})
expect(mockSessionUtilCache)
.to(call(matchingParameters: .atLeast(2)) {
$0.setConfig(
for: .groupMembers,
publicKey: "03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece",
to: any()
)
})
expect(mockSessionUtilCache)
.to(call(matchingParameters: .atLeast(2)) {
$0.setConfig(
for: .groupKeys,
publicKey: "03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece",
to: any()
)
})
}
// MARK: -- saves config dumps for the stored configs
it("saves config dumps for the stored configs") {
createGroupOutput = mockStorage.write(using: dependencies) { db in
try SessionUtil.createGroup(
db,
name: "Testname",
displayPictureUrl: nil,
displayPictureFilename: nil,
displayPictureEncryptionKey: nil,
members: [(
id: "051111111111111111111111111111111111111111111111111111111111111111",
profile: nil
)],
admins: [],
using: dependencies
)
}
let result: [ConfigDump]? = mockStorage.read(using: dependencies) { db in
try ConfigDump.fetchAll(db)
}
expect(result?.map { $0.variant }.asSet())
.to(equal([.groupInfo, .groupKeys, .groupMembers]))
expect(result?.map { $0.publicKey }.asSet())
.to(equal(["03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece"]))
expect(result?.map { $0.timestampMs }.asSet())
.to(equal([1234567890000]))
}
}
}
}
}

View File

@ -223,7 +223,7 @@ class SOGSMessageSpec: QuickSpec {
_ = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData)
expect(mockCrypto)
.to(call(matchingParameters: true) {
.to(call(matchingParameters: .all) {
$0.verify(
.signature(
message: Data(base64Encoded: "VGVzdERhdGE=")!.bytes,
@ -268,7 +268,7 @@ class SOGSMessageSpec: QuickSpec {
_ = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData)
expect(mockCrypto)
.to(call(matchingParameters: true) {
.to(call(matchingParameters: .all) {
$0.verify(
.signatureEd25519(
Data(base64Encoded: "VGVzdFNpZ25hdHVyZQ==")!,

View File

@ -45,10 +45,10 @@ class OpenGroupAPISpec: QuickSpec {
)
mockStorage.write { db in
try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: TestConstants.publicKey)!).insert(db)
try Identity(variant: .x25519PrivateKey, data: Data.data(fromHex: TestConstants.privateKey)!).insert(db)
try Identity(variant: .ed25519PublicKey, data: Data.data(fromHex: TestConstants.edPublicKey)!).insert(db)
try Identity(variant: .ed25519SecretKey, data: Data.data(fromHex: TestConstants.edSecretKey)!).insert(db)
try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db)
try Identity(variant: .x25519PrivateKey, data: Data(hex: TestConstants.privateKey)).insert(db)
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db)
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db)
try OpenGroup(
server: "testServer",
@ -76,8 +76,8 @@ class OpenGroupAPISpec: QuickSpec {
}
.thenReturn(
KeyPair(
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
publicKey: Data(hex: TestConstants.publicKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
)
)
mockCrypto

View File

@ -146,10 +146,10 @@ class OpenGroupManagerSpec: QuickSpec {
)
mockStorage.write { db in
try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: TestConstants.publicKey)!).insert(db)
try Identity(variant: .x25519PrivateKey, data: Data.data(fromHex: TestConstants.privateKey)!).insert(db)
try Identity(variant: .ed25519PublicKey, data: Data.data(fromHex: TestConstants.edPublicKey)!).insert(db)
try Identity(variant: .ed25519SecretKey, data: Data.data(fromHex: TestConstants.edSecretKey)!).insert(db)
try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db)
try Identity(variant: .x25519PrivateKey, data: Data(hex: TestConstants.privateKey)).insert(db)
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db)
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db)
try testGroupThread.insert(db)
try testOpenGroup.insert(db)
@ -170,8 +170,8 @@ class OpenGroupManagerSpec: QuickSpec {
}
.thenReturn(
KeyPair(
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
publicKey: Data(hex: TestConstants.publicKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
)
)
mockCrypto
@ -337,7 +337,7 @@ class OpenGroupManagerSpec: QuickSpec {
openGroupManager.startPolling(using: dependencies)
expect(mockOGMCache)
.to(call(matchingParameters: true) {
.to(call(matchingParameters: .all) {
$0.pollers = [
"testserver": OpenGroupAPI.Poller(for: "testserver"),
"testserver1": OpenGroupAPI.Poller(for: "testserver1")
@ -350,7 +350,7 @@ class OpenGroupManagerSpec: QuickSpec {
openGroupManager.startPolling(using: dependencies)
expect(mockOGMCache)
.to(call(matchingParameters: true) { $0.isPolling = true })
.to(call(matchingParameters: .all) { $0.isPolling = true })
}
// MARK: -- does nothing if already polling
@ -389,14 +389,14 @@ class OpenGroupManagerSpec: QuickSpec {
it("removes all pollers") {
openGroupManager.stopPolling(using: dependencies)
expect(mockOGMCache).to(call(matchingParameters: true) { $0.pollers = [:] })
expect(mockOGMCache).to(call(matchingParameters: .all) { $0.pollers = [:] })
}
// MARK: - updates the isPolling flag
it("updates the isPolling flag") {
openGroupManager.stopPolling(using: dependencies)
expect(mockOGMCache).to(call(matchingParameters: true) { $0.isPolling = false })
expect(mockOGMCache).to(call(matchingParameters: .all) { $0.isPolling = false })
}
}
@ -853,7 +853,7 @@ class OpenGroupManagerSpec: QuickSpec {
.sinkAndStore(in: &disposables)
expect(mockOGMCache)
.to(call(matchingParameters: true) {
.to(call(matchingParameters: .all) {
$0.pollers = ["testserver": OpenGroupAPI.Poller(for: "testserver")]
})
}
@ -1036,7 +1036,7 @@ class OpenGroupManagerSpec: QuickSpec {
)
}
expect(mockOGMCache).to(call(matchingParameters: true) { $0.pollers = [:] })
expect(mockOGMCache).to(call(matchingParameters: .all) { $0.pollers = [:] })
}
// MARK: ---- removes the open group
@ -1615,7 +1615,7 @@ class OpenGroupManagerSpec: QuickSpec {
}
expect(mockOGMCache)
.to(call(matchingParameters: true) {
.to(call(matchingParameters: .all) {
$0.pollers = ["testserver": OpenGroupAPI.Poller(for: "testserver")]
})
}
@ -2698,8 +2698,8 @@ class OpenGroupManagerSpec: QuickSpec {
mockStorage.write { db in
let otherKey: String = TestConstants.publicKey.replacingOccurrences(of: "7", with: "6")
try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: otherKey)!).save(db)
try Identity(variant: .x25519PrivateKey, data: Data.data(fromHex: TestConstants.privateKey)!).save(db)
try Identity(variant: .x25519PublicKey, data: Data(hex: otherKey)).save(db)
try Identity(variant: .x25519PrivateKey, data: Data(hex: TestConstants.privateKey)).save(db)
}
expect(
@ -2724,8 +2724,8 @@ class OpenGroupManagerSpec: QuickSpec {
isHidden: false
).insert(db)
try Identity(variant: .ed25519PublicKey, data: Data.data(fromHex: otherKey)!).save(db)
try Identity(variant: .ed25519SecretKey, data: Data.data(fromHex: TestConstants.edSecretKey)!).save(db)
try Identity(variant: .ed25519PublicKey, data: Data(hex: otherKey)).save(db)
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).save(db)
}
expect(
@ -2753,8 +2753,8 @@ class OpenGroupManagerSpec: QuickSpec {
}
.thenReturn(
KeyPair(
publicKey: Data.data(fromHex: otherKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
publicKey: Data(hex: otherKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
)
)
mockStorage.write { db in
@ -2801,8 +2801,8 @@ class OpenGroupManagerSpec: QuickSpec {
mockStorage.write { db in
let otherKey: String = TestConstants.publicKey.replacingOccurrences(of: "7", with: "6")
try Identity(variant: .ed25519PublicKey, data: Data.data(fromHex: otherKey)!).save(db)
try Identity(variant: .ed25519SecretKey, data: Data.data(fromHex: TestConstants.edSecretKey)!).save(db)
try Identity(variant: .ed25519PublicKey, data: Data(hex: otherKey)).save(db)
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).save(db)
}
expect(
@ -2827,10 +2827,10 @@ class OpenGroupManagerSpec: QuickSpec {
isHidden: false
).insert(db)
try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: otherKey)!).save(db)
try Identity(variant: .x25519PrivateKey, data: Data.data(fromHex: TestConstants.privateKey)!).save(db)
try Identity(variant: .ed25519PublicKey, data: Data.data(fromHex: TestConstants.publicKey)!).save(db)
try Identity(variant: .ed25519SecretKey, data: Data.data(fromHex: TestConstants.edSecretKey)!).save(db)
try Identity(variant: .x25519PublicKey, data: Data(hex: otherKey)).save(db)
try Identity(variant: .x25519PrivateKey, data: Data(hex: TestConstants.privateKey)).save(db)
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.publicKey)).save(db)
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).save(db)
}
expect(
@ -2858,8 +2858,8 @@ class OpenGroupManagerSpec: QuickSpec {
}
.thenReturn(
KeyPair(
publicKey: Data.data(fromHex: otherKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
publicKey: Data(hex: otherKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
)
)
mockStorage.write { db in
@ -2870,10 +2870,10 @@ class OpenGroupManagerSpec: QuickSpec {
isHidden: false
).insert(db)
try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: TestConstants.publicKey)!).save(db)
try Identity(variant: .x25519PrivateKey, data: Data.data(fromHex: TestConstants.privateKey)!).save(db)
try Identity(variant: .ed25519PublicKey, data: Data.data(fromHex: TestConstants.publicKey)!).save(db)
try Identity(variant: .ed25519SecretKey, data: Data.data(fromHex: TestConstants.edSecretKey)!).save(db)
try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).save(db)
try Identity(variant: .x25519PrivateKey, data: Data(hex: TestConstants.privateKey)).save(db)
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.publicKey)).save(db)
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).save(db)
}
expect(
@ -2945,8 +2945,8 @@ class OpenGroupManagerSpec: QuickSpec {
}
.thenReturn(
KeyPair(
publicKey: Data.data(fromHex: otherKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
publicKey: Data(hex: otherKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
)
)
@ -2976,8 +2976,8 @@ class OpenGroupManagerSpec: QuickSpec {
}
.thenReturn(
KeyPair(
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
publicKey: Data(hex: TestConstants.publicKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
)
)
mockStorage.write { db in
@ -2988,10 +2988,10 @@ class OpenGroupManagerSpec: QuickSpec {
isHidden: false
).insert(db)
try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: otherKey)!).save(db)
try Identity(variant: .x25519PrivateKey, data: Data.data(fromHex: TestConstants.privateKey)!).save(db)
try Identity(variant: .ed25519PublicKey, data: Data.data(fromHex: TestConstants.publicKey)!).save(db)
try Identity(variant: .ed25519SecretKey, data: Data.data(fromHex: TestConstants.edSecretKey)!).save(db)
try Identity(variant: .x25519PublicKey, data: Data(hex: otherKey)).save(db)
try Identity(variant: .x25519PrivateKey, data: Data(hex: TestConstants.privateKey)).save(db)
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.publicKey)).save(db)
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).save(db)
}
expect(
@ -3018,8 +3018,8 @@ class OpenGroupManagerSpec: QuickSpec {
}
.thenReturn(
KeyPair(
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
publicKey: Data(hex: TestConstants.publicKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
)
)
mockStorage.write { db in
@ -3032,10 +3032,10 @@ class OpenGroupManagerSpec: QuickSpec {
isHidden: false
).insert(db)
try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: TestConstants.publicKey)!).save(db)
try Identity(variant: .x25519PrivateKey, data: Data.data(fromHex: TestConstants.privateKey)!).save(db)
try Identity(variant: .ed25519PublicKey, data: Data.data(fromHex: otherKey)!).save(db)
try Identity(variant: .ed25519SecretKey, data: Data.data(fromHex: TestConstants.edSecretKey)!).save(db)
try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).save(db)
try Identity(variant: .x25519PrivateKey, data: Data(hex: TestConstants.privateKey)).save(db)
try Identity(variant: .ed25519PublicKey, data: Data(hex: otherKey)).save(db)
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).save(db)
}
expect(
@ -3088,7 +3088,7 @@ class OpenGroupManagerSpec: QuickSpec {
let publisher = OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies)
expect(mockOGMCache)
.to(call(matchingParameters: true) {
.to(call(matchingParameters: .all) {
$0.defaultRoomsPublisher = publisher
})
}
@ -3188,7 +3188,7 @@ class OpenGroupManagerSpec: QuickSpec {
expect(error)
.to(matchError(HTTPError.parsingFailed))
expect(mockOGMCache)
.to(call(matchingParameters: true) {
.to(call(matchingParameters: .all) {
$0.defaultRoomsPublisher = nil
})
}
@ -3242,7 +3242,7 @@ class OpenGroupManagerSpec: QuickSpec {
.sinkAndStore(in: &disposables)
expect(mockUserDefaults)
.to(call(matchingParameters: true) {
.to(call(matchingParameters: .all) {
$0.set(
testDate,
forKey: SNUserDefaults.Date.lastOpenGroupImageUpdate.rawValue
@ -3351,7 +3351,7 @@ class OpenGroupManagerSpec: QuickSpec {
.sinkAndStore(in: &disposables)
expect(mockUserDefaults)
.toNot(call(matchingParameters: true) {
.toNot(call(matchingParameters: .all) {
$0.set(
dependencies.dateNow,
forKey: SNUserDefaults.Date.lastOpenGroupImageUpdate.rawValue
@ -3372,7 +3372,7 @@ class OpenGroupManagerSpec: QuickSpec {
publisher.sinkAndStore(in: &disposables)
expect(mockOGMCache)
.to(call(matchingParameters: true) {
.to(call(matchingParameters: .all) {
$0.groupImagePublishers = [OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): publisher]
})
}
@ -3433,7 +3433,7 @@ class OpenGroupManagerSpec: QuickSpec {
.sinkAndStore(in: &disposables)
expect(mockUserDefaults)
.to(call(matchingParameters: true) {
.to(call(matchingParameters: .all) {
$0.set(
dependencies.dateNow,
forKey: SNUserDefaults.Date.lastOpenGroupImageUpdate.rawValue

View File

@ -131,8 +131,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
"dt0eBaXneOBfr7qB8pHwwMZjtkOu1ED07T9nszgbWabBphUfWXe2U9K3PTRisSCI="
)!,
using: KeyPair(
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
publicKey: Data(hex: TestConstants.publicKey).bytes,
secretKey: Data(hex: TestConstants.privateKey).bytes
),
using: Dependencies()
)
@ -159,8 +159,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
try MessageReceiver.decryptWithSessionProtocol(
ciphertext: "TestMessage".data(using: .utf8)!,
using: KeyPair(
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
publicKey: Data(hex: TestConstants.publicKey).bytes,
secretKey: Data(hex: TestConstants.privateKey).bytes
),
using: dependencies
)
@ -185,8 +185,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
try MessageReceiver.decryptWithSessionProtocol(
ciphertext: "TestMessage".data(using: .utf8)!,
using: KeyPair(
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
publicKey: Data(hex: TestConstants.publicKey).bytes,
secretKey: Data(hex: TestConstants.privateKey).bytes
),
using: dependencies
)
@ -203,8 +203,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
try MessageReceiver.decryptWithSessionProtocol(
ciphertext: "TestMessage".data(using: .utf8)!,
using: KeyPair(
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
publicKey: Data(hex: TestConstants.publicKey).bytes,
secretKey: Data(hex: TestConstants.privateKey).bytes
),
using: dependencies
)
@ -219,8 +219,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
try MessageReceiver.decryptWithSessionProtocol(
ciphertext: "TestMessage".data(using: .utf8)!,
using: KeyPair(
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
publicKey: Data(hex: TestConstants.publicKey).bytes,
secretKey: Data(hex: TestConstants.privateKey).bytes
),
using: dependencies
)
@ -241,8 +241,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)",
with: TestConstants.serverPublicKey,
userEd25519KeyPair: KeyPair(
publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
),
using: Dependencies()
)
@ -263,8 +263,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)",
with: TestConstants.serverPublicKey,
userEd25519KeyPair: KeyPair(
publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
),
using: dependencies
)
@ -282,8 +282,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)",
with: TestConstants.serverPublicKey,
userEd25519KeyPair: KeyPair(
publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
),
using: dependencies
)
@ -315,8 +315,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)",
with: TestConstants.serverPublicKey,
userEd25519KeyPair: KeyPair(
publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
),
using: dependencies
)
@ -350,8 +350,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)",
with: TestConstants.serverPublicKey,
userEd25519KeyPair: KeyPair(
publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
),
using: dependencies
)
@ -371,8 +371,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)",
with: TestConstants.serverPublicKey,
userEd25519KeyPair: KeyPair(
publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
),
using: dependencies
)
@ -404,8 +404,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)",
with: TestConstants.serverPublicKey,
userEd25519KeyPair: KeyPair(
publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
),
using: dependencies
)
@ -437,8 +437,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)",
with: TestConstants.serverPublicKey,
userEd25519KeyPair: KeyPair(
publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
),
using: dependencies
)
@ -464,8 +464,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)",
with: TestConstants.serverPublicKey,
userEd25519KeyPair: KeyPair(
publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
),
using: dependencies
)
@ -489,8 +489,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)",
with: TestConstants.serverPublicKey,
userEd25519KeyPair: KeyPair(
publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
),
using: dependencies
)
@ -514,8 +514,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)",
with: TestConstants.serverPublicKey,
userEd25519KeyPair: KeyPair(
publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
),
using: dependencies
)
@ -539,8 +539,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)",
with: TestConstants.serverPublicKey,
userEd25519KeyPair: KeyPair(
publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
),
using: dependencies
)

View File

@ -154,8 +154,8 @@ class MessageSenderEncryptionSpec: QuickSpec {
}
.thenReturn(
KeyPair(
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
publicKey: Data(hex: TestConstants.publicKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
)
)
mockCrypto

View File

@ -0,0 +1,3 @@
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
import Foundation

View File

@ -0,0 +1,17 @@
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import SessionUtil
import SessionMessagingKit
extension SessionUtil.Config: Mocked {
static var mockValue: SessionUtil.Config = {
var config: config_object = config_object()
return .object(&config)
}()
}
extension ConfigDump.Variant: Mocked {
static var mockValue: ConfigDump.Variant = .userProfile
}

View File

@ -0,0 +1,24 @@
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import Combine
import SessionUtilitiesKit
@testable import SessionMessagingKit
class MockSessionUtilCache: Mock<SessionUtilCacheType>, SessionUtilCacheType {
var isEmpty: Bool { return accept() as! Bool }
var needsSync: Bool { return accept() as! Bool }
func setConfig(for variant: ConfigDump.Variant, publicKey: String, to config: SessionUtil.Config?) {
accept(args: [variant, publicKey, config])
}
func config(for variant: ConfigDump.Variant, publicKey: String) -> Atomic<SessionUtil.Config?> {
return accept(args: [variant, publicKey]) as! Atomic<SessionUtil.Config?>
}
func removeAll() {
accept()
}
}

View File

@ -10,7 +10,7 @@ enum _001_InitialSetupMigration: Migration {
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.1
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
try db.create(table: Snode.self) { t in
t.column(.address, .text).notNull()
t.column(.port, .integer).notNull()

View File

@ -12,7 +12,7 @@ enum _002_SetupStandardJobs: Migration {
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.1
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
try autoreleasepool {
_ = try Job(
variant: .getSnodePool,

View File

@ -10,7 +10,7 @@ enum _003_YDBToGRDBMigration: Migration {
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.1
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
guard !SNUtilitiesKit.isRunningTests else { return Storage.update(progress: 1, for: self, in: target) }
SNLogNotTests("[Migration Error] Attempted to perform legacy migation")

View File

@ -14,7 +14,7 @@ enum _004_FlagMessageHashAsDeletedOrInvalid: Migration {
/// messages from the beginning of time)
static let minExpectedRunDuration: TimeInterval = 0.2
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
try db.alter(table: SnodeReceivedMessageInfo.self) { t in
t.add(.wasDeletedOrInvalid, .boolean)
.indexed() // Faster querying

View File

@ -23,11 +23,11 @@ public enum GetSnodePoolJob: JobExecutor {
// but we want to succeed this job immediately (since it's marked as blocking), this allows us
// to block if we have no Snode pool and prevent other jobs from failing but avoids having to
// wait if we already have a potentially valid snode pool
guard !SnodeAPI.hasCachedSnodesIncludingExpired() else {
guard !SnodeAPI.hasCachedSnodesIncludingExpired(using: dependencies) else {
SNLog("[GetSnodePoolJob] Has valid cached pool, running async instead")
SnodeAPI
.getSnodePool()
.subscribe(on: DispatchQueue.global(qos: .default))
.getSnodePool(using: dependencies)
.subscribe(on: DispatchQueue.global(qos: .default), using: dependencies)
.sinkUntilComplete()
return success(job, false, dependencies)
}
@ -53,7 +53,7 @@ public enum GetSnodePoolJob: JobExecutor {
)
}
public static func run(using dependencies: Dependencies = Dependencies()) {
public static func run(using dependencies: Dependencies) {
GetSnodePoolJob.run(
Job(variant: .getSnodePool),
queue: .global(qos: .background),

View File

@ -561,7 +561,12 @@ public enum OnionRequestAPI {
if pathFailureCount >= pathFailureThreshold {
dropGuardSnode(guardSnode)
path.forEach { snode in
SnodeAPI.handleError(withStatusCode: statusCode, data: data, forSnode: snode) // Intentionally don't throw
SnodeAPI.handleError(
withStatusCode: statusCode,
data: data,
forSnode: snode,
using: dependencies
) // Intentionally don't throw
}
drop(path)
@ -592,13 +597,15 @@ public enum OnionRequestAPI {
snodeFailureCount += 1
if snodeFailureCount >= snodeFailureThreshold {
SnodeAPI.handleError(withStatusCode: statusCode, data: data, forSnode: snode) // Intentionally don't throw
do {
try drop(snode)
}
catch {
handleUnspecificError()
}
SnodeAPI.handleError(
withStatusCode: statusCode,
data: data,
forSnode: snode,
using: dependencies
) // Intentionally don't throw
do { try drop(snode) }
catch { handleUnspecificError() }
}
else {
OnionRequestAPI.snodeFailureCount

View File

@ -61,21 +61,22 @@ public final class SnodeAPI {
private static let snodeFailureThreshold: Int = 3
private static let minSnodePoolCount: Int = 12
public static func currentOffsetTimestampMs() -> Int64 {
return Int64(
Int64(floor(Date().timeIntervalSince1970 * 1000)) +
SnodeAPI.clockOffsetMs.wrappedValue
)
public static func currentOffsetTimestampMs(using dependencies: Dependencies = Dependencies()) -> Int64 {
let clockOffsetMs: Int64 = SnodeAPI.clockOffsetMs.wrappedValue
return (Int64(floor(dependencies.dateNow.timeIntervalSince1970 * 1000)) + clockOffsetMs)
}
// MARK: Snode Pool Interaction
private static var hasInsufficientSnodes: Bool { snodePool.wrappedValue.count < minSnodePoolCount }
private static func loadSnodePoolIfNeeded() {
private static func loadSnodePoolIfNeeded(
using dependencies: Dependencies = Dependencies()
) {
guard !hasLoadedSnodePool.wrappedValue else { return }
let fetchedSnodePool: Set<Snode> = Storage.shared
let fetchedSnodePool: Set<Snode> = dependencies.storage
.read { db in try Snode.fetchSet(db) }
.defaulting(to: [])
@ -83,9 +84,13 @@ public final class SnodeAPI {
hasLoadedSnodePool.mutate { $0 = true }
}
private static func setSnodePool(_ db: Database? = nil, to newValue: Set<Snode>) {
private static func setSnodePool(
_ db: Database? = nil,
to newValue: Set<Snode>,
using dependencies: Dependencies = Dependencies()
) {
guard let db: Database = db else {
Storage.shared.write { db in setSnodePool(db, to: newValue) }
dependencies.storage.write { db in setSnodePool(db, to: newValue, using: dependencies) }
return
}
@ -111,10 +116,13 @@ public final class SnodeAPI {
// MARK: - Swarm Interaction
private static func loadSwarmIfNeeded(for publicKey: String) {
private static func loadSwarmIfNeeded(
for publicKey: String,
using dependencies: Dependencies = Dependencies()
) {
guard !loadedSwarms.wrappedValue.contains(publicKey) else { return }
let updatedCacheForKey: Set<Snode> = Storage.shared
let updatedCacheForKey: Set<Snode> = dependencies.storage
.read { db in try Snode.fetchSet(db, publicKey: publicKey) }
.defaulting(to: [])
@ -122,27 +130,38 @@ public final class SnodeAPI {
loadedSwarms.mutate { $0.insert(publicKey) }
}
private static func setSwarm(to newValue: Set<Snode>, for publicKey: String, persist: Bool = true) {
private static func setSwarm(
to newValue: Set<Snode>,
for publicKey: String,
persist: Bool = true,
using dependencies: Dependencies
) {
swarmCache.mutate { $0[publicKey] = newValue }
guard persist else { return }
Storage.shared.write { db in
dependencies.storage.write { db in
try? newValue.save(db, key: publicKey)
}
}
public static func dropSnodeFromSwarmIfNeeded(_ snode: Snode, publicKey: String) {
public static func dropSnodeFromSwarmIfNeeded(
_ snode: Snode,
publicKey: String,
using dependencies: Dependencies = Dependencies()
) {
let swarmOrNil = swarmCache.wrappedValue[publicKey]
guard var swarm = swarmOrNil, let index = swarm.firstIndex(of: snode) else { return }
swarm.remove(at: index)
setSwarm(to: swarm, for: publicKey)
setSwarm(to: swarm, for: publicKey, using: dependencies)
}
// MARK: - Public API
public static func hasCachedSnodesIncludingExpired() -> Bool {
loadSnodePoolIfNeeded()
public static func hasCachedSnodesIncludingExpired(
using dependencies: Dependencies = Dependencies()
) -> Bool {
loadSnodePoolIfNeeded(using: dependencies)
return !hasInsufficientSnodes
}
@ -150,7 +169,7 @@ public final class SnodeAPI {
public static func getSnodePool(
using dependencies: Dependencies = Dependencies()
) -> AnyPublisher<Set<Snode>, Error> {
loadSnodePoolIfNeeded()
loadSnodePoolIfNeeded(using: dependencies)
let now: Date = Date()
let hasSnodePoolExpired: Bool = dependencies.storage[.lastSnodePoolRefreshDate]
@ -190,10 +209,10 @@ public final class SnodeAPI {
.tryFlatMap { snodePool -> AnyPublisher<Set<Snode>, Error> in
guard !snodePool.isEmpty else { throw SnodeAPIError.snodePoolUpdatingFailed }
return Storage.shared
return dependencies.storage
.writePublisher { db in
db[.lastSnodePoolRefreshDate] = now
setSnodePool(db, to: snodePool)
setSnodePool(db, to: snodePool, using: dependencies)
return snodePool
}
@ -238,35 +257,35 @@ public final class SnodeAPI {
(0..<validationCount)
.map { _ in
SnodeAPI
.getRandomSnode()
.flatMap { snode -> AnyPublisher<String, Error> in
SnodeAPI
.send(
request: SnodeRequest(
endpoint: .oxenDaemonRPCCall,
body: OxenDaemonRPCRequest(
endpoint: .daemonOnsResolve,
body: ONSResolveRequest(
type: 0, // type 0 means Session
base64EncodedNameHash: base64EncodedNameHash
)
.getRandomSnode(using: dependencies)
.flatMap { snode -> AnyPublisher<String, Error> in
SnodeAPI
.send(
request: SnodeRequest(
endpoint: .oxenDaemonRPCCall,
body: OxenDaemonRPCRequest(
endpoint: .daemonOnsResolve,
body: ONSResolveRequest(
type: 0, // type 0 means Session
base64EncodedNameHash: base64EncodedNameHash
)
),
to: snode,
associatedWith: nil,
using: dependencies
)
.decoded(as: ONSResolveResponse.self)
.tryMap { _, response -> String in
try response.sessionId(
sodium: sodium.wrappedValue,
nameBytes: nameAsData,
nameHashBytes: nameHash
)
}
.retry(4)
.eraseToAnyPublisher()
}
),
to: snode,
associatedWith: nil,
using: dependencies
)
.decoded(as: ONSResolveResponse.self)
.tryMap { _, response -> String in
try response.sessionId(
sodium: sodium.wrappedValue,
nameBytes: nameAsData,
nameHashBytes: nameHash
)
}
.retry(4)
.eraseToAnyPublisher()
}
}
)
.collect()
@ -284,7 +303,7 @@ public final class SnodeAPI {
for publicKey: String,
using dependencies: Dependencies = Dependencies()
) -> AnyPublisher<Set<Snode>, Error> {
loadSwarmIfNeeded(for: publicKey)
loadSwarmIfNeeded(for: publicKey, using: dependencies)
if let cachedSwarm = swarmCache.wrappedValue[publicKey], cachedSwarm.count >= minSwarmSnodeCount {
return Just(cachedSwarm)
@ -292,9 +311,10 @@ public final class SnodeAPI {
.eraseToAnyPublisher()
}
SNLog("Getting swarm for: \((publicKey == getUserHexEncodedPublicKey()) ? "self" : publicKey).")
let currentUserPublicKey: String = getUserHexEncodedPublicKey(using: dependencies)
SNLog("Getting swarm for: \((publicKey == currentUserPublicKey) ? "self" : publicKey).")
return getRandomSnode()
return getRandomSnode(using: dependencies)
.flatMap { snode in
SnodeAPI.send(
request: SnodeRequest(
@ -310,7 +330,7 @@ public final class SnodeAPI {
}
.map { _, responseData in parseSnodes(from: responseData) }
.handleEvents(
receiveOutput: { swarm in setSwarm(to: swarm, for: publicKey) }
receiveOutput: { swarm in setSwarm(to: swarm, for: publicKey, using: dependencies) }
)
.eraseToAnyPublisher()
}
@ -1083,9 +1103,11 @@ public final class SnodeAPI {
.eraseToAnyPublisher()
}
internal static func getRandomSnode() -> AnyPublisher<Snode, Error> {
internal static func getRandomSnode(
using dependencies: Dependencies
) -> AnyPublisher<Snode, Error> {
// randomElement() uses the system's default random generator, which is cryptographically secure
return getSnodePool()
return getSnodePool(using: dependencies)
.map { $0.randomElement()! }
.eraseToAnyPublisher()
}
@ -1246,7 +1268,15 @@ public final class SnodeAPI {
.mapError { error in
switch error {
case HTTPError.httpRequestFailed(let statusCode, let data):
return (SnodeAPI.handleError(withStatusCode: statusCode, data: data, forSnode: snode, associatedWith: publicKey) ?? error)
return SnodeAPI
.handleError(
withStatusCode: statusCode,
data: data,
forSnode: snode,
associatedWith: publicKey,
using: dependencies
)
.defaulting(to: error)
default: return error
}
@ -1259,7 +1289,15 @@ public final class SnodeAPI {
.mapError { error in
switch error {
case HTTPError.httpRequestFailed(let statusCode, let data):
return (SnodeAPI.handleError(withStatusCode: statusCode, data: data, forSnode: snode, associatedWith: publicKey) ?? error)
return SnodeAPI
.handleError(
withStatusCode: statusCode,
data: data,
forSnode: snode,
associatedWith: publicKey,
using: dependencies
)
.defaulting(to: error)
default: return error
}
@ -1334,9 +1372,10 @@ public final class SnodeAPI {
withStatusCode statusCode: UInt,
data: Data?,
forSnode snode: Snode,
associatedWith publicKey: String? = nil
associatedWith publicKey: String? = nil,
using dependencies: Dependencies
) -> Error? {
func handleBadSnode() {
func handleBadSnode(using dependencies: Dependencies) {
let oldFailureCount = (SnodeAPI.snodeFailureCount.wrappedValue[snode] ?? 0)
let newFailureCount = oldFailureCount + 1
SnodeAPI.snodeFailureCount.mutate { $0[snode] = newFailureCount }
@ -1344,7 +1383,7 @@ public final class SnodeAPI {
if newFailureCount >= SnodeAPI.snodeFailureThreshold {
SNLog("Failure threshold reached for: \(snode); dropping it.")
if let publicKey = publicKey {
SnodeAPI.dropSnodeFromSwarmIfNeeded(snode, publicKey: publicKey)
SnodeAPI.dropSnodeFromSwarmIfNeeded(snode, publicKey: publicKey, using: dependencies)
}
SnodeAPI.dropSnodeFromSnodePool(snode)
SNLog("Snode pool count: \(snodePool.wrappedValue.count).")
@ -1355,7 +1394,7 @@ public final class SnodeAPI {
switch statusCode {
case 500, 502, 503:
// The snode is unreachable
handleBadSnode()
handleBadSnode(using: dependencies)
case 404:
// May caused by invalid open groups
@ -1370,14 +1409,14 @@ public final class SnodeAPI {
if let publicKey = publicKey {
func invalidateSwarm() {
SNLog("Invalidating swarm for: \(publicKey).")
SnodeAPI.dropSnodeFromSwarmIfNeeded(snode, publicKey: publicKey)
SnodeAPI.dropSnodeFromSwarmIfNeeded(snode, publicKey: publicKey, using: dependencies)
}
if let data: Data = data {
let snodes = parseSnodes(from: data)
if !snodes.isEmpty {
setSwarm(to: snodes, for: publicKey)
setSwarm(to: snodes, for: publicKey, using: dependencies)
}
else {
invalidateSwarm()
@ -1392,7 +1431,7 @@ public final class SnodeAPI {
}
default:
handleBadSnode()
handleBadSnode(using: dependencies)
let message: String = {
if let data: Data = data, let stringFromData = String(data: data, encoding: .utf8) {
return stringFromData

View File

@ -12,7 +12,7 @@ enum _001_ThemePreferences: Migration {
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.1
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
// Determine if the user was matching the system setting (previously the absence of this value
// indicated that the app should match the system setting)
let isExistingUser: Bool = Identity.userExists(db)

View File

@ -9,7 +9,7 @@ enum _001_InitialSetupMigration: Migration {
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.1
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
try db.create(table: Identity.self) { t in
t.column(.variant, .text)
.notNull()

View File

@ -11,7 +11,7 @@ enum _002_SetupStandardJobs: Migration {
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.1
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
try autoreleasepool {
// Note: This job exists in the 'Session' target but that doesn't have it's own migrations
_ = try Job(

View File

@ -9,7 +9,7 @@ enum _003_YDBToGRDBMigration: Migration {
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.1
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
guard !SNUtilitiesKit.isRunningTests else { return Storage.update(progress: 1, for: self, in: target) }
SNLogNotTests("[Migration Error] Attempted to perform legacy migation")

View File

@ -9,7 +9,7 @@ enum _004_AddJobPriority: Migration {
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0.1
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
// Add `priority` to the job table
try db.alter(table: Job.self) { t in
t.add(.priority, .integer).defaults(to: 0)

View File

@ -74,33 +74,45 @@ public extension Identity {
try Identity(variant: .x25519PublicKey, data: Data(x25519KeyPair.publicKey)).save(db)
}
static func userExists(_ db: Database? = nil) -> Bool {
return (fetchUserKeyPair(db) != nil)
static func userExists(
_ db: Database? = nil,
using dependencies: Dependencies = Dependencies()
) -> Bool {
return (fetchUserKeyPair(db, using: dependencies) != nil)
}
static func fetchUserPublicKey(_ db: Database? = nil) -> Data? {
static func fetchUserPublicKey(
_ db: Database? = nil,
using dependencies: Dependencies = Dependencies()
) -> Data? {
guard let db: Database = db else {
return Storage.shared.read { db in fetchUserPublicKey(db) }
return dependencies.storage.read { db in fetchUserPublicKey(db, using: dependencies) }
}
return try? Identity.fetchOne(db, id: .x25519PublicKey)?.data
}
static func fetchUserPrivateKey(_ db: Database? = nil) -> Data? {
static func fetchUserPrivateKey(
_ db: Database? = nil,
using dependencies: Dependencies = Dependencies()
) -> Data? {
guard let db: Database = db else {
return Storage.shared.read { db in fetchUserPrivateKey(db) }
return dependencies.storage.read { db in fetchUserPrivateKey(db, using: dependencies) }
}
return try? Identity.fetchOne(db, id: .x25519PrivateKey)?.data
}
static func fetchUserKeyPair(_ db: Database? = nil) -> KeyPair? {
static func fetchUserKeyPair(
_ db: Database? = nil,
using dependencies: Dependencies = Dependencies()
) -> KeyPair? {
guard let db: Database = db else {
return Storage.shared.read { db in fetchUserKeyPair(db) }
return dependencies.storage.read { db in fetchUserKeyPair(db, using: dependencies) }
}
guard
let publicKey: Data = fetchUserPublicKey(db),
let privateKey: Data = fetchUserPrivateKey(db)
let publicKey: Data = fetchUserPublicKey(db, using: dependencies),
let privateKey: Data = fetchUserPrivateKey(db, using: dependencies)
else { return nil }
return KeyPair(
@ -109,9 +121,12 @@ public extension Identity {
)
}
static func fetchUserEd25519KeyPair(_ db: Database? = nil) -> KeyPair? {
static func fetchUserEd25519KeyPair(
_ db: Database? = nil,
using dependencies: Dependencies = Dependencies()
) -> KeyPair? {
guard let db: Database = db else {
return Storage.shared.read { db in fetchUserEd25519KeyPair(db) }
return dependencies.storage.read { db in fetchUserEd25519KeyPair(db, using: dependencies) }
}
guard
let publicKey: Data = try? Identity.fetchOne(db, id: .ed25519PublicKey)?.data,

View File

@ -56,14 +56,20 @@ open class Storage {
public init(
customWriter: DatabaseWriter? = nil,
customMigrationTargets: [MigratableTarget.Type]? = nil
customMigrationTargets: [MigratableTarget.Type]? = nil,
using dependencies: Dependencies = Dependencies()
) {
configureDatabase(customWriter: customWriter, customMigrationTargets: customMigrationTargets)
configureDatabase(
customWriter: customWriter,
customMigrationTargets: customMigrationTargets,
using: dependencies
)
}
private func configureDatabase(
customWriter: DatabaseWriter? = nil,
customMigrationTargets: [MigratableTarget.Type]? = nil
customMigrationTargets: [MigratableTarget.Type]? = nil,
using dependencies: Dependencies = Dependencies()
) {
// Create the database directory if needed and ensure it's protection level is set before attempting to
// create the database KeySpec or the database itself
@ -80,7 +86,8 @@ open class Storage {
async: false,
onProgressUpdate: nil,
onMigrationRequirement: { _, _ in },
onComplete: { _, _ in }
onComplete: { _, _ in },
using: dependencies
)
return
}
@ -152,7 +159,8 @@ open class Storage {
async: Bool = true,
onProgressUpdate: ((CGFloat, TimeInterval) -> ())?,
onMigrationRequirement: @escaping (Database, MigrationRequirement) -> (),
onComplete: @escaping (Swift.Result<Void, Error>, Bool) -> ()
onComplete: @escaping (Swift.Result<Void, Error>, Bool) -> (),
using dependencies: Dependencies
) {
guard isValid, let dbWriter: DatabaseWriter = dbWriter else {
let error: Error = (startupError ?? StorageError.startupFailed)
@ -186,11 +194,16 @@ open class Storage {
}
// Setup and run any required migrations
migrator = { [weak self] in
migrator = { [weak self, dependencies] in
var migrator: DatabaseMigrator = DatabaseMigrator()
sortedMigrationInfo.forEach { migrationInfo in
migrationInfo.migrations.forEach { migration in
migrator.registerMigration(self, targetIdentifier: migrationInfo.identifier, migration: migration)
migrator.registerMigration(
self,
targetIdentifier: migrationInfo.identifier,
migration: migration,
using: dependencies
)
}
}

View File

@ -10,7 +10,7 @@ public protocol Migration {
static var minExpectedRunDuration: TimeInterval { get }
static var requirements: [MigrationRequirement] { get }
static func migrate(_ db: Database) throws
static func migrate(_ db: Database, using dependencies: Dependencies) throws
}
public extension Migration {
@ -18,7 +18,8 @@ public extension Migration {
static func loggedMigrate(
_ storage: Storage?,
targetIdentifier: TargetMigrations.Identifier
targetIdentifier: TargetMigrations.Identifier,
using dependencies: Dependencies
) -> ((_ db: Database) throws -> ()) {
return { (db: Database) in
SNLogNotTests("[Migration Info] Starting \(targetIdentifier.key(with: self))")
@ -26,7 +27,7 @@ public extension Migration {
storage?.internalCurrentlyRunningMigration.mutate { $0 = (targetIdentifier, self) }
defer { storage?.internalCurrentlyRunningMigration.mutate { $0 = nil } }
try migrate(db)
try migrate(db, using: dependencies)
SNLogNotTests("[Migration Info] Completed \(targetIdentifier.key(with: self))")
}
}

View File

@ -8,11 +8,12 @@ public extension DatabaseMigrator {
_ storage: Storage?,
targetIdentifier: TargetMigrations.Identifier,
migration: Migration.Type,
foreignKeyChecks: ForeignKeyChecks = .deferred
foreignKeyChecks: ForeignKeyChecks = .deferred,
using dependencies: Dependencies
) {
self.registerMigration(
targetIdentifier.key(with: migration),
migrate: migration.loggedMigrate(storage, targetIdentifier: targetIdentifier)
migrate: migration.loggedMigrate(storage, targetIdentifier: targetIdentifier, using: dependencies)
)
}
}

View File

@ -33,7 +33,8 @@ public enum GeneralError: Error {
public func getUserHexEncodedPublicKey(_ db: Database? = nil, using dependencies: Dependencies = Dependencies()) -> String {
if let cachedKey: String = dependencies.caches[.general].encodedPublicKey { return cachedKey }
if let publicKey: Data = Identity.fetchUserPublicKey(db) { // Can be nil under some circumstances
// Can be nil under some circumstances
if let publicKey: Data = Identity.fetchUserPublicKey(db, using: dependencies) {
let sessionId: SessionId = SessionId(.standard, publicKey: publicKey.bytes)
dependencies.caches.mutate(cache: .general) { $0.encodedPublicKey = sessionId.hexString }

View File

@ -53,7 +53,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
try db.create(table: TestType.self) { t in
t.column(.columnA, .text).primaryKey()
}
@ -71,7 +71,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
static let needsConfigSync: Bool = false
static let minExpectedRunDuration: TimeInterval = 0
static func migrate(_ db: Database) throws {
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
try db.alter(table: TestType.self) { t in
t.add(.columnB, .text)
}
@ -98,6 +98,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
override func spec() {
var customWriter: DatabaseQueue!
var mockStorage: Storage!
var dependencies: Dependencies!
describe("a PersistableRecord") {
beforeEach {
@ -108,6 +109,9 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
TestTarget.self
]
)
dependencies = Dependencies(
storage: mockStorage
)
}
afterEach {
@ -345,7 +349,8 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
migrator.registerMigration(
mockStorage,
targetIdentifier: TestAddColumnMigration.target,
migration: TestAddColumnMigration.self
migration: TestAddColumnMigration.self,
using: dependencies
)
expect { try migrator.migrate(customWriter) }

Some files were not shown because too many files have changed in this diff Show More