Attempts to fix more crashes and fix unit tests

• Fixed an index out of bounds issue with the mnemonic
• Attempt to fix a crash due to failing to hash SignalAttachment
• Attempt to fix a crash due to list data change inconsistencies
• Ensuring we are shutting down the network when resetting app data
• Updated the broken unit tests
pull/976/head
Morgan Pretty 11 months ago
parent 3b5eaa6bbb
commit f9d9954cf5

@ -809,6 +809,7 @@
FDBB25E32988B13800F1508E /* _004_AddJobPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E22988B13800F1508E /* _004_AddJobPriority.swift */; };
FDBB25E72988BBBE00F1508E /* UIContextualAction+Theming.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E62988BBBD00F1508E /* UIContextualAction+Theming.swift */; };
FDC0F0042BFECE12002CBFB9 /* TimeUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC0F0032BFECE12002CBFB9 /* TimeUnit.swift */; };
FDC0F0082C00721A002CBFB9 /* MockOGPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC0F0072C00721A002CBFB9 /* MockOGPoller.swift */; };
FDC13D472A16E4CA007267C7 /* SubscribeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D462A16E4CA007267C7 /* SubscribeRequest.swift */; };
FDC13D492A16EC20007267C7 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D482A16EC20007267C7 /* Service.swift */; };
FDC13D4B2A16ECBA007267C7 /* SubscribeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D4A2A16ECBA007267C7 /* SubscribeResponse.swift */; };
@ -1996,6 +1997,7 @@
FDBB25E22988B13800F1508E /* _004_AddJobPriority.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _004_AddJobPriority.swift; sourceTree = "<group>"; };
FDBB25E62988BBBD00F1508E /* UIContextualAction+Theming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Theming.swift"; sourceTree = "<group>"; };
FDC0F0032BFECE12002CBFB9 /* TimeUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeUnit.swift; sourceTree = "<group>"; };
FDC0F0072C00721A002CBFB9 /* MockOGPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockOGPoller.swift; sourceTree = "<group>"; };
FDC13D462A16E4CA007267C7 /* SubscribeRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscribeRequest.swift; sourceTree = "<group>"; };
FDC13D482A16EC20007267C7 /* Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Service.swift; sourceTree = "<group>"; };
FDC13D4A2A16ECBA007267C7 /* SubscribeResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscribeResponse.swift; sourceTree = "<group>"; };
@ -4434,6 +4436,7 @@
children = (
FDC438BC27BB2AB400C60D73 /* Mockable.swift */,
FD078E4C27E17156000769AF /* MockOGMCache.swift */,
FDC0F0072C00721A002CBFB9 /* MockOGPoller.swift */,
);
path = _TestUtilities;
sourceTree = "<group>";
@ -6614,6 +6617,7 @@
FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */,
FD078E4D27E17156000769AF /* MockOGMCache.swift in Sources */,
FD9DD2722A72516D00ECB68E /* TestExtensions.swift in Sources */,
FDC0F0082C00721A002CBFB9 /* MockOGPoller.swift in Sources */,
FDC290A627D860CE005DAE71 /* Mock.swift in Sources */,
FDA1E83D29AC71A800C5C3BD /* LibSessionSpec.swift in Sources */,
FD83B9C027CF2294005E1583 /* TestConstants.swift in Sources */,
@ -7981,7 +7985,7 @@
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
CURRENT_PROJECT_VERSION = 446;
CURRENT_PROJECT_VERSION = 447;
ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@ -8059,7 +8063,7 @@
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CURRENT_PROJECT_VERSION = 446;
CURRENT_PROJECT_VERSION = 447;
ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_NO_COMMON_BLOCKS = YES;

@ -416,7 +416,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
initialUnreadInteractionId: self?.initialUnreadInteractionId
),
currentDataRetriever: { self?.interactionData },
onDataChange: self?.onInteractionChange,
onDataChangeRetriever: { self?.onInteractionChange },
onUnobservedDataChange: { updatedData, changeset in
self?.unobservedInteractionDataChanges = (changeset.isEmpty ?
nil :
@ -671,6 +671,11 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
}
private func forceUpdateDataIfPossible() {
// Ensure this is on the main thread as we access properties that could be accessed on other threads
guard Thread.isMainThread else {
return DispatchQueue.main.async { [weak self] in self?.forceUpdateDataIfPossible() }
}
// If we can't get the current page data then don't bother trying to update (it's not going to work)
guard let currentPageInfo: PagedData.PageInfo = self.pagedDataObserver?.pageInfo.wrappedValue else { return }
@ -685,7 +690,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
initialUnreadInteractionId: initialUnreadInteractionId
),
currentDataRetriever: { [weak self] in self?.interactionData },
onDataChange: self.onInteractionChange,
onDataChangeRetriever: { [weak self] in self?.onInteractionChange },
onUnobservedDataChange: { [weak self] updatedData, changeset in
self?.unobservedInteractionDataChanges = (changeset.isEmpty ?
nil :

@ -207,7 +207,7 @@ public class HomeViewModel {
PagedData.processAndTriggerUpdates(
updatedData: self?.process(data: updatedData, for: updatedPageInfo),
currentDataRetriever: { self?.threadData },
onDataChange: self?.onThreadChange,
onDataChangeRetriever: { self?.onThreadChange },
onUnobservedDataChange: { updatedData, changeset in
self?.unobservedThreadDataChanges = (changeset.isEmpty ?
nil :
@ -286,7 +286,7 @@ public class HomeViewModel {
PagedData.processAndTriggerUpdates(
updatedData: updatedThreadData,
currentDataRetriever: { [weak self] in (self?.unobservedThreadDataChanges?.0 ?? self?.threadData) },
onDataChange: onThreadChange,
onDataChangeRetriever: { [weak self] in self?.onThreadChange },
onUnobservedDataChange: { [weak self] updatedData, changeset in
self?.unobservedThreadDataChanges = (changeset.isEmpty ?
nil :

@ -103,7 +103,7 @@ public class MediaGalleryViewModel {
PagedData.processAndTriggerUpdates(
updatedData: self?.process(data: updatedData, for: updatedPageInfo),
currentDataRetriever: { self?.galleryData },
onDataChange: self?.onGalleryChange,
onDataChangeRetriever: { self?.onGalleryChange },
onUnobservedDataChange: { updatedData, changeset in
self?.unobservedGalleryDataChanges = (changeset.isEmpty ?
nil :

@ -493,7 +493,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
guard !Storage.isDatabasePasswordAccessible else { return } // All good
Logger.info("Exiting because we are in the background and the database password is not accessible.")
Log.info("Exiting because we are in the background and the database password is not accessible.")
let notificationContent: UNMutableNotificationContent = UNMutableNotificationContent()
notificationContent.body = String(

@ -114,6 +114,8 @@ public struct SessionApp {
public static func resetAppData(onReset: (() -> ())? = nil) {
Log.flush()
LibSession.clearMemoryState()
LibSession.clearSnodeCache()
LibSession.closeNetworkConnections()
Storage.resetAllStorage()
ProfileManager.resetProfileStorage()
Attachment.resetAttachmentStorage()

@ -64,7 +64,7 @@ public class BlockedContactsViewModel: SessionTableViewModel, NavigatableStateHo
onChangeUnsorted: { [weak self] updatedData, updatedPageInfo in
PagedData.processAndTriggerUpdates(
updatedData: self?.process(data: updatedData, for: updatedPageInfo)
.mapToSessionTableViewData(for: self),
.mapToSessionTableViewData(for: self), // Update the cell positions for background rounding
currentDataRetriever: { self?.tableData },
valueSubject: self?.pendingTableDataSubject
)

@ -282,9 +282,6 @@ final class NukeDataModal: Modal {
$0.recentReactionTimestamps = []
}
// Clear the Snode pool
LibSession.clearSnodeCache()
// Stop any pollers
(UIApplication.shared.delegate as? AppDelegate)?.stopPollers()

@ -41,23 +41,21 @@ public final class OpenGroupManager {
// Update the cache state and re-create all of the pollers
dependencies.caches.mutate(cache: .openGroupManager) { cache in
cache.isPolling = true
cache.pollers = servers
.reduce(into: [:]) { result, server in
result[server.lowercased()]?.stop() // Should never occur
result[server.lowercased()] = OpenGroupAPI.Poller(for: server.lowercased())
servers
.map { server -> OpenGroupAPI.PollerType in cache.getOrCreatePoller(for: server.lowercased()) }
.forEach { poller in
// Now that the pollers have been created actually start them
poller.stop() // Should never be an issue
poller.startIfNeeded(using: dependencies)
}
}
// Now that the pollers have been created actually start them
dependencies.caches[.openGroupManager].pollers
.forEach { _, poller in poller.startIfNeeded(using: dependencies) }
}
}
public func stopPolling(using dependencies: Dependencies = Dependencies()) {
dependencies.caches.mutate(cache: .openGroupManager) {
$0.pollers.forEach { _, openGroupPoller in openGroupPoller.stop() }
$0.pollers.removeAll()
$0.stopAndRemoveAllPollers()
$0.isPolling = false
}
}
@ -142,7 +140,7 @@ public final class OpenGroupManager {
}
// First check if there is no poller for the specified server
if Set(dependencies.caches[.openGroupManager].pollers.keys).intersection(serverOptions).isEmpty {
if Set(dependencies.caches[.openGroupManager].serversBeingPolled).intersection(serverOptions).isEmpty {
return false
}
@ -295,16 +293,8 @@ public final class OpenGroupManager {
OpenGroupAPI.workQueue.async(using: dependencies) {
// (Re)start the poller if needed (want to force it to poll immediately in the next
// run loop to avoid a big delay before the next poll)
let poller: OpenGroupAPI.Poller = dependencies.caches.mutate(cache: .openGroupManager) {
// Don't create a new poller instance if one already exists so we don't
// double up on pollers
guard let poller: OpenGroupAPI.Poller = $0.pollers[server.lowercased()] else {
let poller: OpenGroupAPI.Poller = OpenGroupAPI.Poller(for: server.lowercased())
$0.pollers[server.lowercased()] = poller
return poller
}
return poller
let poller: OpenGroupAPI.PollerType = dependencies.caches.mutate(cache: .openGroupManager) {
$0.getOrCreatePoller(for: server.lowercased())
}
poller.stop()
@ -356,9 +346,9 @@ public final class OpenGroupManager {
.defaulting(to: 1)
if numActiveRooms == 1, let server: String = server?.lowercased() {
let poller = dependencies.caches[.openGroupManager].pollers[server]
poller?.stop()
dependencies.caches.mutate(cache: .openGroupManager) { $0.pollers[server] = nil }
dependencies.caches.mutate(cache: .openGroupManager) {
$0.stopAndRemovePoller(for: server)
}
}
// Remove all the data (everything should cascade delete)
@ -1230,12 +1220,31 @@ public extension OpenGroupManager {
public var defaultRoomsPublisher: AnyPublisher<[DefaultRoomInfo], Error>?
public var groupImagePublishers: [String: AnyPublisher<Data, Error>] = [:]
public var pollers: [String: OpenGroupAPI.Poller] = [:] // One for each server
public var isPolling: Bool = false
public var serversBeingPolled: Set<String> { return Set(_pollers.keys) }
/// Server URL to value
public var hasPerformedInitialPoll: [String: Bool] = [:]
public var timeSinceLastPoll: [String: TimeInterval] = [:]
private var _pollers: [String: OpenGroupAPI.PollerType] = [:] // One for each server
public func getOrCreatePoller(for server: String) -> OpenGroupAPI.PollerType {
guard let poller: OpenGroupAPI.PollerType = _pollers[server.lowercased()] else {
let poller: OpenGroupAPI.Poller = OpenGroupAPI.Poller(for: server.lowercased())
_pollers[server.lowercased()] = poller
return poller
}
return poller
}
public func stopAndRemovePoller(for server: String) {
_pollers[server.lowercased()]?.stop()
_pollers[server.lowercased()] = nil
}
public func stopAndRemoveAllPollers() {
_pollers.forEach { _, poller in poller.stop() }
_pollers.removeAll()
}
fileprivate var _timeSinceLastOpen: TimeInterval?
public func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval {
@ -1272,8 +1281,8 @@ public protocol OGMImmutableCacheType: ImmutableCacheType {
var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error>? { get }
var groupImagePublishers: [String: AnyPublisher<Data, Error>] { get }
var pollers: [String: OpenGroupAPI.Poller] { get }
var isPolling: Bool { get }
var serversBeingPolled: Set<String> { get }
var hasPerformedInitialPoll: [String: Bool] { get }
var timeSinceLastPoll: [String: TimeInterval] { get }
@ -1285,13 +1294,17 @@ public protocol OGMCacheType: OGMImmutableCacheType, MutableCacheType {
var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error>? { get set }
var groupImagePublishers: [String: AnyPublisher<Data, Error>] { get set }
var pollers: [String: OpenGroupAPI.Poller] { get set }
var isPolling: Bool { get set }
var serversBeingPolled: Set<String> { get }
var hasPerformedInitialPoll: [String: Bool] { get set }
var timeSinceLastPoll: [String: TimeInterval] { get set }
var pendingChanges: [OpenGroupAPI.PendingChange] { get set }
func getOrCreatePoller(for server: String) -> OpenGroupAPI.PollerType
func stopAndRemovePoller(for server: String)
func stopAndRemoveAllPollers()
func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval
}

@ -1133,14 +1133,21 @@ public class SignalAttachment: Equatable, Hashable {
// MARK: - Hashable
public func hash(into hasher: inout Hasher) {
dataSource.hash(into: &hasher)
dataUTI.hash(into: &hasher)
captionText.hash(into: &hasher)
linkPreviewDraft.hash(into: &hasher)
isConvertibleToTextMessage.hash(into: &hasher)
isConvertibleToContactShare.hash(into: &hasher)
cachedImage.hash(into: &hasher)
cachedVideoPreview.hash(into: &hasher)
isVoiceMessage.hash(into: &hasher)
/// There was a crash in `AttachmentApprovalViewController when trying to generate the hash
/// value to store in a dictionary, I'm guessing it's due to either `dataSource`, `cachedImage` or `cachedVideoPreview`
/// so, instead of trying to hash them directly which involves unknown behaviours due to `NSObject` & `UIImage` types, this
/// has been reworked to use primitives
dataSource.sourceFilename.hash(into: &hasher)
cachedImage?.size.width.hash(into: &hasher)
cachedImage?.size.height.hash(into: &hasher)
cachedVideoPreview?.size.width.hash(into: &hasher)
cachedVideoPreview?.size.height.hash(into: &hasher)
}
}

@ -7,7 +7,12 @@ import SessionSnodeKit
import SessionUtilitiesKit
extension OpenGroupAPI {
public final class Poller {
public protocol PollerType {
func startIfNeeded(using dependencies: Dependencies)
func stop()
}
public final class Poller: PollerType {
typealias PollResponse = (info: ResponseInfoType, data: [OpenGroupAPI.Endpoint: Decodable])
private let server: String

@ -305,7 +305,7 @@ fileprivate extension LibSessionUtilSpec {
// Empty contacts shouldn't have an existing contact
let definitelyRealId: String = "050000000000000000000000000000000000000000000000000000000000000000"
var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated()
var cDefinitelyRealId: [CChar] = definitelyRealId.cString(using: .utf8)!
let contactPtr: UnsafeMutablePointer<contacts_contact>? = nil
expect(contacts_get(conf, contactPtr, &cDefinitelyRealId)).to(beFalse())
@ -374,7 +374,7 @@ fileprivate extension LibSessionUtilSpec {
// Pretend we uploaded it
let fakeHash1: String = "fakehash1"
var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated()
var cFakeHash1: [CChar] = fakeHash1.cString(using: .utf8)!
config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1)
expect(config_needs_push(conf)).to(beFalse())
expect(config_needs_dump(conf)).to(beTrue())
@ -415,7 +415,7 @@ fileprivate extension LibSessionUtilSpec {
expect(contact4.created).to(equal(createdTs))
let anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111"
var cAnotherId: [CChar] = anotherId.cArray.nullTerminated()
var cAnotherId: [CChar] = anotherId.cString(using: .utf8)!
var contact5: contacts_contact = contacts_contact()
expect(contacts_get_or_construct(conf2, &contact5, &cAnotherId)).to(beTrue())
expect(String(libSessionVal: contact5.name)).to(beEmpty())
@ -435,8 +435,8 @@ fileprivate extension LibSessionUtilSpec {
// Check the merging
let fakeHash2: String = "fakehash2"
var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated()
var mergeHashes: [UnsafePointer<CChar>?] = [cFakeHash2].unsafeCopy()
var cFakeHash2: [CChar] = fakeHash2.cString(using: .utf8)!
var mergeHashes: [UnsafePointer<CChar>?] = ((try? [cFakeHash2].unsafeCopyCStringArray()) ?? [])
var mergeData: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData4.pointee.config)]
var mergeSize: [Int] = [pushData4.pointee.config_len]
let mergedHashes: UnsafeMutablePointer<config_string_list>? = config_merge(conf, &mergeHashes, &mergeData, &mergeSize, 1)
@ -481,7 +481,7 @@ fileprivate extension LibSessionUtilSpec {
// Client 2 adds a new friend:
let thirdId: String = "052222222222222222222222222222222222222222222222222222222222222222"
var cThirdId: [CChar] = thirdId.cArray.nullTerminated()
var cThirdId: [CChar] = thirdId.cString(using: .utf8)!
var contact7: contacts_contact = contacts_contact()
expect(contacts_get_or_construct(conf2, &contact7, &cThirdId)).to(beTrue())
contact7.nickname = "Nickname 3".toLibSession()
@ -509,13 +509,13 @@ fileprivate extension LibSessionUtilSpec {
.to(equal([fakeHash2]))
let fakeHash3a: String = "fakehash3a"
var cFakeHash3a: [CChar] = fakeHash3a.cArray.nullTerminated()
var cFakeHash3a: [CChar] = fakeHash3a.cString(using: .utf8)!
let fakeHash3b: String = "fakehash3b"
var cFakeHash3b: [CChar] = fakeHash3b.cArray.nullTerminated()
var cFakeHash3b: [CChar] = fakeHash3b.cString(using: .utf8)!
config_confirm_pushed(conf, pushData6.pointee.seqno, &cFakeHash3a)
config_confirm_pushed(conf2, pushData7.pointee.seqno, &cFakeHash3b)
var mergeHashes2: [UnsafePointer<CChar>?] = [cFakeHash3b].unsafeCopy()
var mergeHashes2: [UnsafePointer<CChar>?] = ((try? [cFakeHash3b].unsafeCopyCStringArray()) ?? [])
var mergeData2: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData7.pointee.config)]
var mergeSize2: [Int] = [pushData7.pointee.config_len]
let mergedHashes2: UnsafeMutablePointer<config_string_list>? = config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)
@ -526,7 +526,7 @@ fileprivate extension LibSessionUtilSpec {
mergedHashes2?.deallocate()
pushData7.deallocate()
var mergeHashes3: [UnsafePointer<CChar>?] = [cFakeHash3a].unsafeCopy()
var mergeHashes3: [UnsafePointer<CChar>?] = ((try? [cFakeHash3a].unsafeCopyCStringArray()) ?? [])
var mergeData3: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData6.pointee.config)]
var mergeSize3: [Int] = [pushData6.pointee.config_len]
let mergedHashes3: UnsafeMutablePointer<config_string_list>? = config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 1)
@ -552,7 +552,7 @@ fileprivate extension LibSessionUtilSpec {
.to(equal([fakeHash3a, fakeHash3b]))
let fakeHash4: String = "fakeHash4"
var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated()
var cFakeHash4: [CChar] = fakeHash4.cString(using: .utf8)!
config_confirm_pushed(conf, pushData8.pointee.seqno, &cFakeHash4)
config_confirm_pushed(conf2, pushData9.pointee.seqno, &cFakeHash4)
pushData8.deallocate()
@ -594,7 +594,7 @@ fileprivate extension LibSessionUtilSpec {
) throws -> contacts_contact {
let postPrefixId: String = "05\(rand.nextBytes(count: 32).toHexString())"
let sessionId: String = ("05\(index)a" + postPrefixId.suffix(postPrefixId.count - "05\(index)a".count))
var cSessionId: [CChar] = sessionId.cArray.nullTerminated()
var cSessionId: [CChar] = sessionId.cString(using: .utf8)!
var contact: contacts_contact = contacts_contact()
guard contacts_get_or_construct(conf, &contact, &cSessionId) else {
@ -717,7 +717,7 @@ fileprivate extension LibSessionUtilSpec {
let pushData2: UnsafeMutablePointer<config_push_data> = config_push(conf)
expect(pushData2.pointee.seqno).to(equal(1))
let expPush1Encrypted: [UInt8] = Data(hex: [
let expPush1Encrypted: [UInt8] = Array(Data(hex: [
"9693a69686da3055f1ecdfb239c3bf8e746951a36d888c2fb7c02e856a5c2091b24e39a7e1af828f",
"1fa09fe8bf7d274afde0a0847ba143c43ffb8722301b5ae32e2f078b9a5e19097403336e50b18c84",
"aade446cd2823b011f97d6ad2116a53feb814efecc086bc172d31f4214b4d7c630b63bbe575b0868",
@ -725,7 +725,7 @@ fileprivate extension LibSessionUtilSpec {
"e7486ccde24416a7fd4a8ba5fa73899c65f4276dfaddd5b2100adcf0f793104fb235b31ce32ec656",
"056009a9ebf58d45d7d696b74e0c7ff0499c4d23204976f19561dc0dba6dc53a2497d28ce03498ea",
"49bf122762d7bc1d6d9c02f6d54f8384"
].joined()).bytes
].joined()))
// We haven't dumped, so still need to dump:
expect(config_needs_dump(conf)).to(beTrue())
@ -743,7 +743,7 @@ fileprivate extension LibSessionUtilSpec {
// So now imagine we got back confirmation from the swarm that the push has been stored:
let fakeHash1: String = "fakehash1"
var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated()
var cFakeHash1: [CChar] = fakeHash1.cString(using: .utf8)!
config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1)
pushData2.deallocate()
@ -768,8 +768,8 @@ fileprivate extension LibSessionUtilSpec {
// Now imagine we just pulled down the `exp_push1` string from the swarm; we merge it into
// conf2:
var mergeHashes: [UnsafePointer<CChar>?] = [cFakeHash1].unsafeCopy()
var mergeData: [UnsafePointer<UInt8>?] = [expPush1Encrypted].unsafeCopy()
var mergeHashes: [UnsafePointer<CChar>?] = ((try? [cFakeHash1].unsafeCopyCStringArray()) ?? [])
var mergeData: [UnsafePointer<UInt8>?] = ((try? [expPush1Encrypted].unsafeCopyUInt8Array()) ?? [])
var mergeSize: [Int] = [expPush1Encrypted.count]
let mergedHashes: UnsafeMutablePointer<config_string_list>? = config_merge(conf2, &mergeHashes, &mergeData, &mergeSize, 1)
expect([String](pointer: mergedHashes?.pointee.value, count: mergedHashes?.pointee.len))
@ -820,13 +820,13 @@ fileprivate extension LibSessionUtilSpec {
expect(config_needs_push(conf2)).to(beTrue())
let fakeHash2: String = "fakehash2"
var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated()
var cFakeHash2: [CChar] = fakeHash2.cString(using: .utf8)!
let pushData3: UnsafeMutablePointer<config_push_data> = config_push(conf)
expect(pushData3.pointee.seqno).to(equal(2)) // incremented, since we made a field change
config_confirm_pushed(conf, pushData3.pointee.seqno, &cFakeHash2)
let fakeHash3: String = "fakehash3"
var cFakeHash3: [CChar] = fakeHash3.cArray.nullTerminated()
var cFakeHash3: [CChar] = fakeHash3.cString(using: .utf8)!
let pushData4: UnsafeMutablePointer<config_push_data> = config_push(conf2)
expect(pushData4.pointee.seqno).to(equal(2)) // incremented, since we made a field change
config_confirm_pushed(conf, pushData4.pointee.seqno, &cFakeHash3)
@ -852,7 +852,7 @@ fileprivate extension LibSessionUtilSpec {
// Feed the new config into each other. (This array could hold multiple configs if we pulled
// down more than one).
var mergeHashes2: [UnsafePointer<CChar>?] = [cFakeHash2].unsafeCopy()
var mergeHashes2: [UnsafePointer<CChar>?] = ((try? [cFakeHash2].unsafeCopyCStringArray()) ?? [])
var mergeData2: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData3.pointee.config)]
var mergeSize2: [Int] = [pushData3.pointee.config_len]
let mergedHashes2: UnsafeMutablePointer<config_string_list>? = config_merge(conf2, &mergeHashes2, &mergeData2, &mergeSize2, 1)
@ -862,7 +862,7 @@ fileprivate extension LibSessionUtilSpec {
mergedHashes2?.deallocate()
pushData3.deallocate()
var mergeHashes3: [UnsafePointer<CChar>?] = [cFakeHash3].unsafeCopy()
var mergeHashes3: [UnsafePointer<CChar>?] = ((try? [cFakeHash3].unsafeCopyCStringArray()) ?? [])
var mergeData3: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData4.pointee.config)]
var mergeSize3: [Int] = [pushData4.pointee.config_len]
let mergedHashes3: UnsafeMutablePointer<config_string_list>? = config_merge(conf, &mergeHashes3, &mergeData3, &mergeSize3, 1)
@ -909,9 +909,9 @@ fileprivate extension LibSessionUtilSpec {
expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(1))
let fakeHash4: String = "fakehash4"
var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated()
var cFakeHash4: [CChar] = fakeHash4.cString(using: .utf8)!
let fakeHash5: String = "fakehash5"
var cFakeHash5: [CChar] = fakeHash5.cArray.nullTerminated()
var cFakeHash5: [CChar] = fakeHash5.cString(using: .utf8)!
config_confirm_pushed(conf, pushData5.pointee.seqno, &cFakeHash4)
config_confirm_pushed(conf2, pushData6.pointee.seqno, &cFakeHash5)
pushData5.deallocate()
@ -967,7 +967,7 @@ fileprivate extension LibSessionUtilSpec {
// Empty contacts shouldn't have an existing contact
let definitelyRealId: String = "055000000000000000000000000000000000000000000000000000000000000000"
var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated()
var cDefinitelyRealId: [CChar] = definitelyRealId.cString(using: .utf8)!
var oneToOne1: convo_info_volatile_1to1 = convo_info_volatile_1to1()
expect(convo_info_volatile_get_1to1(conf, &oneToOne1, &cDefinitelyRealId)).to(beFalse())
expect(convo_info_volatile_size(conf)).to(equal(0))
@ -1001,7 +1001,7 @@ fileprivate extension LibSessionUtilSpec {
expect(config_needs_dump(conf)).to(beTrue())
let openGroupBaseUrl: String = "http://Example.ORG:5678"
var cOpenGroupBaseUrl: [CChar] = openGroupBaseUrl.cArray.nullTerminated()
var cOpenGroupBaseUrl: [CChar] = openGroupBaseUrl.cString(using: .utf8)!
let openGroupBaseUrlResult: String = openGroupBaseUrl.lowercased()
// ("http://Example.ORG:5678"
// .lowercased()
@ -1009,7 +1009,7 @@ fileprivate extension LibSessionUtilSpec {
// [CChar](repeating: 0, count: (268 - openGroupBaseUrl.count))
// )
let openGroupRoom: String = "SudokuRoom"
var cOpenGroupRoom: [CChar] = openGroupRoom.cArray.nullTerminated()
var cOpenGroupRoom: [CChar] = openGroupRoom.cString(using: .utf8)!
let openGroupRoomResult: String = openGroupRoom.lowercased()
// ("SudokuRoom"
// .lowercased()
@ -1036,7 +1036,7 @@ fileprivate extension LibSessionUtilSpec {
// Pretend we uploaded it
let fakeHash1: String = "fakehash1"
var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated()
var cFakeHash1: [CChar] = fakeHash1.cString(using: .utf8)!
config_confirm_pushed(conf, pushData1.pointee.seqno, &cFakeHash1)
expect(config_needs_dump(conf)).to(beTrue())
expect(config_needs_push(conf)).to(beFalse())
@ -1070,14 +1070,14 @@ fileprivate extension LibSessionUtilSpec {
community2.unread = true
let anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111"
var cAnotherId: [CChar] = anotherId.cArray.nullTerminated()
var cAnotherId: [CChar] = anotherId.cString(using: .utf8)!
var oneToOne5: convo_info_volatile_1to1 = convo_info_volatile_1to1()
expect(convo_info_volatile_get_or_construct_1to1(conf2, &oneToOne5, &cAnotherId)).to(beTrue())
oneToOne5.unread = true
convo_info_volatile_set_1to1(conf2, &oneToOne5)
let thirdId: String = "05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
var cThirdId: [CChar] = thirdId.cArray.nullTerminated()
var cThirdId: [CChar] = thirdId.cString(using: .utf8)!
var legacyGroup2: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group()
expect(convo_info_volatile_get_or_construct_legacy_group(conf2, &legacyGroup2, &cThirdId)).to(beTrue())
legacyGroup2.last_read = (nowTimestampMs - 50)
@ -1089,8 +1089,8 @@ fileprivate extension LibSessionUtilSpec {
// Check the merging
let fakeHash2: String = "fakehash2"
var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated()
var mergeHashes: [UnsafePointer<CChar>?] = [cFakeHash2].unsafeCopy()
var cFakeHash2: [CChar] = fakeHash2.cString(using: .utf8)!
var mergeHashes: [UnsafePointer<CChar>?] = ((try? [cFakeHash2].unsafeCopyCStringArray()) ?? [])
var mergeData: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData2.pointee.config)]
var mergeSize: [Int] = [pushData2.pointee.config_len]
let mergedHashes: UnsafeMutablePointer<config_string_list>? = config_merge(conf, &mergeHashes, &mergeData, &mergeSize, 1)
@ -1141,7 +1141,7 @@ fileprivate extension LibSessionUtilSpec {
}
let fourthId: String = "052000000000000000000000000000000000000000000000000000000000000000"
var cFourthId: [CChar] = fourthId.cArray.nullTerminated()
var cFourthId: [CChar] = fourthId.cString(using: .utf8)!
expect(config_needs_push(conf)).to(beFalse())
convo_info_volatile_erase_1to1(conf, &cFourthId)
expect(config_needs_push(conf)).to(beFalse())
@ -1231,7 +1231,7 @@ fileprivate extension LibSessionUtilSpec {
// Empty contacts shouldn't have an existing contact
let definitelyRealId: String = "055000000000000000000000000000000000000000000000000000000000000000"
var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated()
var cDefinitelyRealId: [CChar] = definitelyRealId.cString(using: .utf8)!
let legacyGroup1: UnsafeMutablePointer<ugroups_legacy_group_info>? = user_groups_get_legacy_group(conf, &cDefinitelyRealId)
expect(legacyGroup1?.pointee).to(beNil())
expect(user_groups_size(conf)).to(equal(0))
@ -1285,7 +1285,7 @@ fileprivate extension LibSessionUtilSpec {
"055555555555555555555555555555555555555555555555555555555555555555",
"056666666666666666666666666666666666666666666666666666666666666666"
]
var cUsers: [[CChar]] = users.map { $0.cArray.nullTerminated() }
var cUsers: [[CChar]] = users.map { $0.cString(using: .utf8)! }
legacyGroup2.pointee.name = "Englishmen".toLibSession()
legacyGroup2.pointee.disappearing_timer = 60
legacyGroup2.pointee.joined_at = createdTs
@ -1348,9 +1348,9 @@ fileprivate extension LibSessionUtilSpec {
ugroups_legacy_group_free(legacyGroup3)
let communityPubkey: String = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
var cCommunityPubkey: [UInt8] = Data(hex: communityPubkey).cArray
var cCommunityBaseUrl: [CChar] = "http://Example.ORG:5678".cArray.nullTerminated()
var cCommunityRoom: [CChar] = "SudokuRoom".cArray.nullTerminated()
var cCommunityPubkey: [UInt8] = Array(Data(hex: communityPubkey))
var cCommunityBaseUrl: [CChar] = "http://Example.ORG:5678".cString(using: .utf8)!
var cCommunityRoom: [CChar] = "SudokuRoom".cString(using: .utf8)!
var community1: ugroups_community_info = ugroups_community_info()
expect(user_groups_get_or_construct_community(conf, &community1, &cCommunityBaseUrl, &cCommunityRoom, &cCommunityPubkey))
.to(beTrue())
@ -1373,7 +1373,7 @@ fileprivate extension LibSessionUtilSpec {
// Pretend we uploaded it
let fakeHash1: String = "fakehash1"
var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated()
var cFakeHash1: [CChar] = fakeHash1.cString(using: .utf8)!
config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1)
expect(config_needs_dump(conf)).to(beTrue())
expect(config_needs_push(conf)).to(beFalse())
@ -1492,8 +1492,8 @@ fileprivate extension LibSessionUtilSpec {
]))
}
var cCommunity2BaseUrl: [CChar] = "http://example.org:5678".cArray.nullTerminated()
var cCommunity2Room: [CChar] = "sudokuRoom".cArray.nullTerminated()
var cCommunity2BaseUrl: [CChar] = "http://example.org:5678".cString(using: .utf8)!
var cCommunity2Room: [CChar] = "sudokuRoom".cString(using: .utf8)!
var community2: ugroups_community_info = ugroups_community_info()
expect(user_groups_get_community(conf2, &community2, &cCommunity2BaseUrl, &cCommunity2Room))
.to(beTrue())
@ -1518,7 +1518,7 @@ fileprivate extension LibSessionUtilSpec {
expect(config_needs_dump(conf2)).to(beTrue())
let fakeHash2: String = "fakehash2"
var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated()
var cFakeHash2: [CChar] = fakeHash2.cString(using: .utf8)!
let pushData7: UnsafeMutablePointer<config_push_data> = config_push(conf2)
expect(pushData7.pointee.seqno).to(equal(2))
config_confirm_pushed(conf2, pushData7.pointee.seqno, &cFakeHash2)
@ -1542,7 +1542,7 @@ fileprivate extension LibSessionUtilSpec {
config_confirm_pushed(conf2, pushData8.pointee.seqno, &cFakeHash2)
expect(config_needs_dump(conf2)).to(beFalse())
var mergeHashes1: [UnsafePointer<CChar>?] = [cFakeHash2].unsafeCopy()
var mergeHashes1: [UnsafePointer<CChar>?] = ((try? [cFakeHash2].unsafeCopyCStringArray()) ?? [])
var mergeData1: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData8.pointee.config)]
var mergeSize1: [Int] = [pushData8.pointee.config_len]
let mergedHashes1: UnsafeMutablePointer<config_string_list>? = config_merge(conf, &mergeHashes1, &mergeData1, &mergeSize1, 1)
@ -1552,8 +1552,8 @@ fileprivate extension LibSessionUtilSpec {
mergedHashes1?.deallocate()
pushData8.deallocate()
var cCommunity3BaseUrl: [CChar] = "http://example.org:5678".cArray.nullTerminated()
var cCommunity3Room: [CChar] = "SudokuRoom".cArray.nullTerminated()
var cCommunity3BaseUrl: [CChar] = "http://example.org:5678".cString(using: .utf8)!
var cCommunity3Room: [CChar] = "SudokuRoom".cString(using: .utf8)!
var community3: ugroups_community_info = ugroups_community_info()
expect(user_groups_get_community(conf, &community3, &cCommunity3BaseUrl, &cCommunity3Room))
.to(beTrue())
@ -1581,12 +1581,12 @@ fileprivate extension LibSessionUtilSpec {
expect(config_needs_push(conf2)).to(beTrue())
expect(config_needs_dump(conf2)).to(beTrue())
var cCommunity4BaseUrl: [CChar] = "http://exAMple.ORG:5678".cArray.nullTerminated()
var cCommunity4Room: [CChar] = "sudokuROOM".cArray.nullTerminated()
var cCommunity4BaseUrl: [CChar] = "http://exAMple.ORG:5678".cString(using: .utf8)!
var cCommunity4Room: [CChar] = "sudokuROOM".cString(using: .utf8)!
user_groups_erase_community(conf2, &cCommunity4BaseUrl, &cCommunity4Room)
let fakeHash3: String = "fakehash3"
var cFakeHash3: [CChar] = fakeHash3.cArray.nullTerminated()
var cFakeHash3: [CChar] = fakeHash3.cString(using: .utf8)!
let pushData10: UnsafeMutablePointer<config_push_data> = config_push(conf2)
config_confirm_pushed(conf2, pushData10.pointee.seqno, &cFakeHash3)
@ -1599,7 +1599,7 @@ fileprivate extension LibSessionUtilSpec {
.to(equal([fakeHash3]))
currentHashes4?.deallocate()
var mergeHashes2: [UnsafePointer<CChar>?] = [cFakeHash3].unsafeCopy()
var mergeHashes2: [UnsafePointer<CChar>?] = ((try? [cFakeHash3].unsafeCopyCStringArray()) ?? [])
var mergeData2: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData10.pointee.config)]
var mergeSize2: [Int] = [pushData10.pointee.config_len]
let mergedHashes2: UnsafeMutablePointer<config_string_list>? = config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)
@ -1613,13 +1613,13 @@ fileprivate extension LibSessionUtilSpec {
expect(user_groups_size_legacy_groups(conf)).to(equal(1))
var prio: Int32 = 0
var cBeanstalkBaseUrl: [CChar] = "http://jacksbeanstalk.org".cArray.nullTerminated()
var cBeanstalkPubkey: [UInt8] = Data(
var cBeanstalkBaseUrl: [CChar] = "http://jacksbeanstalk.org".cString(using: .utf8)!
var cBeanstalkPubkey: [UInt8] = Array(Data(
hex: "0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff"
).cArray
))
["fee", "fi", "fo", "fum"].forEach { room in
var cRoom: [CChar] = room.cArray.nullTerminated()
var cRoom: [CChar] = room.cString(using: .utf8)!
prio += 1
var community4: ugroups_community_info = ugroups_community_info()
@ -1634,7 +1634,7 @@ fileprivate extension LibSessionUtilSpec {
expect(user_groups_size_legacy_groups(conf)).to(equal(1))
let fakeHash4: String = "fakehash4"
var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated()
var cFakeHash4: [CChar] = fakeHash4.cString(using: .utf8)!
let pushData11: UnsafeMutablePointer<config_push_data> = config_push(conf)
config_confirm_pushed(conf, pushData11.pointee.seqno, &cFakeHash4)
expect(pushData11.pointee.seqno).to(equal(4))
@ -1643,12 +1643,12 @@ fileprivate extension LibSessionUtilSpec {
// Load some obsolete ones in just to check that they get immediately obsoleted
let fakeHash10: String = "fakehash10"
let cFakeHash10: [CChar] = fakeHash10.cArray.nullTerminated()
let cFakeHash10: [CChar] = fakeHash10.cString(using: .utf8)!
let fakeHash11: String = "fakehash11"
let cFakeHash11: [CChar] = fakeHash11.cArray.nullTerminated()
let cFakeHash11: [CChar] = fakeHash11.cString(using: .utf8)!
let fakeHash12: String = "fakehash12"
let cFakeHash12: [CChar] = fakeHash12.cArray.nullTerminated()
var mergeHashes3: [UnsafePointer<CChar>?] = [cFakeHash10, cFakeHash11, cFakeHash12, cFakeHash4].unsafeCopy()
let cFakeHash12: [CChar] = fakeHash12.cString(using: .utf8)!
var mergeHashes3: [UnsafePointer<CChar>?] = ((try? [cFakeHash10, cFakeHash11, cFakeHash12, cFakeHash4].unsafeCopyCStringArray()) ?? [])
var mergeData3: [UnsafePointer<UInt8>?] = [
UnsafePointer(pushData10.pointee.config),
UnsafePointer(pushData2.pointee.config),

@ -12,11 +12,6 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
override class func spec() {
// MARK: - a String
describe("a String") {
// MARK: -- can convert to a cArray
it("can convert to a cArray") {
expect("Test123".cArray).to(equal([84, 101, 115, 116, 49, 50, 51]))
}
// MARK: -- can contain emoji
it("can contain emoji") {
let original: String = "Hi 👋"
@ -187,11 +182,6 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
// MARK: - Data
describe("Data") {
// MARK: -- can convert to a cArray
it("can convert to a cArray") {
expect(Data([1, 2, 3]).cArray).to(equal([1, 2, 3]))
}
// MARK: -- when initialised with a libSession value
context("when initialised with a libSession value") {
// MARK: ---- returns truncated data when given the wrong length
@ -297,9 +287,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
// MARK: ---- returns the correct array
it("returns the correct array") {
var test: [CChar] = (
"Test1".cArray.nullTerminated() +
"Test2".cArray.nullTerminated() +
"Test3AndExtra".cArray.nullTerminated()
"Test1".cString(using: .utf8)! +
"Test2".cString(using: .utf8)! +
"Test3AndExtra".cString(using: .utf8)!
)
let result = test.withUnsafeMutableBufferPointer { ptr in
var mutablePtr = UnsafeMutablePointer(ptr.baseAddress)
@ -325,9 +315,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
// MARK: ---- handles empty strings without issues
it("handles empty strings without issues") {
var test: [CChar] = (
"Test1".cArray.nullTerminated() +
"".cArray.nullTerminated() +
"Test2".cArray.nullTerminated()
"Test1".cString(using: .utf8)! +
"".cString(using: .utf8)! +
"Test2".cString(using: .utf8)!
)
let result = test.withUnsafeMutableBufferPointer { ptr in
var mutablePtr = UnsafeMutablePointer(ptr.baseAddress)
@ -345,7 +335,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec {
// MARK: ---- returns null when given a null count
it("returns null when given a null count") {
var test: [CChar] = "Test1".cArray.nullTerminated()
var test: [CChar] = "Test1".cString(using: .utf8)!
let result = test.withUnsafeMutableBufferPointer { ptr in
var mutablePtr = UnsafeMutablePointer(ptr.baseAddress)

@ -174,11 +174,20 @@ class OpenGroupManagerSpec: QuickSpec {
cache.when { $0.encodedPublicKey }.thenReturn("05\(TestConstants.publicKey)")
}
)
@TestState var mockPoller: MockOGPoller! = MockOGPoller(
initialSetup: { poller in
poller.when { $0.stop() }.thenReturn(())
poller.when { $0.startIfNeeded(using: any()) }.thenReturn(())
}
)
@TestState var mockOGMCache: MockOGMCache! = MockOGMCache(
initialSetup: { cache in
cache.when { $0.pendingChanges }.thenReturn([])
cache.when { $0.pollers = any() }.thenReturn(())
cache.when { $0.isPolling = any() }.thenReturn(())
cache.when { $0.serversBeingPolled }.thenReturn([])
cache.when { $0.hasPerformedInitialPoll }.thenReturn([:])
cache.when { $0.timeSinceLastPoll }.thenReturn([:])
cache.when { $0.getTimeSinceLastOpen(using: any()) }.thenReturn(0)
cache
.when { $0.defaultRoomsPublisher = any(type: [OpenGroupManager.DefaultRoomInfo].self) }
.thenReturn(())
@ -188,6 +197,9 @@ class OpenGroupManagerSpec: QuickSpec {
cache
.when { $0.pendingChanges = any(type: OpenGroupAPI.PendingChange.self) }
.thenReturn(())
cache.when { $0.getOrCreatePoller(for: any()) }.thenReturn(mockPoller)
cache.when { $0.stopAndRemovePoller(for: any()) }.thenReturn(())
cache.when { $0.stopAndRemoveAllPollers() }.thenReturn(())
}
)
@TestState var mockCaches: MockCaches! = MockCaches()
@ -209,13 +221,6 @@ class OpenGroupManagerSpec: QuickSpec {
// MARK: - an OpenGroupManager
describe("an OpenGroupManager") {
afterEach {
// Just in case the shared instance had pollers created we should stop them
OpenGroupManager.shared.stopPolling()
openGroupManager.stopPolling()
}
// MARK: -- cache data
context("cache data") {
// MARK: ---- defaults the time since last open to greatestFiniteMagnitude
@ -293,7 +298,6 @@ class OpenGroupManagerSpec: QuickSpec {
}
.thenReturn(0)
mockOGMCache.when { $0.isPolling }.thenReturn(false)
mockOGMCache.when { $0.pollers }.thenReturn([:])
mockUserDefaults
.when { (defaults: inout any UserDefaultsType) -> Any? in
@ -308,10 +312,11 @@ class OpenGroupManagerSpec: QuickSpec {
expect(mockOGMCache)
.to(call(matchingParameters: true) {
$0.pollers = [
"testserver": OpenGroupAPI.Poller(for: "testserver"),
"testserver1": OpenGroupAPI.Poller(for: "testserver1")
]
$0.getOrCreatePoller(for: "testserver")
})
expect(mockOGMCache)
.to(call(matchingParameters: true) {
$0.getOrCreatePoller(for: "testserver1")
})
}
@ -329,7 +334,7 @@ class OpenGroupManagerSpec: QuickSpec {
openGroupManager.startPolling(using: dependencies)
expect(mockOGMCache).toNot(call { $0.pollers })
expect(mockOGMCache).toNot(call { $0.getOrCreatePoller(for: any()) })
}
}
@ -352,14 +357,13 @@ class OpenGroupManagerSpec: QuickSpec {
}
mockOGMCache.when { $0.isPolling }.thenReturn(true)
mockOGMCache.when { $0.pollers }.thenReturn(["testserver": OpenGroupAPI.Poller(for: "testserver")])
}
// MARK: ---- removes all pollers
it("removes all pollers") {
openGroupManager.stopPolling(using: dependencies)
expect(mockOGMCache).to(call(matchingParameters: true) { $0.pollers = [:] })
expect(mockOGMCache).to(call(matchingParameters: true) { $0.stopAndRemoveAllPollers() })
}
// MARK: ---- updates the isPolling flag
@ -443,13 +447,12 @@ class OpenGroupManagerSpec: QuickSpec {
context("when checking it has an existing open group") {
// MARK: ---- when there is a thread for the room and the cache has a poller
context("when there is a thread for the room and the cache has a poller") {
beforeEach {
mockOGMCache.when { $0.serversBeingPolled }.thenReturn(["testserver"])
}
// MARK: ------ for the no-scheme variant
context("for the no-scheme variant") {
beforeEach {
mockOGMCache.when { $0.pollers }
.thenReturn(["testserver": OpenGroupAPI.Poller(for: "testserver")])
}
// MARK: -------- returns true when no scheme is provided
it("returns true when no scheme is provided") {
expect(
@ -501,11 +504,6 @@ class OpenGroupManagerSpec: QuickSpec {
// MARK: ------ for the http variant
context("for the http variant") {
beforeEach {
mockOGMCache.when { $0.pollers }
.thenReturn(["http://testserver": OpenGroupAPI.Poller(for: "http://testserver")])
}
// MARK: -------- returns true when no scheme is provided
it("returns true when no scheme is provided") {
expect(
@ -557,11 +555,6 @@ class OpenGroupManagerSpec: QuickSpec {
// MARK: ------ for the https variant
context("for the https variant") {
beforeEach {
mockOGMCache.when { $0.pollers }
.thenReturn(["https://testserver": OpenGroupAPI.Poller(for: "https://testserver")])
}
// MARK: -------- returns true when no scheme is provided
it("returns true when no scheme is provided") {
expect(
@ -616,8 +609,7 @@ class OpenGroupManagerSpec: QuickSpec {
context("when given the legacy DNS host and there is a cached poller for the default server") {
// MARK: ------ returns true
it("returns true") {
mockOGMCache.when { $0.pollers }
.thenReturn(["http://116.203.70.33": OpenGroupAPI.Poller(for: "http://116.203.70.33")])
mockOGMCache.when { $0.serversBeingPolled }.thenReturn(["http://116.203.70.33"])
mockStorage.write { db in
try SessionThread(
id: OpenGroup.idFor(roomToken: "testRoom", server: "http://116.203.70.33"),
@ -651,7 +643,7 @@ class OpenGroupManagerSpec: QuickSpec {
context("when given the default server and there is a cached poller for the legacy DNS host") {
// MARK: ------ returns true
it("returns true") {
mockOGMCache.when { $0.pollers }.thenReturn(["http://open.getsession.org": OpenGroupAPI.Poller(for: "http://open.getsession.org")])
mockOGMCache.when { $0.serversBeingPolled }.thenReturn(["http://open.getsession.org"])
mockStorage.write { db in
try SessionThread(
id: OpenGroup.idFor(roomToken: "testRoom", server: "http://open.getsession.org"),
@ -683,8 +675,6 @@ class OpenGroupManagerSpec: QuickSpec {
// MARK: ---- returns false when given an invalid server
it("returns false when given an invalid server") {
mockOGMCache.when { $0.pollers }.thenReturn(["testserver": OpenGroupAPI.Poller(for: "testserver")])
expect(
mockStorage.read { db -> Bool in
openGroupManager
@ -701,7 +691,7 @@ class OpenGroupManagerSpec: QuickSpec {
// MARK: ---- returns false if there is not a poller for the server in the cache
it("returns false if there is not a poller for the server in the cache") {
mockOGMCache.when { $0.pollers }.thenReturn([:])
mockOGMCache.when { $0.serversBeingPolled }.thenReturn([])
expect(
mockStorage.read { db -> Bool in
@ -719,7 +709,6 @@ class OpenGroupManagerSpec: QuickSpec {
// MARK: ---- returns false if there is a poller for the server in the cache but no thread for the room
it("returns false if there is a poller for the server in the cache but no thread for the room") {
mockOGMCache.when { $0.pollers }.thenReturn(["testserver": OpenGroupAPI.Poller(for: "testserver")])
mockStorage.write { db in
try SessionThread.deleteAll(db)
}
@ -749,7 +738,6 @@ class OpenGroupManagerSpec: QuickSpec {
mockNetwork
.when { $0.send(.onionRequest(any(), to: any(), with: any()), using: dependencies) }
.thenReturn(Network.BatchResponse.mockCapabilitiesAndRoomResponse)
mockOGMCache.when { $0.pollers }.thenReturn([:])
mockUserDefaults
.when { (defaults: inout any UserDefaultsType) -> Any? in
@ -824,15 +812,18 @@ class OpenGroupManagerSpec: QuickSpec {
expect(mockOGMCache)
.to(call(matchingParameters: true) {
$0.pollers = ["testserver": OpenGroupAPI.Poller(for: "testserver")]
$0.getOrCreatePoller(for: "testserver")
})
expect(mockPoller)
.to(call(matchingParameters: true) { [dependencies = dependencies!] in
$0.startIfNeeded(using: dependencies)
})
}
// MARK: ---- an existing room
context("an existing room") {
beforeEach {
mockOGMCache.when { $0.pollers }
.thenReturn(["testserver": OpenGroupAPI.Poller(for: "testserver")])
mockOGMCache.when { $0.serversBeingPolled }.thenReturn(["testserver"])
mockStorage.write { db in
try testOpenGroup.insert(db)
}
@ -954,8 +945,6 @@ class OpenGroupManagerSpec: QuickSpec {
.set(to: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"))
)
}
mockOGMCache.when { $0.pollers }.thenReturn([:])
}
// MARK: ---- removes all interactions for the thread
@ -994,8 +983,6 @@ class OpenGroupManagerSpec: QuickSpec {
context("and there is only one open group for this server") {
// MARK: ------ stops the poller
it("stops the poller") {
mockOGMCache.when { $0.pollers }.thenReturn(["testserver": OpenGroupAPI.Poller(for: "testserver")])
mockStorage.write { db in
openGroupManager
.delete(
@ -1006,7 +993,7 @@ class OpenGroupManagerSpec: QuickSpec {
)
}
expect(mockOGMCache).to(call(matchingParameters: true) { $0.pollers = [:] })
expect(mockOGMCache).to(call(matchingParameters: true) { $0.stopAndRemovePoller(for: "testserver") })
}
// MARK: ------ removes the open group
@ -1175,7 +1162,6 @@ class OpenGroupManagerSpec: QuickSpec {
try testOpenGroup.insert(db)
}
mockOGMCache.when { $0.pollers }.thenReturn([:])
mockOGMCache.when { $0.hasPerformedInitialPoll }.thenReturn([:])
mockOGMCache.when { $0.timeSinceLastPoll }.thenReturn([:])
mockOGMCache
@ -1575,54 +1561,6 @@ class OpenGroupManagerSpec: QuickSpec {
}
}
// MARK: ---- when checking to start polling
context("when checking to start polling") {
// MARK: ------ starts a new poller when not already polling
it("starts a new poller when not already polling") {
mockOGMCache.when { $0.pollers }.thenReturn([:])
mockStorage.write { db in
try OpenGroupManager.handlePollInfo(
db,
pollInfo: testPollInfo,
publicKey: TestConstants.publicKey,
for: "testRoom",
on: "testServer",
using: dependencies
)
}
expect(mockOGMCache)
.to(call(matchingParameters: true) {
$0.pollers = ["testserver": OpenGroupAPI.Poller(for: "testserver")]
})
}
// MARK: ------ restarts the poller if there already is one
it("restarts the poller if there already is one") {
mockOGMCache.when { $0.pollers }.thenReturn(["testserver": OpenGroupAPI.Poller(for: "testserver")])
mockNetwork
.when { $0.send(.onionRequest(any(), to: any(), with: any()), using: dependencies) }
.thenReturn(Network.BatchResponse.mockBlindedPollResponse)
mockStorage.write { db in
try OpenGroupManager.handlePollInfo(
db,
pollInfo: testPollInfo,
publicKey: TestConstants.publicKey,
for: "testRoom",
on: "testServer",
using: dependencies
)
}
expect(mockOGMCache)
.to(call(matchingParameters: true) {
$0.pollers = ["testserver": OpenGroupAPI.Poller(for: "testserver")]
})
}
}
// MARK: ---- when trying to get the room image
context("when trying to get the room image") {
beforeEach {

@ -17,16 +17,15 @@ class MockOGMCache: Mock<OGMCacheType>, OGMCacheType {
set { accept(args: [newValue]) }
}
var pollers: [String: OpenGroupAPI.Poller] {
get { return accept() as! [String: OpenGroupAPI.Poller] }
set { accept(args: [newValue]) }
}
var isPolling: Bool {
get { return accept() as! Bool }
set { accept(args: [newValue]) }
}
var serversBeingPolled: Set<String> {
get { return accept() as! Set<String> }
}
var hasPerformedInitialPoll: [String: Bool] {
get { return accept() as! [String: Bool] }
set { accept(args: [newValue]) }
@ -42,6 +41,12 @@ class MockOGMCache: Mock<OGMCacheType>, OGMCacheType {
set { accept(args: [newValue]) }
}
func getOrCreatePoller(for server: String) -> OpenGroupAPI.PollerType {
return accept(args: [server]) as! OpenGroupAPI.PollerType
}
func stopAndRemovePoller(for server: String) { accept(args: [server]) }
func stopAndRemoveAllPollers() { accept() }
func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval {
return accept(args: [dependencies]) as! TimeInterval
}

@ -0,0 +1,16 @@
// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import SessionUtilitiesKit
@testable import SessionMessagingKit
class MockOGPoller: Mock<OpenGroupAPI.PollerType>, OpenGroupAPI.PollerType {
func startIfNeeded(using dependencies: Dependencies) {
accept()
}
func stop() {
accept()
}
}

@ -118,7 +118,9 @@ public enum Mnemonic {
}
public static func decode(mnemonic: String, language: Language = .english) throws -> String {
var words: [String] = mnemonic.components(separatedBy: .whitespacesAndNewlines)
var words: [String] = mnemonic
.components(separatedBy: .whitespacesAndNewlines)
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
let truncatedWordSet: [String] = language.loadTruncatedWordSet()
let prefixLength: Int = language.prefixLength
var result = ""
@ -131,6 +133,13 @@ public enum Mnemonic {
// Get checksum word
let checksumWord = words.popLast()!
// Limit the words to a multiple of 3 to avoid index-out-of-bounds issues
let remainder: Int = (words.count % 3)
if remainder > 0 {
words.removeLast(remainder)
}
// Decode
for chunkStartIndex in stride(from: 0, to: words.count, by: 3) {
guard

@ -45,6 +45,9 @@ public class PagedDatabaseObserver<ObservedTable, T>: TransactionObserver where
// MARK: - Initialization
/// Create a `PagedDatabaseObserver` which triggers the callback whenever changes occur
///
/// **Note:** The `onChangeUnsorted` could be run on any logic may need to be shifted to the UI thread
public init(
pagedTable: ObservedTable.Type,
pageSize: Int,
@ -766,18 +769,10 @@ public class PagedDatabaseObserver<ObservedTable, T>: TransactionObserver where
self.dataCache.mutate { $0 = $0.upserting(items: associatedLoadedData.values) }
self.pageInfo.mutate { $0 = updatedPageInfo }
let triggerUpdates: () -> () = { [weak self, dataCache = self.dataCache.wrappedValue] in
self?.onChangeUnsorted(dataCache.values, updatedPageInfo)
self?.isLoadingMoreData.mutate { $0 = false }
}
// Make sure the updates run on the main thread
guard Thread.isMainThread else {
DispatchQueue.main.async { triggerUpdates() }
return
}
triggerUpdates()
// Trigger the unsorted change callback (the actual UI update triggering should eventually be run on
// the main thread via the `PagedData.processAndTriggerUpdates` function)
self.onChangeUnsorted(self.dataCache.wrappedValue.values, updatedPageInfo)
self.isLoadingMoreData.mutate { $0 = false }
}
public func reload() {
@ -1022,7 +1017,7 @@ public enum PagedData {
public static func processAndTriggerUpdates<SectionModel: DifferentiableSection>(
updatedData: [SectionModel]?,
currentDataRetriever: @escaping (() -> [SectionModel]?),
onDataChange: (([SectionModel], StagedChangeset<[SectionModel]>) -> ())?,
onDataChangeRetriever: @escaping (() -> (([SectionModel], StagedChangeset<[SectionModel]>) -> ())?),
onUnobservedDataChange: @escaping (([SectionModel], StagedChangeset<[SectionModel]>) -> Void)
) {
guard let updatedData: [SectionModel] = updatedData else { return }
@ -1045,7 +1040,7 @@ public enum PagedData {
///
/// **Note:** We do this even if the 'changeset' is empty because if this change reverts a previous change we
/// need to ensure the `onUnobservedDataChange` gets cleared so it doesn't end up in an invalid state
guard let onDataChange: (([SectionModel], StagedChangeset<[SectionModel]>) -> ()) = onDataChange else {
guard let onDataChange: (([SectionModel], StagedChangeset<[SectionModel]>) -> ()) = onDataChangeRetriever() else {
onUnobservedDataChange(updatedData, changeset)
return
}
@ -1056,7 +1051,7 @@ public enum PagedData {
onDataChange(updatedData, changeset)
}
// No need to dispatch to the next run loop if we are alread on the main thread
// No need to dispatch to the next run loop if we are already on the main thread
guard !Thread.isMainThread else {
performUpdates()
return
@ -1096,7 +1091,7 @@ public enum PagedData {
valueSubject?.send((updatedData, StagedChangeset()))
}
// No need to dispatch to the next run loop if we are alread on the main thread
// No need to dispatch to the next run loop if we are already on the main thread
guard !Thread.isMainThread else {
performUpdates()
return

Loading…
Cancel
Save