Merge pull request #418 from mpretty-cyro/fix/updated-libsesison-and-safer-memory-management

Memory management cleanup, tweak MRR logic, libSession 1.3.1
pull/1062/head
Morgan Pretty 2 weeks ago committed by GitHub
commit 8622b0303d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -7810,7 +7810,7 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
COMPILE_LIB_SESSION = "";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 567;
CURRENT_PROJECT_VERSION = 569;
ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@ -7890,7 +7890,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Distribution";
COMPILE_LIB_SESSION = "";
CURRENT_PROJECT_VERSION = 567;
CURRENT_PROJECT_VERSION = 569;
ENABLE_BITCODE = NO;
ENABLE_MODULE_VERIFIER = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@ -8075,6 +8075,7 @@
GENERATE_INFOPLIST_FILE = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionSnodeKitTests;
PRODUCT_NAME = SessionSnodeKitTests;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
@ -8085,6 +8086,7 @@
GENERATE_INFOPLIST_FILE = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionSnodeKitTests;
PRODUCT_NAME = SessionSnodeKitTests;
SWIFT_VERSION = 5.0;
};
name = App_Store_Release;
};
@ -8279,14 +8281,20 @@
FD860CBF2D6E981900BBE29C /* Debug_Compile_LibSession */ = {
isa = XCBuildConfiguration;
buildSettings = {
GENERATE_INFOPLIST_FILE = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionSnodeKitTests;
PRODUCT_NAME = SessionSnodeKitTests;
SWIFT_VERSION = 5.0;
};
name = Debug_Compile_LibSession;
};
FD860CC02D6E981900BBE29C /* App_Store_Release_Compile_LibSession */ = {
isa = XCBuildConfiguration;
buildSettings = {
GENERATE_INFOPLIST_FILE = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionSnodeKitTests;
PRODUCT_NAME = SessionSnodeKitTests;
SWIFT_VERSION = 5.0;
};
name = App_Store_Release_Compile_LibSession;
};
@ -8413,7 +8421,7 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
COMPILE_LIB_SESSION = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 567;
CURRENT_PROJECT_VERSION = 569;
ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@ -9051,7 +9059,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Distribution";
COMPILE_LIB_SESSION = YES;
CURRENT_PROJECT_VERSION = 567;
CURRENT_PROJECT_VERSION = 569;
ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_NO_COMMON_BLOCKS = YES;
@ -10055,7 +10063,7 @@
repositoryURL = "https://github.com/session-foundation/libsession-util-spm";
requirement = {
kind = exactVersion;
version = 1.3.0;
version = 1.3.1;
};
};
FD6A38E72C2A630E00762359 /* XCRemoteSwiftPackageReference "CocoaLumberjack" */ = {

@ -51,8 +51,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/session-foundation/libsession-util-spm",
"state" : {
"revision" : "51580552aef35e52a3ae99ec72df2d70026ef415",
"version" : "1.3.0"
"revision" : "7c6665ffe8a458f57aa3cf9115873c7be9efe7cb",
"version" : "1.3.1"
}
},
{

@ -20,34 +20,6 @@
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FD71160828D00BAE00B47552"
BuildableName = "SessionTests.xctest"
BlueprintName = "SessionTests"
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FDB5DAF92A981C42002C8721"
BuildableName = "SessionSnodeKitTests.xctest"
BlueprintName = "SessionSnodeKitTests"
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
@ -138,18 +110,6 @@
</TestPlanReference>
</TestPlans>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES"
testExecutionOrdering = "random">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FD71160828D00BAE00B47552"
BuildableName = "SessionTests.xctest"
BlueprintName = "SessionTests"
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO"
parallelizable = "YES"
@ -162,18 +122,6 @@
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO"
parallelizable = "YES"
testExecutionOrdering = "random">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FDB5DAF92A981C42002C8721"
BuildableName = "SessionSnodeKitTests.xctest"
BlueprintName = "SessionSnodeKitTests"
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO"
parallelizable = "YES"
@ -186,18 +134,6 @@
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO"
parallelizable = "YES"
testExecutionOrdering = "random">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FDB5DAF92A981C42002C8721"
BuildableName = "SessionSnodeKitTests.xctest"
BlueprintName = "SessionSnodeKitTests"
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction

@ -6,24 +6,6 @@
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Script"
scriptText = "&#10;">
</ActionContent>
</ExecutionAction>
</PreActions>
<PostActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Script"
scriptText = "&#10;">
</ActionContent>
</ExecutionAction>
</PostActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
@ -45,8 +27,13 @@
buildConfiguration = "Debug_Compile_LibSession"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
shouldUseLaunchSchemeArgsEnv = "YES">
<TestPlans>
<TestPlanReference
reference = "container:SessionTests/Session.xctestplan"
default = "YES">
</TestPlanReference>
</TestPlans>
</TestAction>
<LaunchAction
buildConfiguration = "Debug_Compile_LibSession"

@ -561,14 +561,9 @@ extension ConversationVC:
self?.scrollToBottom(isAnimated: false)
}
// Note: 'shouldBeVisible' is set to true the first time a thread is saved so we can
// use it to determine if the user is creating a new thread and update the 'isApproved'
// flags appropriately
let oldThreadShouldBeVisible: Bool = (self.viewModel.threadData.threadShouldBeVisible == true)
let sentTimestampMs: Int64 = viewModel.dependencies[cache: .snodeAPI].currentOffsetTimestampMs()
// Optimistically insert the outgoing message (this will trigger a UI update)
self.viewModel.sentMessageBeforeUpdate = true
let sentTimestampMs: Int64 = viewModel.dependencies[cache: .snodeAPI].currentOffsetTimestampMs()
let optimisticData: ConversationViewModel.OptimisticMessageData = self.viewModel.optimisticallyAppendOutgoingMessage(
text: processedText,
sentTimestampMs: sentTimestampMs,
@ -581,7 +576,8 @@ extension ConversationVC:
approveMessageRequestIfNeeded(
for: self.viewModel.threadData.threadId,
threadVariant: self.viewModel.threadData.threadVariant,
isNewThread: !oldThreadShouldBeVisible,
displayName: self.viewModel.threadData.displayName,
isDraft: (self.viewModel.threadData.threadIsDraft == true),
timestampMs: (sentTimestampMs - 1) // Set 1ms earlier as this is used for sorting
).sinkUntilComplete(
receiveCompletion: { [weak self] _ in
@ -2576,7 +2572,8 @@ extension ConversationVC {
fileprivate func approveMessageRequestIfNeeded(
for threadId: String,
threadVariant: SessionThread.Variant,
isNewThread: Bool,
displayName: String,
isDraft: Bool,
timestampMs: Int64
) -> AnyPublisher<Void, Never> {
let updateNavigationBackStack: () -> Void = {
@ -2610,11 +2607,11 @@ extension ConversationVC {
else { return Just(()).eraseToAnyPublisher() }
return viewModel.dependencies[singleton: .storage]
.writePublisher { [displayName = self.viewModel.threadData.displayName, dependencies = viewModel.dependencies] db in
// If we aren't creating a new thread (ie. sending a message request) then send a
// messageRequestResponse back to the sender (this allows the sender to know that
// they have been approved and can now use this contact in closed groups)
if !isNewThread {
.writePublisher { [dependencies = viewModel.dependencies] db in
/// If this isn't a draft thread (ie. sending a message request) then send a `messageRequestResponse`
/// back to the sender (this allows the sender to know that they have been approved and can now use this
/// contact in closed groups)
if !isDraft {
_ = try? Interaction(
threadId: threadId,
threadVariant: threadVariant,
@ -2648,7 +2645,7 @@ extension ConversationVC {
db,
Contact.Columns.isApproved.set(to: true),
Contact.Columns.didApproveMe
.set(to: contact.didApproveMe || !isNewThread),
.set(to: contact.didApproveMe || !isDraft),
using: dependencies
)
}
@ -2673,8 +2670,8 @@ extension ConversationVC {
return viewModel.dependencies[singleton: .storage]
.writePublisher { [dependencies = viewModel.dependencies] db in
/// Remove any existing `infoGroupInfoInvited` interactions from the group (don't want to have a duplicate one from
/// inside the group history)
/// Remove any existing `infoGroupInfoInvited` interactions from the group (don't want to have a
/// duplicate one from inside the group history)
_ = try Interaction
.filter(Interaction.Columns.threadId == group.id)
.filter(Interaction.Columns.variant == Interaction.Variant.infoGroupInfoInvited)
@ -2691,10 +2688,10 @@ extension ConversationVC {
isHidden: false
).upsert(db)
/// If we aren't creating a new thread (ie. sending a message request) and the user is not an admin
/// then schedule sending a `GroupUpdateInviteResponseMessage` to the group (this allows
/// other members to know that the user has joined the group)
if !isNewThread && group.groupIdentityPrivateKey == nil {
/// If this isn't a draft thread (ie. sending a message request) and the user is not an admin then schedule
/// sending a `GroupUpdateInviteResponseMessage` to the group (this allows other members to
/// know that the user has joined the group)
if !isDraft && group.groupIdentityPrivateKey == nil {
try MessageSender.send(
db,
message: GroupUpdateInviteResponseMessage(
@ -2733,7 +2730,8 @@ extension ConversationVC {
approveMessageRequestIfNeeded(
for: self.viewModel.threadData.threadId,
threadVariant: self.viewModel.threadData.threadVariant,
isNewThread: false,
displayName: self.viewModel.threadData.displayName,
isDraft: (self.viewModel.threadData.threadIsDraft == true),
timestampMs: viewModel.dependencies[cache: .snodeAPI].currentOffsetTimestampMs()
).sinkUntilComplete()
}

@ -76,23 +76,20 @@ public extension Crypto.Generator {
args: [messages, recipients, ed25519PrivateKey, domain]
) {
var outLen: Int = 0
var cMessages: [UnsafePointer<UInt8>?] = (try? (messages
.map { message -> [UInt8] in Array(message) }
.unsafeCopyUInt8Array()))
.defaulting(to: [])
return try messages.map { Array($0) }.withUnsafeUInt8CArray { cMessages in
try recipients.map { Array($0.publicKey) }.withUnsafeUInt8CArray { cRecipients in
var messageSizes: [Int] = messages.map { $0.count }
var cRecipients: [UnsafePointer<UInt8>?] = (try? (recipients
.map { recipient -> [UInt8] in recipient.publicKey }
.unsafeCopyUInt8Array()))
.defaulting(to: [])
var secretKey: [UInt8] = ed25519PrivateKey
var cDomain: [CChar] = try domain.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
var cDomain: [CChar] = try domain.cString(using: .utf8) ?? {
throw LibSessionError.invalidCConversion
}()
let cEncryptedDataPtr: UnsafeMutablePointer<UInt8>? = session_encrypt_for_multiple_simple_ed25519(
&outLen,
&cMessages,
cMessages.baseAddress,
&messageSizes,
messages.count,
&cRecipients,
cRecipients.baseAddress,
recipients.count,
&secretKey,
&cDomain,
@ -109,6 +106,8 @@ public extension Crypto.Generator {
}
}
}
}
}
// MARK: - Decryption

@ -107,22 +107,15 @@ internal extension LibSession {
throw LibSessionError.invalidConfigObject
}
var cMemberIds: [UnsafePointer<CChar>?] = ( try? (memberIds
.map { id in id.cString(using: .utf8) }
.unsafeCopyCStringArray()))
.defaulting(to: [])
defer { cMemberIds.forEach { $0?.deallocate() } }
// Performing a `key_supplement` returns the supplemental key changes, since our state doesn't care
// about the `GROUP_KEYS` needed for other members this change won't result in the `GROUP_KEYS` config
// going into a pending state or the `ConfigurationSyncJob` getting triggered so return the data so that
// the caller can push it directly
return try memberIds.withUnsafeCStrArray { cMemberIds in
/// Performing a `key_supplement` returns the supplemental key changes, since our state doesn't care about the
/// `GROUP_KEYS` needed for other members this change won't result in the `GROUP_KEYS` config going into a pending
/// state or the `ConfigurationSyncJob` getting triggered so return the data so that the caller can push it directly
var cSupplementData: UnsafeMutablePointer<UInt8>!
var cSupplementDataLen: Int = 0
guard
groups_keys_key_supplement(conf, &cMemberIds, cMemberIds.count, &cSupplementData, &cSupplementDataLen),
groups_keys_key_supplement(conf, cMemberIds.baseAddress, cMemberIds.count, &cSupplementData, &cSupplementDataLen),
let cSupplementData: UnsafeMutablePointer<UInt8> = cSupplementData
else { throw LibSessionError.failedToKeySupplementGroup }
@ -136,6 +129,7 @@ internal extension LibSession {
return supplementData
}
}
}
static func loadAdminKey(
_ db: Database,

@ -216,10 +216,9 @@ public extension LibSession {
}
let result: [String] = [String](
pointer: hashList.pointee.value,
count: hashList.pointee.len,
defaultValue: []
)
cStringArray: hashList.pointee.value,
count: hashList.pointee.len
).defaulting(to: [])
hashList.deallocate()
return result
@ -230,10 +229,9 @@ public extension LibSession {
}
let result: [String] = [String](
pointer: hashList.pointee.value,
count: hashList.pointee.len,
defaultValue: []
)
cStringArray: hashList.pointee.value,
count: hashList.pointee.len
).defaulting(to: [])
hashList.deallocate()
return result
@ -251,10 +249,9 @@ public extension LibSession {
}
let result: [String] = [String](
pointer: hashList.pointee.value,
count: hashList.pointee.len,
defaultValue: []
)
cStringArray: hashList.pointee.value,
count: hashList.pointee.len
).defaulting(to: [])
hashList.deallocate()
return result
@ -266,34 +263,13 @@ public extension LibSession {
case .userProfile(let conf), .contacts(let conf),
.convoInfoVolatile(let conf), .userGroups(let conf),
.groupInfo(let conf), .groupMembers(let conf):
var mergeHashes: [UnsafePointer<CChar>?] = (try? (messages
.compactMap { message in message.serverHash.cString(using: .utf8) }
.unsafeCopyCStringArray()))
.defaulting(to: [])
var mergeData: [UnsafePointer<UInt8>?] = (try? (messages
.map { message -> [UInt8] in Array(message.data) }
.unsafeCopyUInt8Array()))
.defaulting(to: [])
defer {
mergeHashes.forEach { $0?.deallocate() }
mergeData.forEach { $0?.deallocate() }
}
guard
mergeHashes.count == messages.count,
mergeData.count == messages.count,
mergeHashes.allSatisfy({ $0 != nil }),
mergeData.allSatisfy({ $0 != nil })
else {
Log.error(.libSession, "Failed to correctly allocate merge data")
return nil
}
return try messages.map { $0.serverHash }.withUnsafeCStrArray { cMergehashes in
try messages.map { Array($0.data) }.withUnsafeUInt8CArray { cMergeData in
var mergeSize: [size_t] = messages.map { size_t($0.data.count) }
let mergedHashesPtr: UnsafeMutablePointer<config_string_list>? = config_merge(
conf,
&mergeHashes,
&mergeData,
cMergehashes.baseAddress,
cMergeData.baseAddress,
&mergeSize,
messages.count
)
@ -304,11 +280,8 @@ public extension LibSession {
// Get the list of hashes from the config (to determine which were successful)
let mergedHashes: [String] = mergedHashesPtr
.map { ptr in
[String](
pointer: ptr.pointee.value,
count: ptr.pointee.len,
defaultValue: []
)
[String](cStringArray: ptr.pointee.value, count: ptr.pointee.len)
.defaulting(to: [])
}
.defaulting(to: [])
mergedHashesPtr?.deallocate()
@ -322,7 +295,8 @@ public extension LibSession {
.map { $0.serverTimestampMs }
.sorted()
.last
}
}
case .groupKeys(let conf, let infoConf, let membersConf):
let successfulMergeTimestamps: [Int64] = try messages

@ -402,7 +402,7 @@ fileprivate extension LibSessionUtilSpec {
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)
expect([String](pointer: mergedHashes?.pointee.value, count: mergedHashes?.pointee.len))
expect([String](cStringArray: mergedHashes?.pointee.value, count: mergedHashes?.pointee.len))
.to(equal(["fakehash2"]))
config_confirm_pushed(conf2, pushData4.pointee.seqno, &cFakeHash2)
mergeHashes.forEach { $0?.deallocate() }
@ -465,9 +465,9 @@ fileprivate extension LibSessionUtilSpec {
let pushData6Data: Data = Data(bytes: pushData6.pointee.config, count: pushData6.pointee.config_len)
let pushData7Data: Data = Data(bytes: pushData7.pointee.config, count: pushData7.pointee.config_len)
expect(pushData6Data).toNot(equal(pushData7Data))
expect([String](pointer: pushData6.pointee.obsolete, count: pushData6.pointee.obsolete_len))
expect([String](cStringArray: pushData6.pointee.obsolete, count: pushData6.pointee.obsolete_len))
.to(equal([fakeHash2]))
expect([String](pointer: pushData7.pointee.obsolete, count: pushData7.pointee.obsolete_len))
expect([String](cStringArray: pushData7.pointee.obsolete, count: pushData7.pointee.obsolete_len))
.to(equal([fakeHash2]))
let fakeHash3a: String = "fakehash3a"
@ -481,7 +481,7 @@ fileprivate extension LibSessionUtilSpec {
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)
expect([String](pointer: mergedHashes2?.pointee.value, count: mergedHashes2?.pointee.len))
expect([String](cStringArray: mergedHashes2?.pointee.value, count: mergedHashes2?.pointee.len))
.to(equal(["fakehash3b"]))
expect(config_needs_push(conf)).to(beTrue())
mergeHashes2.forEach { $0?.deallocate() }
@ -492,7 +492,7 @@ fileprivate extension LibSessionUtilSpec {
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)
expect([String](pointer: mergedHashes3?.pointee.value, count: mergedHashes3?.pointee.len))
expect([String](cStringArray: mergedHashes3?.pointee.value, count: mergedHashes3?.pointee.len))
.to(equal(["fakehash3a"]))
expect(config_needs_push(conf2)).to(beTrue())
mergeHashes3.forEach { $0?.deallocate() }
@ -508,9 +508,9 @@ fileprivate extension LibSessionUtilSpec {
let pushData8Data: Data = Data(bytes: pushData8.pointee.config, count: pushData8.pointee.config_len)
let pushData9Data: Data = Data(bytes: pushData9.pointee.config, count: pushData9.pointee.config_len)
expect(pushData8Data).to(equal(pushData9Data))
expect([String](pointer: pushData8.pointee.obsolete, count: pushData8.pointee.obsolete_len))
expect([String](cStringArray: pushData8.pointee.obsolete, count: pushData8.pointee.obsolete_len))
.to(equal([fakeHash3b, fakeHash3a]))
expect([String](pointer: pushData9.pointee.obsolete, count: pushData9.pointee.obsolete_len))
expect([String](cStringArray: pushData9.pointee.obsolete, count: pushData9.pointee.obsolete_len))
.to(equal([fakeHash3a, fakeHash3b]))
let fakeHash4: String = "fakeHash4"
@ -712,7 +712,7 @@ fileprivate extension LibSessionUtilSpec {
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))
expect([String](cStringArray: mergedHashes?.pointee.value, count: mergedHashes?.pointee.len))
.to(equal(["fakehash1"]))
mergeHashes.forEach { $0?.deallocate() }
mergeData.forEach { $0?.deallocate() }
@ -795,7 +795,7 @@ fileprivate extension LibSessionUtilSpec {
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)
expect([String](pointer: mergedHashes2?.pointee.value, count: mergedHashes2?.pointee.len))
expect([String](cStringArray: mergedHashes2?.pointee.value, count: mergedHashes2?.pointee.len))
.to(equal(["fakehash2"]))
mergeHashes2.forEach { $0?.deallocate() }
mergedHashes2?.deallocate()
@ -805,7 +805,7 @@ fileprivate extension LibSessionUtilSpec {
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)
expect([String](pointer: mergedHashes3?.pointee.value, count: mergedHashes3?.pointee.len))
expect([String](cStringArray: mergedHashes3?.pointee.value, count: mergedHashes3?.pointee.len))
.to(equal(["fakehash3"]))
mergeHashes3.forEach { $0?.deallocate() }
mergedHashes3?.deallocate()
@ -1021,7 +1021,7 @@ fileprivate extension LibSessionUtilSpec {
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)
expect([String](pointer: mergedHashes?.pointee.value, count: mergedHashes?.pointee.len))
expect([String](cStringArray: mergedHashes?.pointee.value, count: mergedHashes?.pointee.len))
.to(equal(["fakehash2"]))
config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash2)
mergeHashes.forEach { $0?.deallocate() }
@ -1188,7 +1188,7 @@ fileprivate extension LibSessionUtilSpec {
// testing:
let pushData1: UnsafeMutablePointer<config_push_data> = config_push(conf)
expect(pushData1.pointee.seqno).to(equal(0))
expect([String](pointer: pushData1.pointee.obsolete, count: pushData1.pointee.obsolete_len))
expect([String](cStringArray: pushData1.pointee.obsolete, count: pushData1.pointee.obsolete_len))
.to(beEmpty())
expect(pushData1.pointee.config_len).to(equal(432))
pushData1.deallocate()
@ -1286,7 +1286,7 @@ fileprivate extension LibSessionUtilSpec {
// dumps; even though we changed two fields here).
let pushData2: UnsafeMutablePointer<config_push_data> = config_push(conf)
expect(pushData2.pointee.seqno).to(equal(1))
expect([String](pointer: pushData2.pointee.obsolete, count: pushData2.pointee.obsolete_len))
expect([String](cStringArray: pushData2.pointee.obsolete, count: pushData2.pointee.obsolete_len))
.to(beEmpty())
// Pretend we uploaded it
@ -1310,12 +1310,12 @@ fileprivate extension LibSessionUtilSpec {
let pushData3: UnsafeMutablePointer<config_push_data> = config_push(conf)
expect(pushData3.pointee.seqno).to(equal(1))
expect([String](pointer: pushData3.pointee.obsolete, count: pushData3.pointee.obsolete_len))
expect([String](cStringArray: pushData3.pointee.obsolete, count: pushData3.pointee.obsolete_len))
.to(beEmpty())
pushData3.deallocate()
let currentHashes1: UnsafeMutablePointer<config_string_list>? = config_current_hashes(conf)
expect([String](pointer: currentHashes1?.pointee.value, count: currentHashes1?.pointee.len))
expect([String](cStringArray: currentHashes1?.pointee.value, count: currentHashes1?.pointee.len))
.to(equal(["fakehash1"]))
currentHashes1?.deallocate()
@ -1325,12 +1325,12 @@ fileprivate extension LibSessionUtilSpec {
let pushData4: UnsafeMutablePointer<config_push_data> = config_push(conf2)
expect(pushData4.pointee.seqno).to(equal(1))
expect(config_needs_dump(conf2)).to(beFalse())
expect([String](pointer: pushData4.pointee.obsolete, count: pushData4.pointee.obsolete_len))
expect([String](cStringArray: pushData4.pointee.obsolete, count: pushData4.pointee.obsolete_len))
.to(beEmpty())
pushData4.deallocate()
let currentHashes2: UnsafeMutablePointer<config_string_list>? = config_current_hashes(conf2)
expect([String](pointer: currentHashes2?.pointee.value, count: currentHashes2?.pointee.len))
expect([String](cStringArray: currentHashes2?.pointee.value, count: currentHashes2?.pointee.len))
.to(equal(["fakehash1"]))
currentHashes2?.deallocate()
@ -1440,11 +1440,11 @@ fileprivate extension LibSessionUtilSpec {
let pushData7: UnsafeMutablePointer<config_push_data> = config_push(conf2)
expect(pushData7.pointee.seqno).to(equal(2))
config_confirm_pushed(conf2, pushData7.pointee.seqno, &cFakeHash2)
expect([String](pointer: pushData7.pointee.obsolete, count: pushData7.pointee.obsolete_len))
expect([String](cStringArray: pushData7.pointee.obsolete, count: pushData7.pointee.obsolete_len))
.to(equal([fakeHash1]))
let currentHashes3: UnsafeMutablePointer<config_string_list>? = config_current_hashes(conf2)
expect([String](pointer: currentHashes3?.pointee.value, count: currentHashes3?.pointee.len))
expect([String](cStringArray: currentHashes3?.pointee.value, count: currentHashes3?.pointee.len))
.to(equal([fakeHash2]))
currentHashes3?.deallocate()
@ -1464,7 +1464,7 @@ fileprivate extension LibSessionUtilSpec {
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)
expect([String](pointer: mergedHashes1?.pointee.value, count: mergedHashes1?.pointee.len))
expect([String](cStringArray: mergedHashes1?.pointee.value, count: mergedHashes1?.pointee.len))
.to(equal(["fakehash2"]))
mergeHashes1.forEach { $0?.deallocate() }
mergedHashes1?.deallocate()
@ -1509,11 +1509,11 @@ fileprivate extension LibSessionUtilSpec {
config_confirm_pushed(conf2, pushData10.pointee.seqno, &cFakeHash3)
expect(pushData10.pointee.seqno).to(equal(3))
expect([String](pointer: pushData10.pointee.obsolete, count: pushData10.pointee.obsolete_len))
expect([String](cStringArray: pushData10.pointee.obsolete, count: pushData10.pointee.obsolete_len))
.to(equal([fakeHash2]))
let currentHashes4: UnsafeMutablePointer<config_string_list>? = config_current_hashes(conf2)
expect([String](pointer: currentHashes4?.pointee.value, count: currentHashes4?.pointee.len))
expect([String](cStringArray: currentHashes4?.pointee.value, count: currentHashes4?.pointee.len))
.to(equal([fakeHash3]))
currentHashes4?.deallocate()
@ -1521,7 +1521,7 @@ fileprivate extension LibSessionUtilSpec {
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)
expect([String](pointer: mergedHashes2?.pointee.value, count: mergedHashes2?.pointee.len))
expect([String](cStringArray: mergedHashes2?.pointee.value, count: mergedHashes2?.pointee.len))
.to(equal(["fakehash3"]))
mergeHashes2.forEach { $0?.deallocate() }
mergedHashes2?.deallocate()
@ -1556,7 +1556,7 @@ fileprivate extension LibSessionUtilSpec {
let pushData11: UnsafeMutablePointer<config_push_data> = config_push(conf)
config_confirm_pushed(conf, pushData11.pointee.seqno, &cFakeHash4)
expect(pushData11.pointee.seqno).to(equal(4))
expect([String](pointer: pushData11.pointee.obsolete, count: pushData11.pointee.obsolete_len))
expect([String](cStringArray: pushData11.pointee.obsolete, count: pushData11.pointee.obsolete_len))
.to(equal([fakeHash3, fakeHash2, fakeHash1]))
// Load some obsolete ones in just to check that they get immediately obsoleted
@ -1580,7 +1580,7 @@ fileprivate extension LibSessionUtilSpec {
pushData11.pointee.config_len
]
let mergedHashes3: UnsafeMutablePointer<config_string_list>? = config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 4)
expect([String](pointer: mergedHashes3?.pointee.value, count: mergedHashes3?.pointee.len))
expect([String](cStringArray: 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())
@ -1592,13 +1592,13 @@ fileprivate extension LibSessionUtilSpec {
pushData11.deallocate()
let currentHashes5: UnsafeMutablePointer<config_string_list>? = config_current_hashes(conf2)
expect([String](pointer: currentHashes5?.pointee.value, count: currentHashes5?.pointee.len))
expect([String](cStringArray: currentHashes5?.pointee.value, count: currentHashes5?.pointee.len))
.to(equal([fakeHash4]))
currentHashes5?.deallocate()
let pushData12: UnsafeMutablePointer<config_push_data> = config_push(conf2)
expect(pushData12.pointee.seqno).to(equal(4))
expect([String](pointer: pushData12.pointee.obsolete, count: pushData12.pointee.obsolete_len))
expect([String](cStringArray: pushData12.pointee.obsolete, count: pushData12.pointee.obsolete_len))
.to(equal([fakeHash11, fakeHash12, fakeHash10, fakeHash3]))
pushData12.deallocate()
@ -1716,7 +1716,7 @@ fileprivate extension LibSessionUtilSpec {
var mergeData1: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData1.pointee.config)]
var mergeSize1: [Int] = [pushData1.pointee.config_len]
let mergedHashes1: UnsafeMutablePointer<config_string_list>? = config_merge(conf2, &mergeHashes1, &mergeData1, &mergeSize1, 1)
expect([String](pointer: mergedHashes1?.pointee.value, count: mergedHashes1?.pointee.len))
expect([String](cStringArray: mergedHashes1?.pointee.value, count: mergedHashes1?.pointee.len))
.to(equal(["fakehash1"]))
expect(config_needs_push(conf2)).to(beFalse())
mergeHashes1.forEach { $0?.deallocate() }
@ -1745,10 +1745,9 @@ fileprivate extension LibSessionUtilSpec {
groups_info_destroy_group(conf2)
let pushData2: UnsafeMutablePointer<config_push_data> = config_push(conf2)
let obsoleteHashes: [String] = [String](
pointer: pushData2.pointee.obsolete,
count: pushData2.pointee.obsolete_len,
defaultValue: []
let obsoleteHashes: [String]? = [String](
cStringArray: pushData2.pointee.obsolete,
count: pushData2.pointee.obsolete_len
)
expect(pushData2.pointee.seqno).to(equal(2))
expect(pushData2.pointee.config_len).to(equal(512))
@ -1762,7 +1761,7 @@ fileprivate extension LibSessionUtilSpec {
var mergeData2: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData2.pointee.config)]
var mergeSize2: [Int] = [pushData2.pointee.config_len]
let mergedHashes2: UnsafeMutablePointer<config_string_list>? = config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)
expect([String](pointer: mergedHashes2?.pointee.value, count: mergedHashes2?.pointee.len))
expect([String](cStringArray: mergedHashes2?.pointee.value, count: mergedHashes2?.pointee.len))
.to(equal(["fakehash2"]))
mergeHashes2.forEach { $0?.deallocate() }
mergedHashes2?.deallocate()
@ -1800,7 +1799,7 @@ fileprivate extension LibSessionUtilSpec {
var mergeData3: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData3.pointee.config)]
var mergeSize3: [Int] = [pushData3.pointee.config_len]
let mergedHashes3: UnsafeMutablePointer<config_string_list>? = config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 1)
expect([String](pointer: mergedHashes3?.pointee.value, count: mergedHashes3?.pointee.len))
expect([String](cStringArray: mergedHashes3?.pointee.value, count: mergedHashes3?.pointee.len))
.to(equal(["fakehash3"]))
mergeHashes3.forEach { $0?.deallocate() }
mergedHashes3?.deallocate()
@ -2069,7 +2068,7 @@ fileprivate extension LibSessionUtilSpec {
var mergeData1: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData1.pointee.config)]
var mergeSize1: [Int] = [pushData1.pointee.config_len]
let mergedHashes1: UnsafeMutablePointer<config_string_list>? = config_merge(conf2, &mergeHashes1, &mergeData1, &mergeSize1, 1)
expect([String](pointer: mergedHashes1?.pointee.value, count: mergedHashes1?.pointee.len))
expect([String](cStringArray: mergedHashes1?.pointee.value, count: mergedHashes1?.pointee.len))
.to(equal(["fakehash1"]))
expect(config_needs_push(conf2)).to(beFalse())
mergeHashes1.forEach { $0?.deallocate() }
@ -2160,10 +2159,9 @@ fileprivate extension LibSessionUtilSpec {
groups_members_set(conf2, &member1)
let pushData2: UnsafeMutablePointer<config_push_data> = config_push(conf2)
let obsoleteHashes: [String] = [String](
pointer: pushData2.pointee.obsolete,
count: pushData2.pointee.obsolete_len,
defaultValue: []
let obsoleteHashes: [String]? = [String](
cStringArray: pushData2.pointee.obsolete,
count: pushData2.pointee.obsolete_len
)
expect(pushData2.pointee.seqno).to(equal(2))
expect(pushData2.pointee.config_len).to(equal(1024))
@ -2177,7 +2175,7 @@ fileprivate extension LibSessionUtilSpec {
var mergeData2: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData2.pointee.config)]
var mergeSize2: [Int] = [pushData2.pointee.config_len]
let mergedHashes2: UnsafeMutablePointer<config_string_list>? = config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)
expect([String](pointer: mergedHashes2?.pointee.value, count: mergedHashes2?.pointee.len))
expect([String](cStringArray: mergedHashes2?.pointee.value, count: mergedHashes2?.pointee.len))
.to(equal(["fakehash2"]))
mergeHashes2.forEach { $0?.deallocate() }
mergedHashes2?.deallocate()
@ -2334,10 +2332,9 @@ fileprivate extension LibSessionUtilSpec {
}
let pushData3: UnsafeMutablePointer<config_push_data> = config_push(conf)
let obsoleteHashes3: [String] = [String](
pointer: pushData3.pointee.obsolete,
count: pushData3.pointee.obsolete_len,
defaultValue: []
let obsoleteHashes3: [String]? = [String](
cStringArray: pushData3.pointee.obsolete,
count: pushData3.pointee.obsolete_len
)
expect(pushData3.pointee.seqno).to(equal(3))
expect(pushData3.pointee.config_len).to(equal(1024))
@ -2351,7 +2348,7 @@ fileprivate extension LibSessionUtilSpec {
var mergeData3: [UnsafePointer<UInt8>?] = [UnsafePointer(pushData3.pointee.config)]
var mergeSize3: [Int] = [pushData3.pointee.config_len]
let mergedHashes3: UnsafeMutablePointer<config_string_list>? = config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 1)
expect([String](pointer: mergedHashes3?.pointee.value, count: mergedHashes3?.pointee.len))
expect([String](cStringArray: mergedHashes3?.pointee.value, count: mergedHashes3?.pointee.len))
.to(equal(["fakehash3"]))
mergeHashes3.forEach { $0?.deallocate() }
mergedHashes3?.deallocate()
@ -2703,3 +2700,50 @@ private extension LibSessionUtilSpec {
return false
}
}
private extension Collection where Element == [CChar]? {
/// This creates an array of UnsafePointer types to access data of the C strings in memory. This array provides no automated
/// memory management of it's children so after use you are responsible for handling the life cycle of the child elements and
/// need to call `deallocate()` on each child.
func unsafeCopyCStringArray() throws -> [UnsafePointer<CChar>?] {
return try self.map { value in
guard let value: [CChar] = value else { return nil }
let copy = UnsafeMutableBufferPointer<CChar>.allocate(capacity: value.underestimatedCount)
var remaining: (unwritten: Array<CChar>.Iterator, index: Int) = copy.initialize(from: value)
guard remaining.unwritten.next() == nil else { throw LibSessionError.invalidCConversion }
return UnsafePointer(copy.baseAddress)
}
}
}
private extension Collection where Element == [CChar] {
/// This creates an array of UnsafePointer types to access data of the C strings in memory. This array provides no automated
/// memory management of it's children so after use you are responsible for handling the life cycle of the child elements and
/// need to call `deallocate()` on each child.
func unsafeCopyCStringArray() throws -> [UnsafePointer<CChar>?] {
return try self.map { value in
let copy = UnsafeMutableBufferPointer<CChar>.allocate(capacity: value.underestimatedCount)
var remaining: (unwritten: Array<CChar>.Iterator, index: Int) = copy.initialize(from: value)
guard remaining.unwritten.next() == nil else { throw LibSessionError.invalidCConversion }
return UnsafePointer(copy.baseAddress)
}
}
}
private extension Collection where Element == [UInt8] {
/// This creates an array of UnsafePointer types to access data of the C strings in memory. This array provides no automated
/// memory management of it's children so after use you are responsible for handling the life cycle of the child elements and
/// need to call `deallocate()` on each child.
func unsafeCopyUInt8Array() throws -> [UnsafePointer<UInt8>?] {
return try self.map { value in
let copy = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: value.underestimatedCount)
var remaining: (unwritten: Array<UInt8>.Iterator, index: Int) = copy.initialize(from: value)
guard remaining.unwritten.next() == nil else { throw LibSessionError.invalidCConversion }
return UnsafePointer(copy.baseAddress)
}
}
}

@ -253,13 +253,12 @@ class LibSessionNetwork: NetworkType {
throw NetworkError.invalidPreparedRequest
case .snode(let snode, let swarmPublicKey):
let cSwarmPublicKey: UnsafePointer<CChar>? = try swarmPublicKey.map {
let cSwarmPublicKey: [CChar]? = try swarmPublicKey.map {
_ = try SessionId(from: $0)
// Quick way to drop '05' prefix if present
return $0.suffix(64).cString(using: .utf8)?.unsafeCopy()
}
wrapper.addUnsafePointerToCleanup(cSwarmPublicKey)
return $0.suffix(64).cString(using: .utf8)
}.flatMap { $0 }
network_send_onion_request_to_snode_destination(
network,
@ -279,9 +278,10 @@ class LibSessionNetwork: NetworkType {
)
case .server:
try destination.withUnsafePointer { cServerDestination in
network_send_onion_request_to_server_destination(
network,
try wrapper.cServerDestination(destination),
cServerDestination,
cPayloadBytes,
cPayloadBytes.count,
Int64(floor(requestTimeout * 1000)),
@ -294,13 +294,15 @@ class LibSessionNetwork: NetworkType {
},
wrapper.unsafePointer()
)
}
case .serverUpload(_, let fileName):
guard !cPayloadBytes.isEmpty else { throw NetworkError.invalidPreparedRequest }
try destination.withUnsafePointer { cServerDestination in
network_upload_to_server(
network,
try wrapper.cServerDestination(destination),
cServerDestination,
cPayloadBytes,
cPayloadBytes.count,
fileName?.cString(using: .utf8),
@ -314,11 +316,13 @@ class LibSessionNetwork: NetworkType {
},
wrapper.unsafePointer()
)
}
case .serverDownload:
try destination.withUnsafePointer { cServerDestination in
network_download_from_server(
network,
try wrapper.cServerDestination(destination),
cServerDestination,
Int64(floor(requestTimeout * 1000)),
Int64(floor((requestAndPathBuildTimeout ?? 0) * 1000)),
{ success, timeout, statusCode, cHeaders, cHeaderVals, headerLen, dataPtr, dataLen, ctx in
@ -329,6 +333,7 @@ class LibSessionNetwork: NetworkType {
},
wrapper.unsafePointer()
)
}
case .cached(let success, let timeout, let statusCode, let headers, let data):
wrapper.run((success, timeout, statusCode, headers, data))
@ -406,13 +411,6 @@ class LibSessionNetwork: NetworkType {
private extension LibSessionNetwork {
class CallbackWrapper<Output> {
public let resultPublisher: CurrentValueSubject<Output?, Error> = CurrentValueSubject(nil)
private var pointersToDeallocate: [UnsafeRawPointer?] = []
// MARK: - Initialization
deinit {
pointersToDeallocate.forEach { $0?.deallocate() }
}
// MARK: - Functions
@ -430,10 +428,6 @@ private extension LibSessionNetwork {
public func unsafePointer() -> UnsafeMutableRawPointer { Unmanaged.passRetained(self).toOpaque() }
public func addUnsafePointerToCleanup<T>(_ pointer: UnsafePointer<T>?) {
pointersToDeallocate.append(UnsafeRawPointer(pointer))
}
public func run(_ output: Output) {
resultPublisher.send(output)
}
@ -554,32 +548,21 @@ extension network_service_node: @retroactive CAccessible, @retroactive CMutable
// MARK: - Convenience
private extension LibSessionNetwork.CallbackWrapper {
static func headers(
_ cHeaders: UnsafeMutablePointer<UnsafePointer<CChar>?>?,
_ cHeaderVals: UnsafeMutablePointer<UnsafePointer<CChar>?>?,
_ count: Int
) -> [String: String] {
let headers: [String] = [String](pointer: cHeaders, count: count, defaultValue: [])
let headerVals: [String] = [String](pointer: cHeaderVals, count: count, defaultValue: [])
return zip(headers, headerVals)
.reduce(into: [:]) { result, next in result[next.0] = next.1 }
}
func cServerDestination(_ destination: Network.Destination) throws -> network_server_destination {
private extension Network.Destination {
func withUnsafePointer<Result>(_ body: (network_server_destination) throws -> Result) throws -> Result {
let method: HTTPMethod
let url: URL
let headers: [HTTPHeader: String]?
let x25519PublicKey: String
switch destination {
switch self {
case .snode, .randomSnode, .randomSnodeLatestNetworkTimeTarget, .cached: throw NetworkError.invalidPreparedRequest
case .server(let info), .serverUpload(let info, _), .serverDownload(let info):
method = info.method
url = try info.url
headers = info.headers
x25519PublicKey = info.x25519PublicKey
x25519PublicKey = String(info.x25519PublicKey
.suffix(64)) // Quick way to drop '05' prefix if present
}
guard let host: String = url.host else { throw NetworkError.invalidURL }
@ -587,75 +570,56 @@ private extension LibSessionNetwork.CallbackWrapper {
throw LibSessionError.invalidCConversion
}
let headerInfo: [(key: String, value: String)]? = headers?.map { ($0.key, $0.value) }
// Handle the more complicated type conversions first
let cHeaderKeysContent: [UnsafePointer<CChar>?] = (try? ((headerInfo ?? [])
.map { $0.key.cString(using: .utf8) }
.unsafeCopyCStringArray()))
.defaulting(to: [])
let cHeaderValuesContent: [UnsafePointer<CChar>?] = (try? ((headerInfo ?? [])
.map { $0.value.cString(using: .utf8) }
.unsafeCopyCStringArray()))
.defaulting(to: [])
let targetScheme: String = (url.scheme ?? "https")
let endpoint: String = url.path
.appending(url.query.map { value in "?\(value)" } ?? "")
let port: UInt16 = UInt16(url.port ?? (targetScheme == "https" ? 443 : 80))
let headerKeys: [String] = (headers?.map { $0.key } ?? [])
let headerValues: [String] = (headers?.map { $0.value } ?? [])
let headersSize = headerKeys.count
// Use scoped closure to avoid manual memory management (crazy nesting but it ends up safer)
return try method.rawValue.withCString { cMethodPtr in
try targetScheme.withCString { cTargetSchemePtr in
try host.withCString { cHostPtr in
try endpoint.withCString { cEndpointPtr in
try x25519PublicKey.withCString { cX25519PubkeyPtr in
try headerKeys.withUnsafeCStrArray { headerKeysPtr in
try headerValues.withUnsafeCStrArray { headerValuesPtr in
let cServerDest = network_server_destination(
method: cMethodPtr,
protocol: cTargetSchemePtr,
host: cHostPtr,
endpoint: cEndpointPtr,
port: port,
x25519_pubkey: cX25519PubkeyPtr,
headers: headerKeysPtr.baseAddress,
header_values: headerValuesPtr.baseAddress,
headers_size: headersSize
)
guard
cHeaderKeysContent.count == cHeaderValuesContent.count,
cHeaderKeysContent.allSatisfy({ $0 != nil }),
cHeaderValuesContent.allSatisfy({ $0 != nil })
else {
cHeaderKeysContent.forEach { $0?.deallocate() }
cHeaderValuesContent.forEach { $0?.deallocate() }
throw LibSessionError.invalidCConversion
return try body(cServerDest)
}
}
}
}
}
}
}
}
}
// Convert the other types
let targetScheme: String = (url.scheme ?? "https")
let cMethod: UnsafePointer<CChar>? = method.rawValue
.cString(using: .utf8)?
.unsafeCopy()
let cTargetScheme: UnsafePointer<CChar>? = targetScheme
.cString(using: .utf8)?
.unsafeCopy()
let cHost: UnsafePointer<CChar>? = host
.cString(using: .utf8)?
.unsafeCopy()
let cEndpoint: UnsafePointer<CChar>? = url.path
.appending(url.query.map { value in "?\(value)" })
.cString(using: .utf8)?
.unsafeCopy()
let cX25519Pubkey: UnsafePointer<CChar>? = x25519PublicKey
.suffix(64) // Quick way to drop '05' prefix if present
.cString(using: .utf8)?
.unsafeCopy()
let cHeaderKeys: UnsafeMutablePointer<UnsafePointer<CChar>?>? = cHeaderKeysContent
.unsafeCopy()
let cHeaderValues: UnsafeMutablePointer<UnsafePointer<CChar>?>? = cHeaderValuesContent
.unsafeCopy()
let cServerDestination = network_server_destination(
method: cMethod,
protocol: cTargetScheme,
host: cHost,
endpoint: cEndpoint,
port: UInt16(url.port ?? (targetScheme == "https" ? 443 : 80)),
x25519_pubkey: cX25519Pubkey,
headers: cHeaderKeys,
header_values: cHeaderValues,
headers_size: (headerInfo ?? []).count
)
private extension LibSessionNetwork.CallbackWrapper {
static func headers(
_ cHeaders: UnsafePointer<UnsafePointer<CChar>?>?,
_ cHeaderVals: UnsafePointer<UnsafePointer<CChar>?>?,
_ count: Int
) -> [String: String] {
let headers: [String] = ([String](cStringArray: cHeaders, count: count) ?? [])
let headerVals: [String] = ([String](cStringArray: cHeaderVals, count: count) ?? [])
// Add a cleanup callback to deallocate the header arrays
self.addUnsafePointerToCleanup(cMethod)
self.addUnsafePointerToCleanup(cTargetScheme)
self.addUnsafePointerToCleanup(cHost)
self.addUnsafePointerToCleanup(cEndpoint)
self.addUnsafePointerToCleanup(cX25519Pubkey)
cHeaderKeysContent.forEach { self.addUnsafePointerToCleanup($0) }
cHeaderValuesContent.forEach { self.addUnsafePointerToCleanup($0) }
self.addUnsafePointerToCleanup(cHeaderKeys)
self.addUnsafePointerToCleanup(cHeaderValues)
return cServerDestination
return zip(headers, headerVals)
.reduce(into: [:]) { result, next in result[next.0] = next.1 }
}
}

@ -116,7 +116,7 @@ class BatchRequestSpec: QuickSpec {
let requestData: Data? = try? JSONEncoder().encode(request)
let requestJson: [[String: Any]]? = requestData
.map { try? JSONSerialization.jsonObject(with: $0) as? [[String: Any]] }
.map { (try? JSONSerialization.jsonObject(with: $0)) as? [[String: Any]] }
expect(requestJson?.first?["path"] as? String).to(equal("/endpoint1"))
expect(requestJson?.first?["method"] as? String).to(equal("GET"))
expect(requestJson?.first?["b64"] as? String).to(equal("testBody"))

@ -2,81 +2,65 @@
import Foundation
extension Collection {
public subscript(ifValid index: Index) -> Iterator.Element? {
return self.indices.contains(index) ? self[index] : nil
}
public extension Collection where Element == String {
func withUnsafeCStrArray<R>(
_ body: (UnsafeBufferPointer<UnsafePointer<CChar>?>) throws -> R
) throws -> R {
let cCharArrays: [[CChar]] = try map { swiftStr in
guard let cChars = swiftStr.cString(using: .utf8) else {
throw LibSessionError.invalidCConversion
}
public extension Collection {
/// This creates an UnsafeMutableBufferPointer to access data in memory directly. This result pointer provides no automated
/// memory management so after use you are responsible for handling the life cycle and need to call `deallocate()`.
func unsafeCopy() -> UnsafeMutableBufferPointer<Element> {
let copy = UnsafeMutableBufferPointer<Element>.allocate(capacity: self.underestimatedCount)
_ = copy.initialize(from: self)
return copy
return cChars
}
/// This creates an UnsafePointer to access data in memory directly. This result pointer provides no automated
/// memory management so after use you are responsible for handling the life cycle and need to call `deallocate()`.
func unsafeCopy() -> UnsafePointer<Element>? {
let copy = UnsafeMutableBufferPointer<Element>.allocate(capacity: self.underestimatedCount)
_ = copy.initialize(from: self)
return UnsafePointer(copy.baseAddress)
func getPointers(
remainingArrays: ArraySlice<[CChar]>,
collectedPointers: [UnsafePointer<CChar>?]
) throws -> R {
guard let currentArray = remainingArrays.first else {
return try collectedPointers.withUnsafeBufferPointer { collectedPointersBuffer in
try body(collectedPointersBuffer)
}
}
/// This creates an UnsafePointer to access data in memory directly. This result pointer provides no automated
/// memory management so after use you are responsible for handling the life cycle and need to call `deallocate()`.
func unsafeCopy() -> UnsafeMutablePointer<Element>? {
let copy = UnsafeMutableBufferPointer<Element>.allocate(capacity: self.underestimatedCount)
_ = copy.initialize(from: self)
return UnsafeMutablePointer(copy.baseAddress)
return try currentArray.withUnsafeBufferPointer { currentBufferPtr in
try getPointers(
remainingArrays: remainingArrays.dropFirst(),
collectedPointers: collectedPointers + [currentBufferPtr.baseAddress]
)
}
}
public extension Collection where Element == [CChar]? {
/// This creates an array of UnsafePointer types to access data of the C strings in memory. This array provides no automated
/// memory management of it's children so after use you are responsible for handling the life cycle of the child elements and
/// need to call `deallocate()` on each child.
func unsafeCopyCStringArray() throws -> [UnsafePointer<CChar>?] {
return try self.map { value in
guard let value: [CChar] = value else { return nil }
let copy = UnsafeMutableBufferPointer<CChar>.allocate(capacity: value.underestimatedCount)
var remaining: (unwritten: Array<CChar>.Iterator, index: Int) = copy.initialize(from: value)
guard remaining.unwritten.next() == nil else { throw LibSessionError.invalidCConversion }
return UnsafePointer(copy.baseAddress)
}
return try getPointers(remainingArrays: ArraySlice(cCharArrays), collectedPointers: [])
}
}
public extension Collection where Element == [CChar] {
/// This creates an array of UnsafePointer types to access data of the C strings in memory. This array provides no automated
/// memory management of it's children so after use you are responsible for handling the life cycle of the child elements and
/// need to call `deallocate()` on each child.
func unsafeCopyCStringArray() throws -> [UnsafePointer<CChar>?] {
return try self.map { value in
let copy = UnsafeMutableBufferPointer<CChar>.allocate(capacity: value.underestimatedCount)
var remaining: (unwritten: Array<CChar>.Iterator, index: Int) = copy.initialize(from: value)
guard remaining.unwritten.next() == nil else { throw LibSessionError.invalidCConversion }
public extension Collection where Element == [UInt8]? {
func withUnsafeUInt8CArray<R>(
_ body: (UnsafeBufferPointer<UnsafePointer<UInt8>?>) throws -> R
) throws -> R {
func processNext<I: IteratorProtocol>(
iterator: inout I,
collectedPointers: [UnsafePointer<UInt8>?]
) throws -> R where I.Element == Element {
if let optionalElement: [UInt8]? = iterator.next() {
if let array: [UInt8] = optionalElement {
return try array.withUnsafeBufferPointer { bufferPtr in
try processNext(iterator: &iterator, collectedPointers: collectedPointers + [bufferPtr.baseAddress])
}
}
return UnsafePointer(copy.baseAddress)
// It's a nil element in the input collection, add a nil pointer and recurse
return try processNext(iterator: &iterator, collectedPointers: collectedPointers + [nil])
} else {
return try collectedPointers.withUnsafeBufferPointer { pointersBuffer in
try body(pointersBuffer)
}
}
}
public extension Collection where Element == [UInt8] {
/// This creates an array of UnsafePointer types to access data of the C strings in memory. This array provides no automated
/// memory management of it's children so after use you are responsible for handling the life cycle of the child elements and
/// need to call `deallocate()` on each child.
func unsafeCopyUInt8Array() throws -> [UnsafePointer<UInt8>?] {
return try self.map { value in
let copy = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: value.underestimatedCount)
var remaining: (unwritten: Array<UInt8>.Iterator, index: Int) = copy.initialize(from: value)
guard remaining.unwritten.next() == nil else { throw LibSessionError.invalidCConversion }
return UnsafePointer(copy.baseAddress)
}
var iterator = makeIterator()
return try processNext(iterator: &iterator, collectedPointers: [])
}
}

@ -21,68 +21,37 @@ public extension String {
// MARK: - Array
public extension Array where Element == String {
init?(pointer: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?, count: Int?) {
init?(cStringArray: UnsafePointer<UnsafePointer<CChar>?>?, count: Int?) {
/// If `count` was provided but is `0` then accessing the pointer could crash (as it could be bad memory) so just return an empty array
guard let count: Int = count else { return nil }
guard count > 0 else {
self = []
return
}
self.init(pointee: pointer.map { $0.pointee.map { UnsafePointer($0) } }, count: count)
}
guard
let cStringArray: UnsafePointer<UnsafePointer<CChar>?> = cStringArray,
let count: Int = count
else { return nil }
init?(pointer: UnsafeMutablePointer<UnsafePointer<CChar>?>?, count: Int?) {
/// If `count` was provided but is `0` then accessing the pointer could crash (as it could be bad memory) so just return an empty array
guard let count: Int = count else { return nil }
guard count > 0 else {
self = []
return
}
self.init()
self.reserveCapacity(count)
self.init(pointee: pointer.map { $0.pointee }, count: count)
for i in 0..<count {
if let cStringPtr: UnsafePointer<CChar> = cStringArray[i] {
self.append(String(cString: cStringPtr))
}
init(
pointer: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?,
count: Int?,
defaultValue: [String]
) {
self = ([String](pointer: pointer, count: count) ?? defaultValue)
}
init(
pointer: UnsafeMutablePointer<UnsafePointer<CChar>?>?,
count: Int?,
defaultValue: [String]
) {
self = ([String](pointer: pointer, count: count) ?? defaultValue)
}
init?(
pointee: UnsafePointer<CChar>?,
count: Int?
) {
init?(cStringArray: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?, count: Int?) {
/// If `count` was provided but is `0` then accessing the pointer could crash (as it could be bad memory) so just return an empty array
guard
let count: Int = count,
let pointee: UnsafePointer<CChar> = pointee
let cStringArray: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?> = cStringArray,
let count: Int = count
else { return nil }
/// If `count` was provided but is `0` then accessing the pointer could crash (as it could be bad memory) so just return an empty array
guard count > 0 else {
self = []
return
}
self = (0..<count).reduce(into: []) { result, index in
/// We need to calculate the start position of each of the hashes in memory which will
/// be at the end of the previous hash plus one (due to the null termination character
/// which isn't included in Swift strings so isn't included in `count`)
let prevLength: Int = (result.isEmpty ? 0 :
result.map { ($0.count + 1) }.reduce(0, +)
)
self.init()
self.reserveCapacity(count)
result.append(String(cString: pointee.advanced(by: prevLength)))
for i in 0..<count {
if let cStringPtr: UnsafeMutablePointer<CChar> = cStringArray[i] {
self.append(String(cString: cStringPtr))
}
}
}
}

@ -304,71 +304,51 @@ class TypeConversionUtilitiesSpec: QuickSpec {
context("when initialised with a 2D C array") {
// MARK: ---- returns the correct array
it("returns the correct array") {
var test: [CChar] = (
"Test1".cString(using: .utf8)! +
"Test2".cString(using: .utf8)! +
"Test3AndExtra".cString(using: .utf8)!
)
let result = test.withUnsafeMutableBufferPointer { ptr in
var mutablePtr = UnsafePointer(ptr.baseAddress)
var test: [String] = ["Test1", "Test2", "Test3AndExtra"]
return [String](pointer: &mutablePtr, count: 3)
let result = try! test.withUnsafeCStrArray { ptr in
return [String](cStringArray: ptr.baseAddress, count: 3)
}
expect(result).to(equal(["Test1", "Test2", "Test3AndExtra"]))
}
// MARK: ---- returns an empty array if given one
it("returns an empty array if given one") {
var test = [CChar]()
let result = test.withUnsafeMutableBufferPointer { ptr in
var mutablePtr = UnsafePointer(ptr.baseAddress)
var test: [String] = []
return [String](pointer: &mutablePtr, count: 0)
let result = try! test.withUnsafeCStrArray { ptr in
return [String](cStringArray: ptr.baseAddress, count: 0)
}
expect(result).to(equal([]))
}
// MARK: ---- handles empty strings without issues
it("handles empty strings without issues") {
var test: [CChar] = (
"Test1".cString(using: .utf8)! +
"".cString(using: .utf8)! +
"Test2".cString(using: .utf8)!
)
let result = test.withUnsafeMutableBufferPointer { ptr in
var mutablePtr = UnsafePointer(ptr.baseAddress)
var test: [String] = ["Test1", "", "Test2"]
return [String](pointer: &mutablePtr, count: 3)
let result = try! test.withUnsafeCStrArray { ptr in
return [String](cStringArray: ptr.baseAddress, count: 3)
}
expect(result).to(equal(["Test1", "", "Test2"]))
}
// MARK: ---- returns null when given a null pointer
it("returns null when given a null pointer") {
expect([String](pointee: nil, count: 5)).to(beNil())
expect([String](
cStringArray: Optional<UnsafePointer<UnsafePointer<CChar>?>>.none,
count: 5)
).to(beNil())
}
// MARK: ---- returns null when given a null count
it("returns null when given a null count") {
var test: [CChar] = "Test1".cString(using: .utf8)!
let result = test.withUnsafeMutableBufferPointer { ptr in
var mutablePtr = UnsafeMutablePointer(ptr.baseAddress)
var test: [String] = ["Test1"]
return [String](pointer: &mutablePtr, count: nil)
let result = try! test.withUnsafeCStrArray { ptr in
return [String](cStringArray: ptr.baseAddress, count: nil)
}
expect(result).to(beNil())
}
// MARK: ---- returns the default value if given null values
it("returns the default value if given null values") {
let ptr: UnsafeMutablePointer<UnsafePointer<CChar>?>? = nil
expect([String](pointer: ptr, count: 5, defaultValue: ["Test"]))
.to(equal(["Test"]))
}
}
}
}

Loading…
Cancel
Save