diff --git a/LibSession-Util b/LibSession-Util index 916a47fcd..6c4e38c4e 160000 --- a/LibSession-Util +++ b/LibSession-Util @@ -1 +1 @@ -Subproject commit 916a47fcd347332256b744457362639c405aebef +Subproject commit 6c4e38c4ed7f3a153428491bda588c782f8e0c60 diff --git a/Scripts/build_libSession_util.sh b/Scripts/build_libSession_util.sh index 37cbaee77..a9b16cfd6 100755 --- a/Scripts/build_libSession_util.sh +++ b/Scripts/build_libSession_util.sh @@ -26,7 +26,7 @@ # request ever gets implemented: https://github.com/CocoaPods/CocoaPods/issues/8464 # Need to set the path or we won't find cmake -PATH=${PATH}:/usr/local/bin:/opt/homebrew/bin:/sbin/md5 +PATH=${PATH}:/usr/local/bin:/opt/local/bin:/opt/homebrew/bin:/sbin/md5 exec 3>&1 # Save original stdout @@ -189,7 +189,8 @@ for i in "${!TARGET_ARCHS[@]}"; do echo_message "Building ${TARGET_ARCHS[$i]} for $platform in $build" cd "${SRCROOT}/LibSession-Util" - ./utils/static-bundle.sh "$build" "" \ + env -i PATH="$PATH" SDKROOT="$(xcrun --sdk macosx --show-sdk-path)" \ + ./utils/static-bundle.sh "$build" "" \ -DCMAKE_TOOLCHAIN_FILE="${SRCROOT}/LibSession-Util/external/ios-cmake/ios.toolchain.cmake" \ -DPLATFORM=$platform \ -DDEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET \ diff --git a/Session/Closed Groups/EditClosedGroupVC.swift b/Session/Closed Groups/EditClosedGroupVC.swift index dcb688ec8..4900897da 100644 --- a/Session/Closed Groups/EditClosedGroupVC.swift +++ b/Session/Closed Groups/EditClosedGroupVC.swift @@ -15,17 +15,17 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat let role: GroupMember.Role let profile: Profile? let accessibilityLabel: String? - let accessibilityId: String? + let accessibilityIdentifier: String? } private let threadId: String private let threadVariant: SessionThread.Variant private var originalName: String = "" - private var originalMembersAndZombieIds: Set = [] + private var originalMembersIds: Set = [] private var name: String = "" private var hasContactsToAdd: Bool = false private var userSessionId: SessionId = .invalid - private var membersAndZombies: [GroupMemberDisplayInfo] = [] + private var allGroupMembers: [GroupMemberDisplayInfo] = [] private var adminIds: Set = [] private var isEditingGroupName = false { didSet { handleIsEditingGroupNameChanged() } } private var tableViewHeightConstraint: NSLayoutConstraint! @@ -125,8 +125,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat ) .asRequest(of: GroupMemberDisplayInfo.self) .fetchAll(db) - self?.membersAndZombies = allGroupMembers - .filter { $0.role == .standard || $0.role == .zombie } + self?.allGroupMembers = allGroupMembers self?.adminIds = allGroupMembers .filter { $0.role == .admin } .map { $0.profileId } @@ -135,7 +134,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat let uniqueGroupMemberIds: Set = allGroupMembers .map { $0.profileId } .asSet() - self?.originalMembersAndZombieIds = uniqueGroupMemberIds + self?.originalMembersIds = uniqueGroupMemberIds self?.hasContactsToAdd = ((try? Profile .allContactProfiles( excluding: uniqueGroupMemberIds.inserting(userSessionId.hexString) @@ -214,16 +213,16 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat // MARK: - Table View Data Source / Delegate func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return membersAndZombies.count + return allGroupMembers.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: SessionCell = tableView.dequeue(type: SessionCell.self, for: indexPath) - let displayInfo: GroupMemberDisplayInfo = membersAndZombies[indexPath.row] + let displayInfo: GroupMemberDisplayInfo = allGroupMembers[indexPath.row] cell.update( with: SessionCell.Info( id: displayInfo, - position: Position.with(indexPath.row, count: membersAndZombies.count), + position: Position.with(indexPath.row, count: allGroupMembers.count), leftAccessory: .profile(id: displayInfo.profileId, profile: displayInfo.profile), title: ( displayInfo.profile?.displayName() ?? @@ -256,7 +255,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat } func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - let profileId: String = self.membersAndZombies[indexPath.row].profileId + let profileId: String = self.allGroupMembers[indexPath.row].profileId let delete: UIContextualAction = UIContextualAction( title: "GROUP_ACTION_REMOVE".localized(), @@ -269,7 +268,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat tableView: tableView ) { [weak self] _, _, completionHandler in self?.adminIds.remove(profileId) - self?.membersAndZombies.remove(at: indexPath.row) + self?.allGroupMembers.remove(at: indexPath.row) self?.handleMembersChanged() completionHandler(true) @@ -302,7 +301,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat } private func handleMembersChanged() { - tableViewHeightConstraint.constant = CGFloat(membersAndZombies.count) * 78 + tableViewHeightConstraint.constant = CGFloat(allGroupMembers.count) * 78 tableView.reloadData() } @@ -364,7 +363,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat let userSessionId: SessionId = self.userSessionId let userSelectionVC: UserSelectionVC = UserSelectionVC( with: title, - excluding: membersAndZombies + excluding: allGroupMembers .map { $0.profileId } .asSet() ) { [weak self] selectedUserIds in @@ -378,10 +377,10 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat role: .standard, profile: profile, accessibilityLabel: "Contact", - accessibilityId: "Contact" + accessibilityIdentifier: "Contact" ) } - self?.membersAndZombies = (self?.membersAndZombies ?? []) + self?.allGroupMembers = (self?.allGroupMembers ?? []) .appending(contentsOf: selectedGroupMembers) .sorted(by: { lhs, rhs in if lhs.role == .zombie && rhs.role != .zombie { @@ -408,7 +407,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat }) .filter { $0.role == .standard || $0.role == .zombie } - let uniqueGroupMemberIds: Set = (self?.membersAndZombies ?? []) + let uniqueGroupMemberIds: Set = (self?.allGroupMembers ?? []) .map { $0.profileId } .asSet() .inserting(contentsOf: self?.adminIds) @@ -443,16 +442,16 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat let threadId: String = self.threadId let updatedName: String = self.name let userSessionId: SessionId = self.userSessionId - let updatedMembers: [(String, Profile?)] = self.membersAndZombies + let updatedMembers: [(String, Profile?)] = self.allGroupMembers .map { ($0.profileId, $0.profile) } let updatedMemberIds: Set = updatedMembers.map { $0.0 }.asSet() - guard updatedMemberIds != self.originalMembersAndZombieIds || updatedName != self.originalName else { + guard updatedMemberIds != self.originalMembersIds || updatedName != self.originalName else { return popToConversationVC(self) } if !updatedMemberIds.contains(userSessionId.hexString) { - guard self.originalMembersAndZombieIds.removing(userSessionId.hexString) == updatedMemberIds else { + guard self.originalMembersIds.removing(userSessionId.hexString) == updatedMemberIds else { return showError( title: "GROUP_UPDATE_ERROR_TITLE".localized(), message: "GROUP_UPDATE_ERROR_MESSAGE".localized() diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift index 018939764..041efd85e 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift @@ -24,7 +24,7 @@ extension MessageReceiver { groupIdentityPrivateKey: Data?, name: String?, authData: Data?, - created: Int64, + joinedAt: Int64, invited: Bool, calledFromConfigHandling: Bool, using dependencies: Dependencies @@ -36,7 +36,7 @@ extension MessageReceiver { let closedGroup: ClosedGroup = try ClosedGroup( threadId: groupSessionId, name: (name ?? "GROUP_TITLE_FALLBACK".localized()), - formationTimestamp: TimeInterval(created), + formationTimestamp: TimeInterval(joinedAt), groupIdentityPrivateKey: groupIdentityPrivateKey, authData: authData, invited: invited @@ -50,7 +50,7 @@ extension MessageReceiver { groupIdentityPrivateKey: groupIdentityPrivateKey, name: name, authData: authData, - joinedAt: created, + joinedAt: joinedAt, invited: invited, using: dependencies ) diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 86843c04b..fe70761c5 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -173,7 +173,6 @@ public final class MessageSender { MessageWrapper.wrap( type: .sessionMessage, timestamp: sentTimestamp, - senderPublicKey: "", base64EncodedContent: ciphertext.base64EncodedString() ) ) @@ -189,7 +188,6 @@ public final class MessageSender { MessageWrapper.wrap( type: .closedGroupMessage, timestamp: sentTimestamp, - senderPublicKey: groupId, base64EncodedContent: plaintext.base64EncodedString(), wrapInWebSocketMessage: false ) @@ -223,7 +221,7 @@ public final class MessageSender { MessageWrapper.wrap( type: .closedGroupMessage, timestamp: sentTimestamp, - senderPublicKey: groupPublicKey, + senderPublicKey: groupPublicKey, // Needed for Android base64EncodedContent: ciphertext.base64EncodedString() ) ) diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+GroupMembers.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+GroupMembers.swift index dd5fd1221..f571fda25 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+GroupMembers.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+GroupMembers.swift @@ -96,8 +96,9 @@ internal extension SessionUtil { .forEach { try $0.save(db) } try GroupMember + .filter(GroupMember.Columns.groupId == groupSessionId.hexString) .filter( - GroupMember.Columns.groupId == groupSessionId.hexString && ( + ( GroupMember.Columns.role == GroupMember.Role.standard && !updatedStandardMemberIds.contains(GroupMember.Columns.profileId) ) || ( diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift index f285ad4a5..9142a2f4a 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift @@ -425,10 +425,6 @@ internal extension SessionUtil { .reduce(into: [:]) { result, next in result[next.id] = next } try groups.forEach { group in - guard - let joinedAt: Int64 = group.joinedAt - else { return } - switch (existingGroups[group.groupSessionId], existingGroupSessionIds.contains(group.groupSessionId)) { case (.none, _), (_, false): // Add a new group if it doesn't already exist @@ -438,19 +434,24 @@ internal extension SessionUtil { groupIdentityPrivateKey: group.groupIdentityPrivateKey, name: group.name, authData: group.authData, - created: Int64((group.joinedAt ?? (serverTimestampMs / 1000))), + joinedAt: Int64((group.joinedAt ?? (serverTimestampMs / 1000))), invited: (group.invited == true), calledFromConfigHandling: true, using: dependencies ) case (.some(let existingGroup), _): + let joinedAt: TimeInterval = ( + group.joinedAt.map { TimeInterval($0) } ?? + existingGroup.formationTimestamp + ) + /// Otherwise update the existing group /// /// **Note:** We ignore the `name` value here as if it's an existing group then assume we will get the /// proper name by polling for the `GROUP_INFO` instead of via syncing the `USER_GROUPS` data let groupChanges: [ConfigColumnAssignment] = [ - (existingGroup.formationTimestamp == TimeInterval(joinedAt) ? nil : + (existingGroup.formationTimestamp == joinedAt ? nil : ClosedGroup.Columns.formationTimestamp.set(to: TimeInterval(joinedAt)) ), (existingGroup.authData == group.authData ? nil : diff --git a/SessionMessagingKit/SessionUtil/Types/Config.swift b/SessionMessagingKit/SessionUtil/Types/Config.swift index 9a48b0927..42b7fcd79 100644 --- a/SessionMessagingKit/SessionUtil/Types/Config.swift +++ b/SessionMessagingKit/SessionUtil/Types/Config.swift @@ -219,8 +219,9 @@ public extension SessionUtil { .map { message -> [UInt8] in message.data.bytes } .unsafeCopy() var mergeSize: [Int] = messages.map { $0.data.count } + var mergedHashesPtr: UnsafeMutablePointer? try CExceptionHelper.performSafely { - config_merge( + mergedHashesPtr = config_merge( conf, &mergeHashes, &mergeData, @@ -232,10 +233,23 @@ public extension SessionUtil { mergeData.forEach { $0?.deallocate() } // Get the list of hashes from the config (to determine which were successful) - let currentHashes: Set = currentHashes().asSet() + let mergedHashes: [String] = mergedHashesPtr + .map { ptr in + [String]( + pointer: ptr.pointee.value, + count: ptr.pointee.len, + defaultValue: [] + ) + } + .defaulting(to: []) + mergedHashesPtr?.deallocate() + + if mergedHashes.count != messages.count { + SNLog("[SessionUtil] Unable to merge all \(messages[0].namespace) messages (\(mergedHashes.count)/\(messages.count))") + } return messages - .filter { currentHashes.contains($0.serverHash) } + .filter { mergedHashes.contains($0.serverHash) } .map { $0.serverTimestampMs } .sorted() .last diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index 9093d28e2..3640d9ff8 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -1025,11 +1025,11 @@ public extension SessionThreadViewModel { LEFT JOIN ( SELECT \(groupMember[.groupId]), - COUNT(\(groupMember[.rowId])) AS \(ClosedGroupUserCount.Columns.closedGroupUserCount) + COUNT(DISTINCT \(groupMember[.profileId])) AS \(ClosedGroupUserCount.Columns.closedGroupUserCount) FROM \(GroupMember.self) WHERE ( \(SQL("\(groupMember[.groupId]) = \(threadId)")) AND - \(SQL("\(groupMember[.role]) = \(GroupMember.Role.standard)")) + \(SQL("\(groupMember[.role]) != \(GroupMember.Role.zombie)")) ) ) AS \(closedGroupUserCount) ON \(SQL("\(closedGroupUserCount[.groupId]) = \(threadId)")) diff --git a/SessionMessagingKit/Utilities/MessageWrapper.swift b/SessionMessagingKit/Utilities/MessageWrapper.swift index fd9711c67..fdca43953 100644 --- a/SessionMessagingKit/Utilities/MessageWrapper.swift +++ b/SessionMessagingKit/Utilities/MessageWrapper.swift @@ -25,7 +25,7 @@ public enum MessageWrapper { public static func wrap( type: SNProtoEnvelope.SNProtoEnvelopeType, timestamp: UInt64, - senderPublicKey: String, + senderPublicKey: String = "", // FIXME: Remove once legacy groups are deprecated base64EncodedContent: String, wrapInWebSocketMessage: Bool = true ) throws -> Data { diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index 24487feb2..737b1576e 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -30,7 +30,6 @@ public struct ProfileManager { private static let avatarTagLength: Int = 16 private static var profileAvatarCache: Atomic<[String: Data]> = Atomic([:]) - private static var currentAvatarDownloads: Atomic> = Atomic([]) private static var downloadsToSchedule: Atomic> = Atomic([]) private static var scheduleDownloadsPublisher: AnyPublisher? diff --git a/SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift b/SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift index e9a7647c5..2f4d4407c 100644 --- a/SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift @@ -12,6 +12,8 @@ import Nimble @testable import SessionMessagingKit class LibSessionSpec: QuickSpec { + static let maxMessageSizeBytes: Int = 76800 // Storage server's limit, should match `config.hpp` in libSession + // FIXME: Would be good to move the identity generation into the libSession-util instead of using Sodium separately static let userSeed: Data = Data(hex: "0123456789abcdef0123456789abcdef") static let seed: Data = Data( @@ -164,7 +166,7 @@ fileprivate extension LibSessionSpec { context("when checking error catching") { // MARK: ---- it can catch size limit errors thrown when pushing it("can catch size limit errors thrown when pushing") { - try (0..<10000).forEach { index in + try (0..<2500).forEach { index in var contact: contacts_contact = try createContact( for: index, in: conf, @@ -174,7 +176,7 @@ fileprivate extension LibSessionSpec { contacts_set(conf, &contact) } - expect(contacts_size(conf)).to(equal(10000)) + expect(contacts_size(conf)).to(equal(2500)) expect(config_needs_push(conf)).to(beTrue()) expect(config_needs_dump(conf)).to(beTrue()) @@ -189,7 +191,7 @@ fileprivate extension LibSessionSpec { context("when checking size limits") { // MARK: ---- has not changed the max empty records it("has not changed the max empty records") { - for index in (0..<100000) { + for index in (0..<2500) { var contact: contacts_contact = try createContact( for: index, in: conf, @@ -210,7 +212,7 @@ fileprivate extension LibSessionSpec { // MARK: ---- has not changed the max name only records it("has not changed the max name only records") { - for index in (0..<100000) { + for index in (0..<2500) { var contact: contacts_contact = try createContact( for: index, in: conf, @@ -232,7 +234,7 @@ fileprivate extension LibSessionSpec { // MARK: ---- has not changed the max name and profile pic only records it("has not changed the max name and profile pic only records") { - for index in (0..<100000) { + for index in (0..<2500) { var contact: contacts_contact = try createContact( for: index, in: conf, @@ -254,7 +256,7 @@ fileprivate extension LibSessionSpec { // MARK: ---- has not changed the max filled records it("has not changed the max filled records") { - for index in (0..<100000) { + for index in (0..<2500) { var contact: contacts_contact = try createContact( for: index, in: conf, @@ -417,9 +419,12 @@ fileprivate extension LibSessionSpec { var mergeHashes: [UnsafePointer?] = [cFakeHash2].unsafeCopy() var mergeData: [UnsafePointer?] = [UnsafePointer(pushData4.pointee.config)] var mergeSize: [Int] = [pushData4.pointee.config_len] - expect(config_merge(conf, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1)) + let mergedHashes: UnsafeMutablePointer? = config_merge(conf, &mergeHashes, &mergeData, &mergeSize, 1) + expect([String](pointer: mergedHashes?.pointee.value, count: mergedHashes?.pointee.len)) + .to(equal(["fakehash2"])) config_confirm_pushed(conf2, pushData4.pointee.seqno, &cFakeHash2) mergeHashes.forEach { $0?.deallocate() } + mergedHashes?.deallocate() pushData4.deallocate() expect(config_needs_push(conf)).to(beFalse()) @@ -493,18 +498,24 @@ fileprivate extension LibSessionSpec { var mergeHashes2: [UnsafePointer?] = [cFakeHash3b].unsafeCopy() var mergeData2: [UnsafePointer?] = [UnsafePointer(pushData7.pointee.config)] var mergeSize2: [Int] = [pushData7.pointee.config_len] - expect(config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1)) + let mergedHashes2: UnsafeMutablePointer? = config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1) + expect([String](pointer: mergedHashes2?.pointee.value, count: mergedHashes2?.pointee.len)) + .to(equal(["fakehash3b"])) expect(config_needs_push(conf)).to(beTrue()) + mergeHashes2.forEach { $0?.deallocate() } + mergedHashes2?.deallocate() + pushData7.deallocate() var mergeHashes3: [UnsafePointer?] = [cFakeHash3a].unsafeCopy() var mergeData3: [UnsafePointer?] = [UnsafePointer(pushData6.pointee.config)] var mergeSize3: [Int] = [pushData6.pointee.config_len] - expect(config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 1)).to(equal(1)) + let mergedHashes3: UnsafeMutablePointer? = config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 1) + expect([String](pointer: mergedHashes3?.pointee.value, count: mergedHashes3?.pointee.len)) + .to(equal(["fakehash3a"])) expect(config_needs_push(conf2)).to(beTrue()) - mergeHashes2.forEach { $0?.deallocate() } mergeHashes3.forEach { $0?.deallocate() } + mergedHashes3?.deallocate() pushData6.deallocate() - pushData7.deallocate() let pushData8: UnsafeMutablePointer = config_push(conf) expect(pushData8.pointee.seqno).to(equal(4)) @@ -634,35 +645,32 @@ fileprivate extension LibSessionSpec { // We don't need to push since we haven't changed anything, so this call is mainly just for // testing: - let PROTOBUF_OVERHEAD: Int = 28 // To be removed once we no longer protobuf wrap this - let PROTOBUF_DATA_OFFSET: Int = 26 + let PROTOBUF_OVERHEAD: Int = 176 // To be removed once we no longer protobuf wrap this let pushData1: UnsafeMutablePointer = config_push(conf) expect(pushData1.pointee).toNot(beNil()) expect(pushData1.pointee.seqno).to(equal(0)) expect(pushData1.pointee.config_len).to(equal(256 + PROTOBUF_OVERHEAD)) - - let encDomain: [CChar] = "UserProfile" - .bytes - .map { CChar(bitPattern: $0) } expect(String(cString: config_encryption_domain(conf))).to(equal("UserProfile")) - var toPushDecSize: Int = 0 - let toPushDecrypted: UnsafeMutablePointer? = config_decrypt( - pushData1.pointee.config.advanced(by: PROTOBUF_DATA_OFFSET), - (pushData1.pointee.config_len - PROTOBUF_OVERHEAD), - userEdSK, - encDomain, - &toPushDecSize - ) - let prefixPadding: String = (0..<193) - .map { _ in "\0" } - .joined() - expect(toPushDecrypted).toNot(beNil()) - expect(toPushDecSize).to(equal(216)) // 256 - 40 overhead - expect(String(pointer: toPushDecrypted, length: toPushDecSize)) - .to(equal("\(prefixPadding)d1:#i0e1:&de1:? = config_decrypt( - pushData2.pointee.config.advanced(by: PROTOBUF_DATA_OFFSET), - (pushData2.pointee.config_len - PROTOBUF_OVERHEAD), - userEdSK, - encDomain, - &pushData2DecSize - ) - let prefixPadding2: String = (0..<(256 - 40 - expPush1Decrypted.count)) - .map { _ in "\0" } - .joined() - expect(pushData2DecSize).to(equal(216)) // 256 - 40 overhead - - let pushData2DecryptedStr: String = String(pointer: pushData2Decrypted, length: pushData2DecSize, encoding: .ascii)! - let expPush1DecryptedStr: String = String(pointer: expPush1Decrypted, length: expPush1Decrypted.count, encoding: .ascii) - .map { "\(prefixPadding2)\($0)" }! - expect(pushData2DecryptedStr).to(equal(expPush1DecryptedStr)) - pushData2Decrypted?.deallocate() - // We haven't dumped, so still need to dump: expect(config_needs_dump(conf)).to(beTrue()) // We did call push, but we haven't confirmed it as stored yet, so this will still return true: @@ -851,14 +831,16 @@ fileprivate extension LibSessionSpec { expect(user_profile_init(&conf2, &userEdSK, nil, 0, &error2)).to(equal(0)) expect(config_needs_dump(conf2)).to(beFalse()) - // Now imagine we just pulled down the `exp_push1` string from the swarm; we merge it into - // conf2: + // Now imagine we just pulled down the encrypted string from the swarm; we merge it into conf2: var mergeHashes: [UnsafePointer?] = [cFakeHash1].unsafeCopy() var mergeData: [UnsafePointer?] = [expPush1Encrypted].unsafeCopy() var mergeSize: [Int] = [expPush1Encrypted.count] - expect(config_merge(conf2, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1)) + let mergedHashes: UnsafeMutablePointer? = config_merge(conf2, &mergeHashes, &mergeData, &mergeSize, 1) + expect([String](pointer: mergedHashes?.pointee.value, count: mergedHashes?.pointee.len)) + .to(equal(["fakehash1"])) mergeHashes.forEach { $0?.deallocate() } mergeData.forEach { $0?.deallocate() } + mergedHashes?.deallocate() // Our state has changed, so we need to dump: expect(config_needs_dump(conf2)).to(beTrue()) @@ -937,12 +919,21 @@ fileprivate extension LibSessionSpec { var mergeHashes2: [UnsafePointer?] = [cFakeHash2].unsafeCopy() var mergeData2: [UnsafePointer?] = [UnsafePointer(pushData3.pointee.config)] var mergeSize2: [Int] = [pushData3.pointee.config_len] - expect(config_merge(conf2, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1)) + let mergedHashes2: UnsafeMutablePointer? = config_merge(conf2, &mergeHashes2, &mergeData2, &mergeSize2, 1) + expect([String](pointer: mergedHashes2?.pointee.value, count: mergedHashes2?.pointee.len)) + .to(equal(["fakehash2"])) + mergeHashes2.forEach { $0?.deallocate() } + mergedHashes2?.deallocate() pushData3.deallocate() + var mergeHashes3: [UnsafePointer?] = [cFakeHash3].unsafeCopy() var mergeData3: [UnsafePointer?] = [UnsafePointer(pushData4.pointee.config)] var mergeSize3: [Int] = [pushData4.pointee.config_len] - expect(config_merge(conf, &mergeHashes3, &mergeData3, &mergeSize3, 1)).to(equal(1)) + let mergedHashes3: UnsafeMutablePointer? = config_merge(conf, &mergeHashes3, &mergeData3, &mergeSize3, 1) + expect([String](pointer: mergedHashes3?.pointee.value, count: mergedHashes3?.pointee.len)) + .to(equal(["fakehash3"])) + mergeHashes3.forEach { $0?.deallocate() } + mergedHashes3?.deallocate() pushData4.deallocate() // Now after the merge we *will* want to push from both client, since both will have generated a @@ -1157,8 +1148,12 @@ fileprivate extension LibSessionSpec { var mergeHashes: [UnsafePointer?] = [cFakeHash2].unsafeCopy() var mergeData: [UnsafePointer?] = [UnsafePointer(pushData2.pointee.config)] var mergeSize: [Int] = [pushData2.pointee.config_len] - expect(config_merge(conf, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1)) + let mergedHashes: UnsafeMutablePointer? = config_merge(conf, &mergeHashes, &mergeData, &mergeSize, 1) + expect([String](pointer: mergedHashes?.pointee.value, count: mergedHashes?.pointee.len)) + .to(equal(["fakehash2"])) config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash2) + mergeHashes.forEach { $0?.deallocate() } + mergedHashes?.deallocate() pushData2.deallocate() expect(config_needs_push(conf)).to(beFalse()) @@ -1594,7 +1589,11 @@ fileprivate extension LibSessionSpec { var mergeHashes1: [UnsafePointer?] = [cFakeHash2].unsafeCopy() var mergeData1: [UnsafePointer?] = [UnsafePointer(pushData8.pointee.config)] var mergeSize1: [Int] = [pushData8.pointee.config_len] - expect(config_merge(conf, &mergeHashes1, &mergeData1, &mergeSize1, 1)).to(equal(1)) + let mergedHashes1: UnsafeMutablePointer? = config_merge(conf, &mergeHashes1, &mergeData1, &mergeSize1, 1) + expect([String](pointer: mergedHashes1?.pointee.value, count: mergedHashes1?.pointee.len)) + .to(equal(["fakehash2"])) + mergeHashes1.forEach { $0?.deallocate() } + mergedHashes1?.deallocate() pushData8.deallocate() var cCommunity3BaseUrl: [CChar] = "http://example.org:5678".cArray.nullTerminated() @@ -1647,7 +1646,11 @@ fileprivate extension LibSessionSpec { var mergeHashes2: [UnsafePointer?] = [cFakeHash3].unsafeCopy() var mergeData2: [UnsafePointer?] = [UnsafePointer(pushData10.pointee.config)] var mergeSize2: [Int] = [pushData10.pointee.config_len] - expect(config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1)) + let mergedHashes2: UnsafeMutablePointer? = config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1) + expect([String](pointer: mergedHashes2?.pointee.value, count: mergedHashes2?.pointee.len)) + .to(equal(["fakehash3"])) + mergeHashes2.forEach { $0?.deallocate() } + mergedHashes2?.deallocate() expect(user_groups_size(conf)).to(equal(1)) expect(user_groups_size_communities(conf)).to(equal(0)) @@ -1702,9 +1705,13 @@ fileprivate extension LibSessionSpec { pushData7.pointee.config_len, pushData11.pointee.config_len ] - expect(config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 4)).to(equal(4)) + let mergedHashes3: UnsafeMutablePointer? = config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 4) + expect([String](pointer: mergedHashes3?.pointee.value, count: mergedHashes3?.pointee.len)) + .to(equal(["fakehash10", "fakehash11", "fakehash12", "fakehash4"])) expect(config_needs_dump(conf2)).to(beTrue()) expect(config_needs_push(conf2)).to(beFalse()) + mergeHashes3.forEach { $0?.deallocate() } + mergedHashes3?.deallocate() pushData2.deallocate() pushData7.deallocate() pushData10.deallocate() @@ -1825,9 +1832,12 @@ fileprivate extension LibSessionSpec { var mergeHashes1: [UnsafePointer?] = [cFakeHash1].unsafeCopy() var mergeData1: [UnsafePointer?] = [UnsafePointer(pushData1.pointee.config)] var mergeSize1: [Int] = [pushData1.pointee.config_len] - expect(config_merge(conf2, &mergeHashes1, &mergeData1, &mergeSize1, 1)).to(equal(1)) + let mergedHashes1: UnsafeMutablePointer? = config_merge(conf2, &mergeHashes1, &mergeData1, &mergeSize1, 1) + expect([String](pointer: mergedHashes1?.pointee.value, count: mergedHashes1?.pointee.len)) + .to(equal(["fakehash1"])) expect(config_needs_push(conf2)).to(beFalse()) mergeHashes1.forEach { $0?.deallocate() } + mergedHashes1?.deallocate() pushData1.deallocate() let namePtr: UnsafePointer? = groups_info_get_name(conf2) @@ -1867,8 +1877,11 @@ fileprivate extension LibSessionSpec { var mergeHashes2: [UnsafePointer?] = [cFakeHash2].unsafeCopy() var mergeData2: [UnsafePointer?] = [UnsafePointer(pushData2.pointee.config)] var mergeSize2: [Int] = [pushData2.pointee.config_len] - expect(config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1)) + let mergedHashes2: UnsafeMutablePointer? = config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1) + expect([String](pointer: mergedHashes2?.pointee.value, count: mergedHashes2?.pointee.len)) + .to(equal(["fakehash2"])) mergeHashes2.forEach { $0?.deallocate() } + mergedHashes2?.deallocate() expect(config_needs_push(conf)).to(beTrue()) @@ -1896,8 +1909,11 @@ fileprivate extension LibSessionSpec { var mergeHashes3: [UnsafePointer?] = [cFakeHash3].unsafeCopy() var mergeData3: [UnsafePointer?] = [UnsafePointer(pushData3.pointee.config)] var mergeSize3: [Int] = [pushData3.pointee.config_len] - expect(config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 1)).to(equal(1)) + let mergedHashes3: UnsafeMutablePointer? = config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 1) + expect([String](pointer: mergedHashes3?.pointee.value, count: mergedHashes3?.pointee.len)) + .to(equal(["fakehash3"])) mergeHashes3.forEach { $0?.deallocate() } + mergedHashes3?.deallocate() pushData3.deallocate() let namePtr3: UnsafePointer? = groups_info_get_name(conf2) @@ -1991,7 +2007,7 @@ fileprivate extension LibSessionSpec { it("can catch size limit errors thrown when pushing") { var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) - try (0..<10000).forEach { index in + try (0..<2500).forEach { index in var member: config_group_member = try createMember( for: index, in: conf, @@ -2001,7 +2017,7 @@ fileprivate extension LibSessionSpec { groups_members_set(conf, &member) } - expect(groups_members_size(conf)).to(equal(10000)) + expect(groups_members_size(conf)).to(equal(2500)) expect(config_needs_push(conf)).to(beTrue()) expect(config_needs_dump(conf)).to(beTrue()) @@ -2018,7 +2034,7 @@ fileprivate extension LibSessionSpec { it("has not changed the max empty records") { var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) - for index in (0..<100000) { + for index in (0..<2500) { var member: config_group_member = try createMember( for: index, in: conf, @@ -2041,7 +2057,7 @@ fileprivate extension LibSessionSpec { it("has not changed the max name only records") { var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) - for index in (0..<100000) { + for index in (0..<2500) { var member: config_group_member = try createMember( for: index, in: conf, @@ -2065,7 +2081,7 @@ fileprivate extension LibSessionSpec { it("has not changed the max name and profile pic only records") { var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) - for index in (0..<100000) { + for index in (0..<2500) { var member: config_group_member = try createMember( for: index, in: conf, @@ -2089,7 +2105,7 @@ fileprivate extension LibSessionSpec { it("has not changed the max filled records") { var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) - for index in (0..<100000) { + for index in (0..<2500) { var member: config_group_member = try createMember( for: index, in: conf, @@ -2185,9 +2201,12 @@ fileprivate extension LibSessionSpec { var mergeHashes1: [UnsafePointer?] = [cFakeHash1].unsafeCopy() var mergeData1: [UnsafePointer?] = [UnsafePointer(pushData1.pointee.config)] var mergeSize1: [Int] = [pushData1.pointee.config_len] - expect(config_merge(conf2, &mergeHashes1, &mergeData1, &mergeSize1, 1)).to(equal(1)) + let mergedHashes1: UnsafeMutablePointer? = config_merge(conf2, &mergeHashes1, &mergeData1, &mergeSize1, 1) + expect([String](pointer: mergedHashes1?.pointee.value, count: mergedHashes1?.pointee.len)) + .to(equal(["fakehash1"])) expect(config_needs_push(conf2)).to(beFalse()) mergeHashes1.forEach { $0?.deallocate() } + mergedHashes1?.deallocate() pushData1.deallocate() expect(groups_members_size(conf2)).to(equal(25)) @@ -2284,8 +2303,11 @@ fileprivate extension LibSessionSpec { var mergeHashes2: [UnsafePointer?] = [cFakeHash2].unsafeCopy() var mergeData2: [UnsafePointer?] = [UnsafePointer(pushData2.pointee.config)] var mergeSize2: [Int] = [pushData2.pointee.config_len] - expect(config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1)) + let mergedHashes2: UnsafeMutablePointer? = config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1) + expect([String](pointer: mergedHashes2?.pointee.value, count: mergedHashes2?.pointee.len)) + .to(equal(["fakehash2"])) mergeHashes2.forEach { $0?.deallocate() } + mergedHashes2?.deallocate() var cSessionId2: [CChar] = sids[23].cArray var member2: config_group_member = config_group_member() @@ -2429,9 +2451,12 @@ fileprivate extension LibSessionSpec { var mergeHashes3: [UnsafePointer?] = [cFakeHash3].unsafeCopy() var mergeData3: [UnsafePointer?] = [UnsafePointer(pushData3.pointee.config)] var mergeSize3: [Int] = [pushData3.pointee.config_len] - expect(config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 1)).to(equal(1)) + let mergedHashes3: UnsafeMutablePointer? = config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 1) + expect([String](pointer: mergedHashes3?.pointee.value, count: mergedHashes3?.pointee.len)) + .to(equal(["fakehash3"])) mergeHashes3.forEach { $0?.deallocate() } - + mergedHashes3?.deallocate() + expect(groups_members_size(conf2)).to(equal(44)) // 18 deleted earlier (0..<62).forEach { index in @@ -2592,6 +2617,75 @@ fileprivate extension LibSessionSpec { class func groupKeysSpec() { context("GROUP_KEYS") { + @TestState var userEdSK: [UInt8]! = LibSessionSpec.userEdSK + @TestState var edPK: [UInt8]! = LibSessionSpec.edPK + @TestState var edSK: [UInt8]! = LibSessionSpec.edSK + @TestState var error: [CChar]! = [CChar](repeating: 0, count: 256) + @TestState var infoConf: UnsafeMutablePointer? + @TestState var membersConf: UnsafeMutablePointer? + @TestState var keysConf: UnsafeMutablePointer? + @TestState var infoInitResult: Int32! = { + groups_info_init(&infoConf, &edPK, &edSK, nil, 0, &error) + }() + @TestState var membersInitResult: Int32! = { + groups_members_init(&membersConf, &edPK, &edSK, nil, 0, &error) + }() + @TestState var keysInitResult: Int32! = { + LibSessionSpec.initKeysConf(&keysConf, &infoConf, &membersConf) + }() + + @TestState var membersConf2: UnsafeMutablePointer? + @TestState var keysConf2: UnsafeMutablePointer? + @TestState var membersInitResult2: Int32! = { + groups_members_init(&membersConf2, &edPK, &edSK, nil, 0, &error) + }() + @TestState var keysInitResult2: Int32! = { + LibSessionSpec.initKeysConf(&keysConf2, &infoConf, &membersConf2) + }() + @TestState var numRecords: Int! = 0 + + // Convenience + var conf: UnsafeMutablePointer? { keysConf } + var conf2: UnsafeMutablePointer? { keysConf2 } + + // MARK: - when checking error catching + context("when checking error catching") { + // MARK: -- does not throw size exceptions when generating + it("does not throw size exceptions when generating") { + var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) + var pushResultLen: Int = 0 + + // It's actually the number of members which can cause the keys message to get too large so + // start by generating too many members + try (0..<1750).forEach { index in + var member: config_group_member = try createMember( + for: index, + in: membersConf, + rand: &randomGenerator, + maxing: .allProperties + ) + groups_members_set(membersConf, &member) + } + + expect { + try CExceptionHelper.performSafely { + var pushResult: UnsafePointer? = nil + expect(groups_keys_rekey( + conf, + infoConf, + membersConf, + &pushResult, + &pushResultLen + )).to(beTrue()) + } + } + .toNot(throwError(NSError(domain: "cpp_exception", code: -2, userInfo: ["NSLocalizedDescription": "Config data is too large"]))) + + expect(pushResultLen).to(beGreaterThan(LibSessionSpec.maxMessageSizeBytes)) + expect(groups_keys_needs_dump(conf)).to(beTrue()) + } + } + // MARK: -- generates config correctly it("generates config correctly") { let userSeed: Data = Data(hex: "0123456789abcdef0123456789abcdef")