From 67713ca498b98c48879d30cae3c2a6669bd1aa0b Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 13 Oct 2023 11:32:46 +1100 Subject: [PATCH] Fixed a few bugs and resolved some TODOs Added additional properties to the Group to simplify the code Added the roleStatus to the GroupMember for future functionality Fixed a bug where the input field might not appear if a group becomes valid while it's open Fixed a bug where updated groups might not get their states loaded into memory under certain conditions Removed some duplicate code --- Session/Closed Groups/EditClosedGroupVC.swift | 2 +- Session/Closed Groups/NewClosedGroupVC.swift | 2 +- Session/Conversations/ConversationVC.swift | 6 +- Session/Utilities/MockDataGenerator.swift | 3 + .../_018_GroupsRebuildChanges.swift | 25 +++++- .../Database/Models/ClosedGroup.swift | 35 ++++----- .../Database/Models/GroupMember.swift | 28 +++++++ .../Database/Models/OpenGroup.swift | 3 - .../Open Groups/OpenGroupManager.swift | 4 + .../MessageReceiver+Groups.swift | 1 + .../MessageReceiver+LegacyClosedGroups.swift | 7 +- .../MessageSender+LegacyClosedGroups.swift | 6 +- .../Pollers/ClosedGroupPoller.swift | 6 +- .../Sending & Receiving/Pollers/Poller.swift | 2 +- .../SessionUtil+Contacts.swift | 6 +- .../SessionUtil+ConvoInfoVolatile.swift | 2 +- .../SessionUtil+GroupMembers.swift | 14 +++- .../SessionUtil+SharedGroup.swift | 77 ++++++------------- .../SessionUtil+UserGroups.swift | 31 +++++--- .../SessionUtil/SessionUtil.swift | 43 ++++++++--- .../Utilities/ProfileManager.swift | 4 +- 21 files changed, 187 insertions(+), 120 deletions(-) diff --git a/Session/Closed Groups/EditClosedGroupVC.swift b/Session/Closed Groups/EditClosedGroupVC.swift index cf02f307c..74068873a 100644 --- a/Session/Closed Groups/EditClosedGroupVC.swift +++ b/Session/Closed Groups/EditClosedGroupVC.swift @@ -348,7 +348,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat guard !updatedName.isEmpty else { return showError(title: "vc_create_closed_group_group_name_missing_error".localized()) } - guard updatedName.utf8CString.count < SessionUtil.libSessionMaxGroupNameByteLength else { + guard updatedName.utf8CString.count < SessionUtil.sizeMaxGroupNameBytes else { return showError(title: "vc_create_closed_group_group_name_too_long_error".localized()) } diff --git a/Session/Closed Groups/NewClosedGroupVC.swift b/Session/Closed Groups/NewClosedGroupVC.swift index 23fcd55f5..f380e4ffb 100644 --- a/Session/Closed Groups/NewClosedGroupVC.swift +++ b/Session/Closed Groups/NewClosedGroupVC.swift @@ -320,7 +320,7 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate else { return showError(title: "vc_create_closed_group_group_name_missing_error".localized()) } - guard name.utf8CString.count < SessionUtil.libSessionMaxGroupNameByteLength else { + guard name.utf8CString.count < SessionUtil.sizeMaxGroupNameBytes else { return showError(title: "vc_create_closed_group_group_name_too_long_error".localized()) } guard selectedProfiles.count >= 1 else { diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 52b9e7f77..1f55182bc 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -895,10 +895,12 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers } // Now we have done all the needed diffs update the viewModel with the latest data + let oldCanWrite: Bool = viewModel.threadData.canWrite self.viewModel.updateThreadData(updatedThreadData) - /// **Note:** This needs to happen **after** we have update the viewModel's thread data - if initialLoad || viewModel.threadData.currentUserIsClosedGroupMember != updatedThreadData.currentUserIsClosedGroupMember { + /// **Note:** This needs to happen **after** we have update the viewModel's thread data (otherwise the `inputAccessoryView` + /// won't be generated correctly) + if initialLoad || oldCanWrite != updatedThreadData.canWrite { if !self.isFirstResponder { self.becomeFirstResponder() } diff --git a/Session/Utilities/MockDataGenerator.swift b/Session/Utilities/MockDataGenerator.swift index 4bdc47990..8ab40b568 100644 --- a/Session/Utilities/MockDataGenerator.swift +++ b/Session/Utilities/MockDataGenerator.swift @@ -197,6 +197,7 @@ enum MockDataGenerator { threadId: randomLegacyGroupPublicKey, name: groupName, formationTimestamp: timestampNow, + shouldPoll: true, invited: false ) .saved(db) @@ -206,6 +207,7 @@ enum MockDataGenerator { groupId: randomLegacyGroupPublicKey, profileId: memberId, role: .standard, + roleStatus: .accepted, // Legacy group members don't have role statuses isHidden: false ) .save(db) @@ -215,6 +217,7 @@ enum MockDataGenerator { groupId: randomLegacyGroupPublicKey, profileId: adminId, role: .admin, + roleStatus: .accepted, // Legacy group members don't have role statuses isHidden: false ) .save(db) diff --git a/SessionMessagingKit/Database/Migrations/_018_GroupsRebuildChanges.swift b/SessionMessagingKit/Database/Migrations/_018_GroupsRebuildChanges.swift index 9ef0275ba..356b16c0c 100644 --- a/SessionMessagingKit/Database/Migrations/_018_GroupsRebuildChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_018_GroupsRebuildChanges.swift @@ -10,20 +10,41 @@ enum _018_GroupsRebuildChanges: Migration { static let needsConfigSync: Bool = false static let minExpectedRunDuration: TimeInterval = 0.1 static var requirements: [MigrationRequirement] = [.sessionUtilStateLoaded] - static var fetchedTables: [(FetchableRecord & TableRecord).Type] = [] - static var createdOrAlteredTables: [(FetchableRecord & TableRecord).Type] = [ClosedGroup.self] + static var fetchedTables: [(FetchableRecord & TableRecord).Type] = [Identity.self] + static var createdOrAlteredTables: [(FetchableRecord & TableRecord).Type] = [ + ClosedGroup.self, GroupMember.self + ] static func migrate(_ db: Database, using dependencies: Dependencies) throws { try db.alter(table: ClosedGroup.self) { t in + t.add(.groupDescription, .text) t.add(.displayPictureUrl, .text) t.add(.displayPictureFilename, .text) t.add(.displayPictureEncryptionKey, .blob) t.add(.lastDisplayPictureUpdate, .integer).defaults(to: 0) + t.add(.shouldPoll, .boolean).defaults(to: false) t.add(.groupIdentityPrivateKey, .blob) t.add(.authData, .blob) t.add(.invited, .boolean).defaults(to: false) } + try db.alter(table: GroupMember.self) { t in + t.add(.roleStatus, .integer) + .notNull() + .defaults(to: GroupMember.RoleStatus.accepted) + } + + // Update existing groups where the current user is a member to have `shouldPoll` as `true` + try ClosedGroup + .joining( + required: ClosedGroup.members + .filter(GroupMember.Columns.profileId == getUserSessionId(db, using: dependencies).hexString) + ) + .updateAll( + db, + ClosedGroup.Columns.shouldPoll.set(to: true) + ) + Storage.update(progress: 1, for: self, in: target, using: dependencies) } } diff --git a/SessionMessagingKit/Database/Models/ClosedGroup.swift b/SessionMessagingKit/Database/Models/ClosedGroup.swift index 5b380c13a..738e68485 100644 --- a/SessionMessagingKit/Database/Models/ClosedGroup.swift +++ b/SessionMessagingKit/Database/Models/ClosedGroup.swift @@ -20,6 +20,7 @@ public struct ClosedGroup: Codable, Identifiable, FetchableRecord, PersistableRe public enum CodingKeys: String, CodingKey, ColumnExpression { case threadId case name + case groupDescription case formationTimestamp case displayPictureUrl @@ -27,6 +28,7 @@ public struct ClosedGroup: Codable, Identifiable, FetchableRecord, PersistableRe case displayPictureEncryptionKey case lastDisplayPictureUpdate + case shouldPoll case groupIdentityPrivateKey case authData case invited @@ -40,6 +42,7 @@ public struct ClosedGroup: Codable, Identifiable, FetchableRecord, PersistableRe /// **Note:** This value will always be publicKey for the closed group public let threadId: String public let name: String + public let groupDescription: String? public let formationTimestamp: TimeInterval /// The URL from which to fetch the groups's display picture. @@ -54,6 +57,9 @@ public struct ClosedGroup: Codable, Identifiable, FetchableRecord, PersistableRe /// The timestamp (in seconds since epoch) that the display picture was last updated public let lastDisplayPictureUpdate: TimeInterval? + /// A flag indicating whether we should poll for messages in this group + public let shouldPoll: Bool? + /// The private key for performing admin actions on this group public let groupIdentityPrivateKey: Data? @@ -104,22 +110,26 @@ public struct ClosedGroup: Codable, Identifiable, FetchableRecord, PersistableRe public init( threadId: String, name: String, + groupDescription: String? = nil, formationTimestamp: TimeInterval, displayPictureUrl: String? = nil, displayPictureFilename: String? = nil, displayPictureEncryptionKey: Data? = nil, lastDisplayPictureUpdate: TimeInterval? = nil, + shouldPoll: Bool?, groupIdentityPrivateKey: Data? = nil, authData: Data? = nil, invited: Bool? ) { self.threadId = threadId self.name = name + self.groupDescription = groupDescription self.formationTimestamp = formationTimestamp self.displayPictureUrl = displayPictureUrl self.displayPictureFilename = displayPictureFilename self.displayPictureEncryptionKey = displayPictureEncryptionKey self.lastDisplayPictureUpdate = lastDisplayPictureUpdate + self.shouldPoll = shouldPoll self.groupIdentityPrivateKey = groupIdentityPrivateKey self.authData = authData self.invited = invited @@ -158,27 +168,6 @@ public extension ClosedGroup { case forced } - /// The Group public key takes up 32 bytes - static func pubKeyByteLength(for variant: SessionThread.Variant) -> Int { - return 32 - } - - /// The Group secret key size differs between legacy and updated groups - static func secretKeyByteLength(for variant: SessionThread.Variant) -> Int { - switch variant { - case .group: return 64 - default: return 32 - } - } - - /// The Group authData size differs between legacy and updated groups - static func authDataByteLength(for variant: SessionThread.Variant) -> Int { - switch variant { - case .group: return 100 - default: return 0 - } - } - static func approveGroup( _ db: Database, group: ClosedGroup, @@ -189,12 +178,13 @@ public extension ClosedGroup { throw MessageReceiverError.noUserED25519KeyPair } - if group.invited == true { + if group.invited == true || group.shouldPoll != true { try ClosedGroup .filter(id: group.id) .updateAllAndConfig( db, ClosedGroup.Columns.invited.set(to: false), + ClosedGroup.Columns.shouldPoll.set(to: true), calledFromConfig: calledFromConfigHandling, using: dependencies ) @@ -205,6 +195,7 @@ public extension ClosedGroup { userED25519KeyPair: userED25519KeyPair, groupIdentityPrivateKey: group.groupIdentityPrivateKey, authData: group.authData, + shouldLoadState: true, using: dependencies ) diff --git a/SessionMessagingKit/Database/Models/GroupMember.swift b/SessionMessagingKit/Database/Models/GroupMember.swift index 75fb605f0..4534ba84c 100644 --- a/SessionMessagingKit/Database/Models/GroupMember.swift +++ b/SessionMessagingKit/Database/Models/GroupMember.swift @@ -17,6 +17,7 @@ public struct GroupMember: Codable, Equatable, Hashable, FetchableRecord, Persis case groupId case profileId case role + case roleStatus case isHidden } @@ -26,10 +27,17 @@ public struct GroupMember: Codable, Equatable, Hashable, FetchableRecord, Persis case moderator case admin } + + public enum RoleStatus: Int, Codable, DatabaseValueConvertible { + case accepted + case pending + case failed + } public let groupId: String public let profileId: String public let role: Role + public let roleStatus: RoleStatus public let isHidden: Bool // MARK: - Relationships @@ -52,11 +60,31 @@ public struct GroupMember: Codable, Equatable, Hashable, FetchableRecord, Persis groupId: String, profileId: String, role: Role, + roleStatus: RoleStatus, isHidden: Bool ) { self.groupId = groupId self.profileId = profileId self.role = role + self.roleStatus = roleStatus self.isHidden = isHidden } } + +// MARK: - Decoding + +extension GroupMember { + public init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + self = GroupMember( + groupId: try container.decode(String.self, forKey: .groupId), + profileId: try container.decode(String.self, forKey: .profileId), + role: try container.decode(Role.self, forKey: .role), + // Added in `_018_GroupsRebuildChanges` + roleStatus: ((try? container.decode(RoleStatus.self, forKey: .roleStatus)) ?? .accepted), + // Added in `_006_FixHiddenModAdminSupport` + isHidden: ((try? container.decode(Bool.self, forKey: .isHidden)) ?? false) + ) + } +} diff --git a/SessionMessagingKit/Database/Models/OpenGroup.swift b/SessionMessagingKit/Database/Models/OpenGroup.swift index 24cc4a2c4..9b0ae4aff 100644 --- a/SessionMessagingKit/Database/Models/OpenGroup.swift +++ b/SessionMessagingKit/Database/Models/OpenGroup.swift @@ -63,9 +63,6 @@ public struct OpenGroup: Codable, Identifiable, FetchableRecord, PersistableReco static let all: Permissions = [ .read, .write, .upload ] } - /// The Community public key takes up 32 bytes - static let pubkeyByteLength: Int = 32 - public var id: String { threadId } // Identifiable /// The id for the thread this open group belongs to diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index e6535ab55..5df7feedb 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -471,6 +471,7 @@ public final class OpenGroupManager { groupId: threadId, profileId: adminId, role: .admin, + roleStatus: .accepted, // Community members don't have role statuses isHidden: false ).save(db) } @@ -482,6 +483,7 @@ public final class OpenGroupManager { groupId: threadId, profileId: adminId, role: .admin, + roleStatus: .accepted, // Community members don't have role statuses isHidden: true ).save(db) } @@ -491,6 +493,7 @@ public final class OpenGroupManager { groupId: threadId, profileId: moderatorId, role: .moderator, + roleStatus: .accepted, // Community members don't have role statuses isHidden: false ).save(db) } @@ -502,6 +505,7 @@ public final class OpenGroupManager { groupId: threadId, profileId: moderatorId, role: .moderator, + roleStatus: .accepted, // Community members don't have role statuses isHidden: true ).save(db) } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift index 041efd85e..66f1eb944 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift @@ -37,6 +37,7 @@ extension MessageReceiver { threadId: groupSessionId, name: (name ?? "GROUP_TITLE_FALLBACK".localized()), formationTimestamp: TimeInterval(joinedAt), + shouldPoll: false, groupIdentityPrivateKey: groupIdentityPrivateKey, authData: authData, invited: invited diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LegacyClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LegacyClosedGroups.swift index 6e3ae4c7e..e3aa5f4f7 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LegacyClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LegacyClosedGroups.swift @@ -163,7 +163,8 @@ extension MessageReceiver { threadId: legacyGroupSessionId, name: name, formationTimestamp: (TimeInterval(formationTimestampMs) / 1000), - invited: false // Legacy groups are never in the "invite" state + shouldPoll: true, // Legacy groups should always poll + invited: false // Legacy groups are never in the "invite" state ).saved(db) // Clear the zombie list if the group wasn't active (ie. had no keys) @@ -177,6 +178,7 @@ extension MessageReceiver { groupId: legacyGroupSessionId, profileId: memberId, role: .standard, + roleStatus: .accepted, // Legacy group members don't have role statuses isHidden: false ).save(db) } @@ -186,6 +188,7 @@ extension MessageReceiver { groupId: legacyGroupSessionId, profileId: adminId, role: .admin, + roleStatus: .accepted, // Legacy group members don't have role statuses isHidden: false ).save(db) } @@ -431,6 +434,7 @@ extension MessageReceiver { groupId: threadId, profileId: memberId, role: .standard, + roleStatus: .accepted, // Legacy group members don't have role statuses isHidden: false ).save(db) } @@ -633,6 +637,7 @@ extension MessageReceiver { groupId: threadId, profileId: sender, role: .zombie, + roleStatus: .accepted, // Legacy group members don't have role statuses isHidden: false ).save(db) } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+LegacyClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+LegacyClosedGroups.swift index bb9c91c4d..aabee887b 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+LegacyClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+LegacyClosedGroups.swift @@ -42,7 +42,8 @@ extension MessageSender { threadId: legacyGroupSessionId, name: name, formationTimestamp: formationTimestamp, - invited: false // Legacy groups are never in the "invite" state + shouldPoll: true, // Legacy groups should always poll + invited: false // Legacy groups are never in the "invite" state ).insert(db) // Store the key pair @@ -60,6 +61,7 @@ extension MessageSender { groupId: legacyGroupSessionId, profileId: adminId, role: .admin, + roleStatus: .accepted, // Legacy group members don't have role statuses isHidden: false ).save(db) } @@ -69,6 +71,7 @@ extension MessageSender { groupId: legacyGroupSessionId, profileId: memberId, role: .standard, + roleStatus: .accepted, // Legacy group members don't have role statuses isHidden: false ).save(db) } @@ -483,6 +486,7 @@ extension MessageSender { groupId: closedGroup.id, profileId: member, role: .standard, + roleStatus: .accepted, // Legacy group members don't have role statuses isHidden: false ).save(db) } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index ef5aaf65e..83fcfb518 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -50,11 +50,7 @@ public final class ClosedGroupPoller: Poller { .read { db -> Set in try ClosedGroup .select(.threadId) - .filter(ClosedGroup.Columns.invited == false) - .joining( - required: ClosedGroup.members - .filter(GroupMember.Columns.profileId == getUserSessionId(db, using: dependencies).hexString) - ) + .filter(ClosedGroup.Columns.shouldPoll == true) .asRequest(of: String.self) .fetchSet(db) } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index 347ba47cb..ffcf68540 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -165,7 +165,7 @@ public class Poller { let pollerName: String = ( poller?.pollerName(for: publicKey) ?? - "poller with public key \(publicKey)" + "poller with public key \(publicKey)" // stringlint:disable ) let configHashes: [String] = SessionUtil.configHashes(for: publicKey, using: dependencies) diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift index 589d93f23..4b3cfaadb 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -8,9 +8,9 @@ import SessionUtilitiesKit // MARK: - Size Restrictions public extension SessionUtil { - static var libSessionMaxNameByteLength: Int { CONTACT_MAX_NAME_LENGTH } - static var libSessionMaxNicknameByteLength: Int { CONTACT_MAX_NAME_LENGTH } - static var libSessionMaxProfileUrlByteLength: Int { PROFILE_PIC_MAX_URL_LENGTH } + static var sizeMaxNameBytes: Int { CONTACT_MAX_NAME_LENGTH } + static var sizeMaxNicknameBytes: Int { CONTACT_MAX_NAME_LENGTH } + static var sizeMaxProfileUrlBytes: Int { PROFILE_PIC_MAX_URL_LENGTH } } // MARK: - Contacts Handling diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift index e712e27d4..c4ea213ea 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift @@ -623,7 +623,7 @@ public extension SessionUtil { let roomToken: String = String(libSessionVal: community.room) let publicKey: String = Data( libSessionVal: community.pubkey, - count: OpenGroup.pubkeyByteLength + count: SessionUtil.sizeCommunityPubkeyBytes ).toHexString() result.append( diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+GroupMembers.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+GroupMembers.swift index 3b34ef819..82c185390 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+GroupMembers.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+GroupMembers.swift @@ -71,12 +71,18 @@ internal extension SessionUtil { .fetchSet(db)) .defaulting(to: []) let updatedMembers: Set = result - .map { + .map { data in GroupMember( groupId: groupSessionId.hexString, - profileId: $0.memberId, - role: ($0.admin ? .admin : .standard), - // TODO: Other properties + profileId: data.memberId, + role: (data.admin || (data.promoted > 0) ? .admin : .standard), + roleStatus: { + switch (data.invited, data.promoted, data.admin) { + case (2, _, _), (_, 2, false): return .failed // Explicitly failed + case (1..., _, _), (_, 1..., false): return .pending // Pending if not accepted + default: return .accepted // Otherwise it's accepted + } + }(), isHidden: false ) } diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+SharedGroup.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+SharedGroup.swift index 2280fa7c1..4c5831aa1 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+SharedGroup.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+SharedGroup.swift @@ -33,11 +33,7 @@ internal extension SessionUtil { 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 - // the in-memory state after init though - var secretKey: [UInt8] = userED25519KeyPair.secretKey - var groupIdentityPublicKey: [UInt8] = groupIdentityKeyPair.publicKey - var groupIdentityPrivateKey: [UInt8] = groupIdentityKeyPair.secretKey + // Prep the relevant details let groupSessionId: SessionId = SessionId(.group, publicKey: groupIdentityKeyPair.publicKey) let creationTimestamp: TimeInterval = TimeInterval( SnodeAPI.currentOffsetTimestampMs(using: dependencies) / 1000 @@ -46,43 +42,17 @@ internal extension SessionUtil { let currentUserProfile: Profile? = Profile.fetchOrCreateCurrentUser(db, using: dependencies) // Create the new config objects - var groupKeysConf: UnsafeMutablePointer? = nil - var groupInfoConf: UnsafeMutablePointer? = nil - var groupMembersConf: UnsafeMutablePointer? = nil - var error: [CChar] = [CChar](repeating: 0, count: 256) - try groups_info_init( - &groupInfoConf, - &groupIdentityPublicKey, - &groupIdentityPrivateKey, - nil, - 0, - &error - ).orThrow(error: error) - try groups_members_init( - &groupMembersConf, - &groupIdentityPublicKey, - &groupIdentityPrivateKey, - nil, - 0, - &error - ).orThrow(error: error) - try groups_keys_init( - &groupKeysConf, - &secretKey, - &groupIdentityPublicKey, - &groupIdentityPrivateKey, - groupInfoConf, - groupMembersConf, - nil, - 0, - &error - ).orThrow(error: error) + let groupState: [ConfigDump.Variant: Config] = try createGroupState( + groupSessionId: groupSessionId, + userED25519KeyPair: userED25519KeyPair, + groupIdentityPrivateKey: Data(groupIdentityKeyPair.secretKey), + authData: nil, + shouldLoadState: false, // We manually load the state after populating the configs + using: dependencies + ) - guard - let keysConf: UnsafeMutablePointer = groupKeysConf, - let infoConf: UnsafeMutablePointer = groupInfoConf, - let membersConf: UnsafeMutablePointer = groupMembersConf - else { + // Extract the conf objects from the state to load in the initial data + guard case .groupKeys(_, let groupInfoConf, let membersConf) = groupState[.groupKeys] else { SNLog("[SessionUtil Error] Group config objects were null") throw SessionUtilError.unableToCreateConfigObject } @@ -133,13 +103,8 @@ internal extension SessionUtil { groups_members_set(membersConf, &member) } } - // Define the config state map and load it into memory - let groupState: [ConfigDump.Variant: Config] = [ - .groupKeys: .groupKeys(keysConf, info: infoConf, members: membersConf), - .groupInfo: .object(infoConf), - .groupMembers: .object(membersConf), - ] + // Now that everything has been populated correctly we can load the state into memory dependencies.mutate(cache: .sessionUtil) { cache in groupState.forEach { variant, config in cache.setConfig(for: variant, sessionId: groupSessionId, to: config) @@ -147,7 +112,7 @@ internal extension SessionUtil { } return ( - SessionId(.group, publicKey: groupIdentityPublicKey), + groupSessionId, groupIdentityKeyPair, groupState, ClosedGroup( @@ -158,7 +123,8 @@ internal extension SessionUtil { displayPictureFilename: displayPictureFilename, displayPictureEncryptionKey: displayPictureEncryptionKey, lastDisplayPictureUpdate: creationTimestamp, - groupIdentityPrivateKey: Data(groupIdentityPrivateKey), + shouldPoll: true, + groupIdentityPrivateKey: Data(groupIdentityKeyPair.secretKey), invited: false ), finalMembers.map { memberId, info -> GroupMember in @@ -166,6 +132,7 @@ internal extension SessionUtil { groupId: groupSessionId.hexString, profileId: memberId, role: (info.isAdmin ? .admin : .standard), + roleStatus: (memberId == userSessionId.hexString ? .accepted : .pending), isHidden: false ) } @@ -222,6 +189,7 @@ internal extension SessionUtil { userED25519KeyPair: KeyPair, groupIdentityPrivateKey: Data?, authData: Data?, + shouldLoadState: Bool, using dependencies: Dependencies ) throws -> [ConfigDump.Variant: Config] { var secretKey: [UInt8] = userED25519KeyPair.secretKey @@ -277,9 +245,14 @@ internal extension SessionUtil { .groupMembers: .object(membersConf), ] - dependencies.mutate(cache: .sessionUtil) { cache in - groupState.forEach { variant, config in - cache.setConfig(for: variant, sessionId: groupSessionId, to: config) + // Only load the state if specified (during initial group creation we want to + // load the state after populating the different configs incase invalid data + // was provided) + if shouldLoadState { + dependencies.mutate(cache: .sessionUtil) { cache in + groupState.forEach { variant, config in + cache.setConfig(for: variant, sessionId: groupSessionId, to: config) + } } } diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift index 9142a2f4a..2fad97faa 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift @@ -10,10 +10,16 @@ import SessionSnodeKit // MARK: - Size Restrictions public extension SessionUtil { - static var libSessionMaxGroupNameByteLength: Int { GROUP_NAME_MAX_LENGTH } - static var libSessionMaxGroupBaseUrlByteLength: Int { COMMUNITY_BASE_URL_MAX_LENGTH } - static var libSessionMaxGroupFullUrlByteLength: Int { COMMUNITY_FULL_URL_MAX_LENGTH } - static var libSessionMaxCommunityRoomByteLength: Int { COMMUNITY_ROOM_MAX_LENGTH } + static var sizeMaxGroupNameBytes: Int { GROUP_NAME_MAX_LENGTH } + static var sizeMaxCommunityBaseUrlBytes: Int { COMMUNITY_BASE_URL_MAX_LENGTH } + static var sizeMaxCommunityFullUrlBytes: Int { COMMUNITY_FULL_URL_MAX_LENGTH } + static var sizeMaxCommunityRoomBytes: Int { COMMUNITY_ROOM_MAX_LENGTH } + + static var sizeCommunityPubkeyBytes: Int { 32 } + static var sizeLegacyGroupPubkeyBytes: Int { 32 } + static var sizeLegacyGroupSecretKeyBytes: Int { 32 } + static var sizeGroupSecretKeyBytes: Int { 64 } + static var sizeGroupAuthDataBytes: Int { 100 } } // MARK: - UserGroups Handling @@ -58,7 +64,7 @@ internal extension SessionUtil { roomToken: roomToken, publicKey: Data( libSessionVal: community.pubkey, - count: OpenGroup.pubkeyByteLength + count: SessionUtil.sizeCommunityPubkeyBytes ).toHexString() ), priority: community.priority @@ -77,11 +83,11 @@ internal extension SessionUtil { threadId: groupId, publicKey: Data( libSessionVal: legacyGroup.enc_pubkey, - count: ClosedGroup.pubKeyByteLength(for: .legacyGroup) + count: SessionUtil.sizeLegacyGroupPubkeyBytes ), secretKey: Data( libSessionVal: legacyGroup.enc_seckey, - count: ClosedGroup.secretKeyByteLength(for: .legacyGroup) + count: SessionUtil.sizeLegacyGroupSecretKeyBytes ), receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs(using: dependencies)) / 1000) ), @@ -99,6 +105,7 @@ internal extension SessionUtil { groupId: groupId, profileId: memberId, role: .standard, + roleStatus: .accepted, // Legacy group members don't have role statuses isHidden: false ) }, @@ -109,6 +116,7 @@ internal extension SessionUtil { groupId: groupId, profileId: memberId, role: .admin, + roleStatus: .accepted, // Legacy group members don't have role statuses isHidden: false ) }, @@ -126,7 +134,7 @@ internal extension SessionUtil { groupIdentityPrivateKey: (!group.have_secretkey ? nil : Data( libSessionVal: group.secretkey, - count: ClosedGroup.secretKeyByteLength(for: .group), + count: SessionUtil.sizeGroupSecretKeyBytes, nullIfEmpty: true ) ), @@ -134,7 +142,7 @@ internal extension SessionUtil { authData: (!group.have_auth_data ? nil : Data( libSessionVal: group.auth_data, - count: ClosedGroup.authDataByteLength(for: .group), + count: SessionUtil.sizeGroupAuthDataBytes, nullIfEmpty: true ) ), @@ -334,6 +342,7 @@ internal extension SessionUtil { groupId: admin.groupId, profileId: admin.profileId, role: .standard, + roleStatus: .accepted, // Legacy group members don't have role statuses isHidden: false ) } @@ -842,6 +851,7 @@ public extension SessionUtil { groupId: legacyGroupSessionId, profileId: memberId, role: .standard, + roleStatus: .accepted, // Legacy group members don't have role statuses isHidden: false ) }, @@ -851,6 +861,7 @@ public extension SessionUtil { groupId: legacyGroupSessionId, profileId: memberId, role: .admin, + roleStatus: .accepted, // Legacy group members don't have role statuses isHidden: false ) }, @@ -891,6 +902,7 @@ public extension SessionUtil { groupId: legacyGroupSessionId, profileId: memberId, role: .standard, + roleStatus: .accepted, // Legacy group members don't have role statuses isHidden: false ) }, @@ -900,6 +912,7 @@ public extension SessionUtil { groupId: legacyGroupSessionId, profileId: memberId, role: .admin, + roleStatus: .accepted, // Legacy group members don't have role statuses isHidden: false ) } diff --git a/SessionMessagingKit/SessionUtil/SessionUtil.swift b/SessionMessagingKit/SessionUtil/SessionUtil.swift index bdfada1f8..9d20b2ef0 100644 --- a/SessionMessagingKit/SessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/SessionUtil/SessionUtil.swift @@ -45,7 +45,7 @@ public enum SessionUtil { // 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, using: dependencies)?.secretKey, + let ed25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db, using: dependencies), dependencies[cache: .sessionUtil].isEmpty else { return SNLog("[SessionUtil] Ignoring loadState due to existing state") } @@ -58,11 +58,14 @@ public enum SessionUtil { .asSet() let missingRequiredVariants: Set = ConfigDump.Variant.userVariants .subtracting(existingDumpVariants) - let groupsByKey: [String: Data] = (try? ClosedGroup - .filter(ids: existingDumps.map { $0.sessionId.hexString }) + let groupsByKey: [String: ClosedGroup] = (try? ClosedGroup + .filter(ClosedGroup.Columns.threadId.like("\(SessionId.Prefix.group.rawValue)%")) .fetchAll(db) - .reduce(into: [:]) { result, next in result[next.threadId] = next.groupIdentityPrivateKey }) + .reduce(into: [:]) { result, next in result[next.threadId] = next }) .defaulting(to: [:]) + let groupsWithNoDumps: [ClosedGroup] = groupsByKey + .values + .filter { group in !existingDumps.contains(where: { $0.sessionId.hexString == group.id }) } // Create the config records for each dump dependencies.mutate(cache: .sessionUtil) { cache in @@ -74,8 +77,10 @@ public enum SessionUtil { .loadState( for: dump.variant, sessionId: dump.sessionId, - userEd25519SecretKey: ed25519SecretKey, - groupEd25519SecretKey: groupsByKey[dump.sessionId.hexString].map { Array($0) }, + userEd25519SecretKey: ed25519KeyPair.secretKey, + groupEd25519SecretKey: groupsByKey[dump.sessionId.hexString]? + .groupIdentityPrivateKey + .map { Array($0) }, cachedData: dump.data, cache: cache ) @@ -83,6 +88,8 @@ public enum SessionUtil { ) } + /// It's possible for there to not be dumps for all of the user configs so we load any missing ones to ensure funcitonality + /// works smoothly missingRequiredVariants.forEach { variant in cache.setConfig( for: variant, @@ -91,7 +98,7 @@ public enum SessionUtil { .loadState( for: variant, sessionId: userSessionId, - userEd25519SecretKey: ed25519SecretKey, + userEd25519SecretKey: ed25519KeyPair.secretKey, groupEd25519SecretKey: nil, cachedData: nil, cache: cache @@ -101,6 +108,22 @@ public enum SessionUtil { } } + /// It's possible for a group to get created but for a dump to not be created (eg. when a crash happens at the right time), to + /// handle this we also load the state of any groups which don't have dumps if they aren't in the `invited` state (those in + /// the `invited` state will have their state loaded if the invite is accepted) + groupsWithNoDumps + .filter { $0.invited != true } + .forEach { group in + _ = try? SessionUtil.createGroupState( + groupSessionId: SessionId(.group, hex: group.id), + userED25519KeyPair: ed25519KeyPair, + groupIdentityPrivateKey: group.groupIdentityPrivateKey, + authData: group.authData, + shouldLoadState: true, + using: dependencies + ) + } + SNLog("[SessionUtil] Completed loadState") } @@ -498,9 +521,9 @@ public enum SessionUtil { public extension SessionUtil { static func parseCommunity(url: String) -> (room: String, server: String, publicKey: String)? { var cFullUrl: [CChar] = url.cArray.nullTerminated() - var cBaseUrl: [CChar] = [CChar](repeating: 0, count: COMMUNITY_BASE_URL_MAX_LENGTH) - var cRoom: [CChar] = [CChar](repeating: 0, count: COMMUNITY_ROOM_MAX_LENGTH) - var cPubkey: [UInt8] = [UInt8](repeating: 0, count: OpenGroup.pubkeyByteLength) + var cBaseUrl: [CChar] = [CChar](repeating: 0, count: SessionUtil.sizeMaxCommunityBaseUrlBytes) + var cRoom: [CChar] = [CChar](repeating: 0, count: SessionUtil.sizeMaxCommunityRoomBytes) + var cPubkey: [UInt8] = [UInt8](repeating: 0, count: SessionUtil.sizeCommunityPubkeyBytes) guard community_parse_full_url(&cFullUrl, &cBaseUrl, &cRoom, &cPubkey) && diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index 737b1576e..0252a01b2 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -38,11 +38,11 @@ public struct ProfileManager { // MARK: - Functions public static func isToLong(profileName: String) -> Bool { - return (profileName.utf8CString.count > SessionUtil.libSessionMaxNameByteLength) + return (profileName.utf8CString.count > SessionUtil.sizeMaxNameBytes) } public static func isToLong(profileUrl: String) -> Bool { - return (profileUrl.utf8CString.count > SessionUtil.libSessionMaxProfileUrlByteLength) + return (profileUrl.utf8CString.count > SessionUtil.sizeMaxProfileUrlBytes) } public static func profileAvatar(