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
pull/941/head
Morgan Pretty 2 years ago
parent 8e04944af0
commit 67713ca498

@ -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())
}

@ -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 {

@ -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()
}

@ -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)

@ -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)
}
}

@ -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
)

@ -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<CodingKeys> = 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)
)
}
}

@ -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

@ -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)
}

@ -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

@ -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)
}

@ -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)
}

@ -50,11 +50,7 @@ public final class ClosedGroupPoller: Poller {
.read { db -> Set<String> 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)
}

@ -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)

@ -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

@ -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(

@ -71,12 +71,18 @@ internal extension SessionUtil {
.fetchSet(db))
.defaulting(to: [])
let updatedMembers: Set<GroupMember> = 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
)
}

@ -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<config_group_keys>? = nil
var groupInfoConf: UnsafeMutablePointer<config_object>? = nil
var groupMembersConf: UnsafeMutablePointer<config_object>? = 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<config_group_keys> = groupKeysConf,
let infoConf: UnsafeMutablePointer<config_object> = groupInfoConf,
let membersConf: UnsafeMutablePointer<config_object> = 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)
}
}
}

@ -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
)
}

@ -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> = 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) &&

@ -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(

Loading…
Cancel
Save