Defensive coding for C API conversation, threading & logging tweaks

• Reworked some of the C API conversions to try to prevent invalid cases
• Tweaked the threading around libSession networking callbacks to minimise Swift code blocking libSession threads
• More logging tweaks
pull/976/head
Morgan Pretty 2 months ago
parent dd5716e831
commit 733694d464

@ -36,7 +36,6 @@ extension ProjectState {
.contains("precondition(", caseSensitive: false), .contains("precondition(", caseSensitive: false),
.contains("preconditionFailure(", caseSensitive: false), .contains("preconditionFailure(", caseSensitive: false),
.contains("print(", caseSensitive: false), .contains("print(", caseSensitive: false),
.contains("NSLog(", caseSensitive: false),
.contains("SNLog(", caseSensitive: false), .contains("SNLog(", caseSensitive: false),
.contains("Log.setup(", caseSensitive: false), .contains("Log.setup(", caseSensitive: false),
.contains("Log.trace(", caseSensitive: false), .contains("Log.trace(", caseSensitive: false),
@ -80,8 +79,6 @@ extension ProjectState {
), ),
.contains("SQL(", caseSensitive: false), .contains("SQL(", caseSensitive: false),
.regex(".*static var databaseTableName: String"), .regex(".*static var databaseTableName: String"),
.regex("Logger\\..*\\("),
.regex("OWSLogger\\..*\\("),
.regex("case .* = "), .regex("case .* = "),
.regex("Error.*\\(") .regex("Error.*\\(")
] ]

@ -357,16 +357,14 @@ class ThreadSettingsViewModel: SessionTableViewModel, NavigationItemSource, Navi
case .community: case .community:
guard guard
let server: String = threadViewModel.openGroupServer, let urlString: String = LibSession.communityUrlFor(
let roomToken: String = threadViewModel.openGroupRoomToken, server: threadViewModel.openGroupServer,
let publicKey: String = threadViewModel.openGroupPublicKey roomToken: threadViewModel.openGroupRoomToken,
publicKey: threadViewModel.openGroupPublicKey
)
else { return } else { return }
UIPasteboard.general.string = LibSession.communityUrlFor( UIPasteboard.general.string = urlString
server: server,
roomToken: roomToken,
publicKey: publicKey
)
} }
self?.showToast( self?.showToast(
@ -765,17 +763,13 @@ class ThreadSettingsViewModel: SessionTableViewModel, NavigationItemSource, Navi
private func addUsersToOpenGoup(threadViewModel: SessionThreadViewModel, selectedUsers: Set<String>) { private func addUsersToOpenGoup(threadViewModel: SessionThreadViewModel, selectedUsers: Set<String>) {
guard guard
let name: String = threadViewModel.openGroupName, let name: String = threadViewModel.openGroupName,
let server: String = threadViewModel.openGroupServer, let communityUrl: String = LibSession.communityUrlFor(
let roomToken: String = threadViewModel.openGroupRoomToken, server: threadViewModel.openGroupServer,
let publicKey: String = threadViewModel.openGroupPublicKey roomToken: threadViewModel.openGroupRoomToken,
publicKey: threadViewModel.openGroupPublicKey
)
else { return } else { return }
let communityUrl: String = LibSession.communityUrlFor(
server: server,
roomToken: roomToken,
publicKey: publicKey
)
dependencies.storage.writeAsync { [dependencies] db in dependencies.storage.writeAsync { [dependencies] db in
let currentUserSessionId: String = getUserHexEncodedPublicKey(db, using: dependencies) let currentUserSessionId: String = getUserHexEncodedPublicKey(db, using: dependencies)
try selectedUsers.forEach { userId in try selectedUsers.forEach { userId in

@ -74,7 +74,6 @@ final class MainAppContext: AppContext {
AssertIsOnMainThread() AssertIsOnMainThread()
self.reportedApplicationState = .inactive self.reportedApplicationState = .inactive
OWSLogger.info("")
NotificationCenter.default.post( NotificationCenter.default.post(
name: .sessionWillEnterForeground, name: .sessionWillEnterForeground,
@ -149,10 +148,10 @@ final class MainAppContext: AppContext {
if blockingObjects.count > 1 { if blockingObjects.count > 1 {
logString = "\(logString) (and \(blockingObjects.count - 1) others)" logString = "\(logString) (and \(blockingObjects.count - 1) others)"
} }
OWSLogger.info(logString) Log.info(logString)
} }
else { else {
OWSLogger.info("Unblocking Sleep.") Log.info("Unblocking Sleep.")
} }
} }
UIApplication.shared.isIdleTimerDisabled = shouldBeBlocking UIApplication.shared.isIdleTimerDisabled = shouldBeBlocking

@ -188,7 +188,7 @@ public enum PushRegistrationError: Error {
.map { tokenData -> String in .map { tokenData -> String in
if self.isSusceptibleToFailedPushRegistration { if self.isSusceptibleToFailedPushRegistration {
// Sentinal in case this bug is fixed // Sentinal in case this bug is fixed
OWSLogger.debug("Device was unexpectedly able to complete push registration even though it was susceptible to failure.") Log.debug("Device was unexpectedly able to complete push registration even though it was susceptible to failure.")
} }
return tokenData.toHexString() return tokenData.toHexString()

@ -265,7 +265,7 @@ public class UserNotificationActionHandler: NSObject {
case .finished: break case .finished: break
case .failure(let error): case .failure(let error):
completionHandler() completionHandler()
OWSLogger.error("Failed to handle notification response: \(error)") Log.error("Failed to handle notification response: \(error)")
} }
}, },
receiveValue: { _ in completionHandler() } receiveValue: { _ in completionHandler() }
@ -281,14 +281,14 @@ public class UserNotificationActionHandler: NSObject {
switch response.actionIdentifier { switch response.actionIdentifier {
case UNNotificationDefaultActionIdentifier: case UNNotificationDefaultActionIdentifier:
OWSLogger.debug("Notification response: default action") Log.debug("Notification response: default action")
return actionHandler.showThread(userInfo: userInfo) return actionHandler.showThread(userInfo: userInfo)
.setFailureType(to: Error.self) .setFailureType(to: Error.self)
.eraseToAnyPublisher() .eraseToAnyPublisher()
case UNNotificationDismissActionIdentifier: case UNNotificationDismissActionIdentifier:
// TODO - mark as read? // TODO - mark as read?
OWSLogger.debug("Notification response: dismissed notification") Log.debug("Notification response: dismissed notification")
return Just(()) return Just(())
.setFailureType(to: Error.self) .setFailureType(to: Error.self)
.eraseToAnyPublisher() .eraseToAnyPublisher()

@ -162,6 +162,7 @@ public enum MessageSendJob: JobExecutor {
// Store the sentTimestamp from the message in case it fails due to a clockOutOfSync error // Store the sentTimestamp from the message in case it fails due to a clockOutOfSync error
let originalSentTimestamp: UInt64? = details.message.sentTimestamp let originalSentTimestamp: UInt64? = details.message.sentTimestamp
let startTime: CFTimeInterval = CACurrentMediaTime()
/// Perform the actual message sending - this will timeout if the entire process takes longer than `HTTP.defaultTimeout * 2` /// Perform the actual message sending - this will timeout if the entire process takes longer than `HTTP.defaultTimeout * 2`
/// which can occur if it needs to build a new onion path (which doesn't actually have any limits so can take forever in rare cases) /// which can occur if it needs to build a new onion path (which doesn't actually have any limits so can take forever in rare cases)
@ -193,7 +194,7 @@ public enum MessageSendJob: JobExecutor {
case .failure(let error): case .failure(let error):
switch error { switch error {
case MessageSenderError.sendJobTimeout: case MessageSenderError.sendJobTimeout:
SNLog("[MessageSendJob] Couldn't send message due to error: \(error) (paths: \(LibSession.pathsDescription)).") SNLog("[MessageSendJob] Failed after \(CACurrentMediaTime() - startTime)s: \(error).")
// In this case the `MessageSender` process gets cancelled so we need to // In this case the `MessageSender` process gets cancelled so we need to
// call `handleFailedMessageSend` to update the statuses correctly // call `handleFailedMessageSend` to update the statuses correctly

@ -283,9 +283,11 @@ internal extension LibSession {
// Update the name // Update the name
try targetContacts try targetContacts
.forEach { info in .forEach { info in
var sessionId: [CChar] = info.id.cArray.nullTerminated()
var contact: contacts_contact = contacts_contact() var contact: contacts_contact = contacts_contact()
guard contacts_get_or_construct(conf, &contact, &sessionId) else { guard
var sessionId: [CChar] = info.id.cString(using: .utf8),
contacts_get_or_construct(conf, &contact, &sessionId)
else {
/// It looks like there are some situations where this object might not get created correctly (and /// It looks like there are some situations where this object might not get created correctly (and
/// will throw due to the implicit unwrapping) as a result we put it in a guard and throw instead /// will throw due to the implicit unwrapping) as a result we put it in a guard and throw instead
SNLog("Unable to upsert contact to LibSession: \(LibSession.lastError(conf))") SNLog("Unable to upsert contact to LibSession: \(LibSession.lastError(conf))")
@ -382,10 +384,10 @@ internal extension LibSession {
// contacts are new/invalid, and if so, fetch any profile data we have for them // contacts are new/invalid, and if so, fetch any profile data we have for them
let newContactIds: [String] = targetContacts let newContactIds: [String] = targetContacts
.compactMap { contactData -> String? in .compactMap { contactData -> String? in
var cContactId: [CChar] = contactData.id.cArray.nullTerminated()
var contact: contacts_contact = contacts_contact() var contact: contacts_contact = contacts_contact()
guard guard
var cContactId: [CChar] = contactData.id.cString(using: .utf8),
contacts_get(conf, &contact, &cContactId), contacts_get(conf, &contact, &cContactId),
String(libSessionVal: contact.name, nullIfEmpty: true) != nil String(libSessionVal: contact.name, nullIfEmpty: true) != nil
else { return contactData.id } else { return contactData.id }
@ -557,7 +559,7 @@ public extension LibSession {
publicKey: getUserHexEncodedPublicKey(db) publicKey: getUserHexEncodedPublicKey(db)
) { conf in ) { conf in
contactIds.forEach { sessionId in contactIds.forEach { sessionId in
var cSessionId: [CChar] = sessionId.cArray.nullTerminated() guard var cSessionId: [CChar] = sessionId.cString(using: .utf8) else { return }
// Don't care if the contact doesn't exist // Don't care if the contact doesn't exist
contacts_erase(conf, &cSessionId) contacts_erase(conf, &cSessionId)

@ -132,7 +132,10 @@ internal extension LibSession {
} }
try validChanges.forEach { threadInfo in try validChanges.forEach { threadInfo in
var cThreadId: [CChar] = threadInfo.threadId.cArray.nullTerminated() guard var cThreadId: [CChar] = threadInfo.threadId.cString(using: .utf8) else {
SNLog("Unable to upsert contact volatile info to LibSession: \(LibSessionError.invalidCConversion)")
throw LibSessionError.invalidCConversion
}
switch threadInfo.variant { switch threadInfo.variant {
case .contact: case .contact:
@ -179,9 +182,9 @@ internal extension LibSession {
case .community: case .community:
guard guard
var cBaseUrl: [CChar] = threadInfo.openGroupUrlInfo?.server.cArray.nullTerminated(), var cBaseUrl: [CChar] = threadInfo.openGroupUrlInfo?.server.cString(using: .utf8),
var cRoomToken: [CChar] = threadInfo.openGroupUrlInfo?.roomToken.cArray.nullTerminated(), var cRoomToken: [CChar] = threadInfo.openGroupUrlInfo?.roomToken.cString(using: .utf8),
var cPubkey: [UInt8] = threadInfo.openGroupUrlInfo?.publicKey.bytes var cPubkey: [UInt8] = threadInfo.openGroupUrlInfo.map({ Array(Data(hex: $0.publicKey)) })
else { else {
SNLog("Unable to create community conversation when updating last read timestamp due to missing URL info") SNLog("Unable to create community conversation when updating last read timestamp due to missing URL info")
return return
@ -248,8 +251,8 @@ internal extension LibSession {
for: .convoInfoVolatile, for: .convoInfoVolatile,
publicKey: getUserHexEncodedPublicKey(db) publicKey: getUserHexEncodedPublicKey(db)
) { conf in ) { conf in
volatileContactIds.forEach { contactId in try volatileContactIds.forEach { contactId in
var cSessionId: [CChar] = contactId.cArray.nullTerminated() var cSessionId: [CChar] = try contactId.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
// Don't care if the data doesn't exist // Don't care if the data doesn't exist
convo_info_volatile_erase_1to1(conf, &cSessionId) convo_info_volatile_erase_1to1(conf, &cSessionId)
@ -263,8 +266,8 @@ internal extension LibSession {
for: .convoInfoVolatile, for: .convoInfoVolatile,
publicKey: getUserHexEncodedPublicKey(db) publicKey: getUserHexEncodedPublicKey(db)
) { conf in ) { conf in
volatileLegacyGroupIds.forEach { legacyGroupId in try volatileLegacyGroupIds.forEach { legacyGroupId in
var cLegacyGroupId: [CChar] = legacyGroupId.cArray.nullTerminated() var cLegacyGroupId: [CChar] = try legacyGroupId.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
// Don't care if the data doesn't exist // Don't care if the data doesn't exist
convo_info_volatile_erase_legacy_group(conf, &cLegacyGroupId) convo_info_volatile_erase_legacy_group(conf, &cLegacyGroupId)
@ -278,9 +281,9 @@ internal extension LibSession {
for: .convoInfoVolatile, for: .convoInfoVolatile,
publicKey: getUserHexEncodedPublicKey(db) publicKey: getUserHexEncodedPublicKey(db)
) { conf in ) { conf in
volatileCommunityInfo.forEach { urlInfo in try volatileCommunityInfo.forEach { urlInfo in
var cBaseUrl: [CChar] = urlInfo.server.cArray.nullTerminated() var cBaseUrl: [CChar] = try urlInfo.server.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
var cRoom: [CChar] = urlInfo.roomToken.cArray.nullTerminated() var cRoom: [CChar] = try urlInfo.roomToken.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
// Don't care if the data doesn't exist // Don't care if the data doesn't exist
convo_info_volatile_erase_community(conf, &cBaseUrl, &cRoom) convo_info_volatile_erase_community(conf, &cBaseUrl, &cRoom)
@ -332,34 +335,34 @@ public extension LibSession {
.map { conf in .map { conf in
switch threadVariant { switch threadVariant {
case .contact: case .contact:
var cThreadId: [CChar] = threadId.cArray.nullTerminated()
var oneToOne: convo_info_volatile_1to1 = convo_info_volatile_1to1() var oneToOne: convo_info_volatile_1to1 = convo_info_volatile_1to1()
guard convo_info_volatile_get_1to1(conf, &oneToOne, &cThreadId) else { guard
return false var cThreadId: [CChar] = threadId.cString(using: .utf8),
} convo_info_volatile_get_1to1(conf, &oneToOne, &cThreadId)
else { return false }
return (oneToOne.last_read >= timestampMs) return (oneToOne.last_read >= timestampMs)
case .legacyGroup: case .legacyGroup:
var cThreadId: [CChar] = threadId.cArray.nullTerminated()
var legacyGroup: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() var legacyGroup: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group()
guard convo_info_volatile_get_legacy_group(conf, &legacyGroup, &cThreadId) else { guard
return false var cThreadId: [CChar] = threadId.cString(using: .utf8),
} convo_info_volatile_get_legacy_group(conf, &legacyGroup, &cThreadId)
else { return false }
return (legacyGroup.last_read >= timestampMs) return (legacyGroup.last_read >= timestampMs)
case .community: case .community:
guard let openGroup: OpenGroup = openGroup else { return false } guard let openGroup: OpenGroup = openGroup else { return false }
var cBaseUrl: [CChar] = openGroup.server.cArray.nullTerminated()
var cRoomToken: [CChar] = openGroup.roomToken.cArray.nullTerminated()
var convoCommunity: convo_info_volatile_community = convo_info_volatile_community() var convoCommunity: convo_info_volatile_community = convo_info_volatile_community()
guard convo_info_volatile_get_community(conf, &convoCommunity, &cBaseUrl, &cRoomToken) else { guard
return false var cBaseUrl: [CChar] = openGroup.server.cString(using: .utf8),
} var cRoomToken: [CChar] = openGroup.roomToken.cString(using: .utf8),
convo_info_volatile_get_community(conf, &convoCommunity, &cBaseUrl, &cRoomToken)
else { return false }
return (convoCommunity.last_read >= timestampMs) return (convoCommunity.last_read >= timestampMs)

@ -396,7 +396,7 @@ public extension LibSession {
.config(for: configVariant, publicKey: userPublicKey) .config(for: configVariant, publicKey: userPublicKey)
.wrappedValue .wrappedValue
.map { conf in .map { conf in
var cThreadId: [CChar] = threadId.cArray.nullTerminated() guard var cThreadId: [CChar] = threadId.cString(using: .utf8) else { return false }
switch threadVariant { switch threadVariant {
case .contact: case .contact:
@ -422,10 +422,12 @@ public extension LibSession {
.read { db in try OpenGroupUrlInfo.fetchAll(db, ids: [threadId]) }? .read { db in try OpenGroupUrlInfo.fetchAll(db, ids: [threadId]) }?
.first .first
guard let urlInfo: OpenGroupUrlInfo = maybeUrlInfo else { return false } guard
let urlInfo: OpenGroupUrlInfo = maybeUrlInfo,
var cBaseUrl: [CChar] = urlInfo.server.cString(using: .utf8),
var cRoom: [CChar] = urlInfo.roomToken.cString(using: .utf8)
else { return false }
var cBaseUrl: [CChar] = urlInfo.server.cArray.nullTerminated()
var cRoom: [CChar] = urlInfo.roomToken.cArray.nullTerminated()
var community: ugroups_community_info = ugroups_community_info() var community: ugroups_community_info = ugroups_community_info()
/// Not handling the `hidden` behaviour for communities so just indicate the existence /// Not handling the `hidden` behaviour for communities so just indicate the existence

@ -405,7 +405,7 @@ internal extension LibSession {
try legacyGroups try legacyGroups
.forEach { legacyGroup in .forEach { legacyGroup in
var cGroupId: [CChar] = legacyGroup.id.cArray.nullTerminated() var cGroupId: [CChar] = try legacyGroup.id.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
guard let userGroup: UnsafeMutablePointer<ugroups_legacy_group_info> = user_groups_get_or_construct_legacy_group(conf, &cGroupId) else { guard let userGroup: UnsafeMutablePointer<ugroups_legacy_group_info> = user_groups_get_or_construct_legacy_group(conf, &cGroupId) else {
/// It looks like there are some situations where this object might not get created correctly (and /// It looks like there are some situations where this object might not get created correctly (and
/// will throw due to the implicit unwrapping) as a result we put it in a guard and throw instead /// will throw due to the implicit unwrapping) as a result we put it in a guard and throw instead
@ -460,13 +460,13 @@ internal extension LibSession {
let membersIdsToAdd: Set<String> = memberIds.subtracting(existingMemberIds) let membersIdsToAdd: Set<String> = memberIds.subtracting(existingMemberIds)
let membersIdsToRemove: Set<String> = existingMemberIds.subtracting(memberIds) let membersIdsToRemove: Set<String> = existingMemberIds.subtracting(memberIds)
membersIdsToAdd.forEach { memberId in try membersIdsToAdd.forEach { memberId in
var cProfileId: [CChar] = memberId.cArray.nullTerminated() var cProfileId: [CChar] = try memberId.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
ugroups_legacy_member_add(userGroup, &cProfileId, false) ugroups_legacy_member_add(userGroup, &cProfileId, false)
} }
membersIdsToRemove.forEach { memberId in try membersIdsToRemove.forEach { memberId in
var cProfileId: [CChar] = memberId.cArray.nullTerminated() var cProfileId: [CChar] = try memberId.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
ugroups_legacy_member_remove(userGroup, &cProfileId) ugroups_legacy_member_remove(userGroup, &cProfileId)
} }
} }
@ -480,13 +480,13 @@ internal extension LibSession {
let adminIdsToAdd: Set<String> = adminIds.subtracting(existingAdminIds) let adminIdsToAdd: Set<String> = adminIds.subtracting(existingAdminIds)
let adminIdsToRemove: Set<String> = existingAdminIds.subtracting(adminIds) let adminIdsToRemove: Set<String> = existingAdminIds.subtracting(adminIds)
adminIdsToAdd.forEach { adminId in try adminIdsToAdd.forEach { adminId in
var cProfileId: [CChar] = adminId.cArray.nullTerminated() var cProfileId: [CChar] = try adminId.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
ugroups_legacy_member_add(userGroup, &cProfileId, true) ugroups_legacy_member_add(userGroup, &cProfileId, true)
} }
adminIdsToRemove.forEach { adminId in try adminIdsToRemove.forEach { adminId in
var cProfileId: [CChar] = adminId.cArray.nullTerminated() var cProfileId: [CChar] = try adminId.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
ugroups_legacy_member_remove(userGroup, &cProfileId) ugroups_legacy_member_remove(userGroup, &cProfileId)
} }
} }
@ -512,9 +512,15 @@ internal extension LibSession {
try communities try communities
.forEach { community in .forEach { community in
var cBaseUrl: [CChar] = community.urlInfo.server.cArray.nullTerminated() guard
var cRoom: [CChar] = community.urlInfo.roomToken.cArray.nullTerminated() var cBaseUrl: [CChar] = community.urlInfo.server.cString(using: .utf8),
var cPubkey: [UInt8] = Data(hex: community.urlInfo.publicKey).cArray var cRoom: [CChar] = community.urlInfo.roomToken.cString(using: .utf8)
else {
SNLog("Unable to upsert community conversation to LibSession: \(LibSessionError.invalidCConversion)")
throw LibSessionError.invalidCConversion
}
var cPubkey: [UInt8] = Array(Data(hex: community.urlInfo.publicKey))
var userCommunity: ugroups_community_info = ugroups_community_info() var userCommunity: ugroups_community_info = ugroups_community_info()
guard user_groups_get_or_construct_community(conf, &userCommunity, &cBaseUrl, &cRoom, &cPubkey) else { guard user_groups_get_or_construct_community(conf, &userCommunity, &cBaseUrl, &cRoom, &cPubkey) else {
@ -569,8 +575,8 @@ public extension LibSession {
for: .userGroups, for: .userGroups,
publicKey: getUserHexEncodedPublicKey(db) publicKey: getUserHexEncodedPublicKey(db)
) { conf in ) { conf in
var cBaseUrl: [CChar] = server.cArray.nullTerminated() var cBaseUrl: [CChar] = try server.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
var cRoom: [CChar] = roomToken.cArray.nullTerminated() var cRoom: [CChar] = try roomToken.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
// Don't care if the community doesn't exist // Don't care if the community doesn't exist
user_groups_erase_community(conf, &cBaseUrl, &cRoom) user_groups_erase_community(conf, &cBaseUrl, &cRoom)
@ -610,7 +616,7 @@ public extension LibSession {
) { conf in ) { conf in
guard conf != nil else { throw LibSessionError.nilConfigObject } guard conf != nil else { throw LibSessionError.nilConfigObject }
var cGroupId: [CChar] = groupPublicKey.cArray.nullTerminated() var cGroupId: [CChar] = try groupPublicKey.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
let userGroup: UnsafeMutablePointer<ugroups_legacy_group_info>? = user_groups_get_legacy_group(conf, &cGroupId) let userGroup: UnsafeMutablePointer<ugroups_legacy_group_info>? = user_groups_get_legacy_group(conf, &cGroupId)
// Need to make sure the group doesn't already exist (otherwise we will end up overriding the // Need to make sure the group doesn't already exist (otherwise we will end up overriding the
@ -734,7 +740,7 @@ public extension LibSession {
publicKey: getUserHexEncodedPublicKey(db) publicKey: getUserHexEncodedPublicKey(db)
) { conf in ) { conf in
legacyGroupIds.forEach { threadId in legacyGroupIds.forEach { threadId in
var cGroupId: [CChar] = threadId.cArray.nullTerminated() guard var cGroupId: [CChar] = threadId.cString(using: .utf8) else { return }
// Don't care if the group doesn't exist // Don't care if the group doesn't exist
user_groups_erase_legacy_group(conf, &cGroupId) user_groups_erase_legacy_group(conf, &cGroupId)

@ -178,7 +178,7 @@ internal extension LibSession {
guard conf != nil else { throw LibSessionError.nilConfigObject } guard conf != nil else { throw LibSessionError.nilConfigObject }
// Update the name // Update the name
var updatedName: [CChar] = profile.name.cArray.nullTerminated() var updatedName: [CChar] = try profile.name.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
user_profile_set_name(conf, &updatedName) user_profile_set_name(conf, &updatedName)
// Either assign the updated profile pic, or sent a blank profile pic (to remove the current one) // Either assign the updated profile pic, or sent a blank profile pic (to remove the current one)

@ -336,10 +336,12 @@ public extension LibSession {
return LibSession return LibSession
.config(for: variant, publicKey: publicKey) .config(for: variant, publicKey: publicKey)
.mutate { conf in .mutate { conf in
guard conf != nil else { return nil } guard
conf != nil,
var cHash: [CChar] = serverHash.cString(using: .utf8)
else { return nil }
// Mark the config as pushed // Mark the config as pushed
var cHash: [CChar] = serverHash.cArray.nullTerminated()
config_confirm_pushed(conf, seqNo, &cHash) config_confirm_pushed(conf, seqNo, &cHash)
// Update the result to indicate whether the config needs to be dumped // Update the result to indicate whether the config needs to be dumped
@ -407,13 +409,28 @@ public extension LibSession {
.config(for: key, publicKey: publicKey) .config(for: key, publicKey: publicKey)
.mutate { conf in .mutate { conf in
// Merge the messages // Merge the messages
var mergeHashes: [UnsafePointer<CChar>?] = value var mergeHashes: [UnsafePointer<CChar>?] = (try? (value
.map { message in message.serverHash.cArray.nullTerminated() } .compactMap { message in message.serverHash.cString(using: .utf8) }
.unsafeCopy() .unsafeCopyCStringArray()))
var mergeData: [UnsafePointer<UInt8>?] = value .defaulting(to: [])
.map { message -> [UInt8] in message.data.bytes } var mergeData: [UnsafePointer<UInt8>?] = (try? (value
.unsafeCopy() .map { message -> [UInt8] in Array(message.data) }
var mergeSize: [Int] = value.map { $0.data.count } .unsafeCopyUInt8Array()))
.defaulting(to: [])
defer {
mergeHashes.forEach { $0?.deallocate() }
mergeData.forEach { $0?.deallocate() }
}
guard
conf != nil,
mergeHashes.count == value.count,
mergeData.count == value.count,
mergeHashes.allSatisfy({ $0 != nil }),
mergeData.allSatisfy({ $0 != nil })
else { return SNLog("[LibSession] Failed to correctly allocate merge data") }
var mergeSize: [size_t] = value.map { size_t($0.data.count) }
var mergedHashesPtr: UnsafeMutablePointer<config_string_list>? var mergedHashesPtr: UnsafeMutablePointer<config_string_list>?
try CExceptionHelper.performSafely { try CExceptionHelper.performSafely {
mergedHashesPtr = config_merge( mergedHashesPtr = config_merge(
@ -424,8 +441,6 @@ public extension LibSession {
value.count value.count
) )
} }
mergeHashes.forEach { $0?.deallocate() }
mergeData.forEach { $0?.deallocate() }
// Get the list of hashes from the config (to determine which were successful) // Get the list of hashes from the config (to determine which were successful)
let mergedHashes: [String] = mergedHashesPtr let mergedHashes: [String] = mergedHashesPtr
@ -537,12 +552,12 @@ fileprivate extension LibSession {
public extension LibSession { public extension LibSession {
static func parseCommunity(url: String) -> (room: String, server: String, publicKey: String)? { static func parseCommunity(url: String) -> (room: String, server: String, publicKey: String)? {
var cFullUrl: [CChar] = url.cArray.nullTerminated()
var cBaseUrl: [CChar] = [CChar](repeating: 0, count: COMMUNITY_BASE_URL_MAX_LENGTH) var cBaseUrl: [CChar] = [CChar](repeating: 0, count: COMMUNITY_BASE_URL_MAX_LENGTH)
var cRoom: [CChar] = [CChar](repeating: 0, count: COMMUNITY_ROOM_MAX_LENGTH) var cRoom: [CChar] = [CChar](repeating: 0, count: COMMUNITY_ROOM_MAX_LENGTH)
var cPubkey: [UInt8] = [UInt8](repeating: 0, count: OpenGroup.pubkeyByteLength) var cPubkey: [UInt8] = [UInt8](repeating: 0, count: OpenGroup.pubkeyByteLength)
guard guard
var cFullUrl: [CChar] = url.cString(using: .utf8),
community_parse_full_url(&cFullUrl, &cBaseUrl, &cRoom, &cPubkey) && community_parse_full_url(&cFullUrl, &cBaseUrl, &cRoom, &cPubkey) &&
!String(cString: cRoom).isEmpty && !String(cString: cRoom).isEmpty &&
!String(cString: cBaseUrl).isEmpty && !String(cString: cBaseUrl).isEmpty &&
@ -559,10 +574,14 @@ public extension LibSession {
return (room, baseUrl, pubkeyHex) return (room, baseUrl, pubkeyHex)
} }
static func communityUrlFor(server: String, roomToken: String, publicKey: String) -> String { static func communityUrlFor(server: String?, roomToken: String?, publicKey: String?) -> String? {
var cBaseUrl: [CChar] = server.cArray.nullTerminated() guard
var cRoom: [CChar] = roomToken.cArray.nullTerminated() var cBaseUrl: [CChar] = server?.cString(using: .utf8),
var cPubkey: [UInt8] = Data(hex: publicKey).cArray var cRoom: [CChar] = roomToken?.cString(using: .utf8),
let publicKey: String = publicKey
else { return nil }
var cPubkey: [UInt8] = Array(Data(hex: publicKey))
var cFullUrl: [CChar] = [CChar](repeating: 0, count: COMMUNITY_FULL_URL_MAX_LENGTH) var cFullUrl: [CChar] = [CChar](repeating: 0, count: COMMUNITY_FULL_URL_MAX_LENGTH)
community_make_full_url(&cBaseUrl, &cRoom, &cPubkey, &cFullUrl) community_make_full_url(&cBaseUrl, &cRoom, &cPubkey, &cFullUrl)

@ -44,7 +44,7 @@ public enum MessageSenderError: Error, CustomStringConvertible, Equatable {
case .noUsername: return "Missing username (MessageSenderError.noUsername)." case .noUsername: return "Missing username (MessageSenderError.noUsername)."
case .attachmentsNotUploaded: return "Attachments for this message have not been uploaded (MessageSenderError.attachmentsNotUploaded)." case .attachmentsNotUploaded: return "Attachments for this message have not been uploaded (MessageSenderError.attachmentsNotUploaded)."
case .blindingFailed: return "Couldn't blind the sender (MessageSenderError.blindingFailed)." case .blindingFailed: return "Couldn't blind the sender (MessageSenderError.blindingFailed)."
case .sendJobTimeout: return "Send job timeout (likely due to path building taking too long - MessageSenderError.sendJobTimeout)." case .sendJobTimeout: return "Send job timeout (MessageSenderError.sendJobTimeout)."
// Closed groups // Closed groups
case .noThread: return "Couldn't find a thread associated with the given group public key (MessageSenderError.noThread)." case .noThread: return "Couldn't find a thread associated with the given group public key (MessageSenderError.noThread)."

@ -275,7 +275,7 @@ extension OpenGroupAPI {
} }
} }
SNLog("Open group polling to \(server) failed due to error: \(error). Setting failure count to \(pollFailureCount).") SNLog("Open group polling to \(server) failed due to error: \(error). Setting failure count to \(pollFailureCount + 1).")
// Add a note to the logs that this happened // Add a note to the logs that this happened
if !prunedIds.isEmpty { if !prunedIds.isEmpty {

@ -213,7 +213,7 @@ public struct ProfileManager {
let filePath: String = ProfileManager.profileAvatarFilepath(filename: fileName) let filePath: String = ProfileManager.profileAvatarFilepath(filename: fileName)
var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(label: funcName) var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(label: funcName)
OWSLogger.verbose("downloading profile avatar: \(profile.id)") Log.trace("downloading profile avatar: \(profile.id)")
currentAvatarDownloads.mutate { $0.insert(profile.id) } currentAvatarDownloads.mutate { $0.insert(profile.id) }
let useOldServer: Bool = (profileUrlStringAtStart.contains(FileServerAPI.oldServer)) let useOldServer: Bool = (profileUrlStringAtStart.contains(FileServerAPI.oldServer))
@ -240,12 +240,12 @@ public struct ProfileManager {
!latestProfileKey.isEmpty, !latestProfileKey.isEmpty,
latestProfileKey == profileKeyAtStart latestProfileKey == profileKeyAtStart
else { else {
OWSLogger.warn("Ignoring avatar download for obsolete user profile.") Log.warn("Ignoring avatar download for obsolete user profile.")
return return
} }
guard profileUrlStringAtStart == latestProfile.profilePictureUrl else { guard profileUrlStringAtStart == latestProfile.profilePictureUrl else {
OWSLogger.warn("Avatar url has changed during download.") Log.warn("Avatar url has changed during download.")
if latestProfile.profilePictureUrl?.isEmpty == false { if latestProfile.profilePictureUrl?.isEmpty == false {
self.downloadAvatar(for: latestProfile) self.downloadAvatar(for: latestProfile)
@ -254,14 +254,14 @@ public struct ProfileManager {
} }
guard let decryptedData: Data = decryptData(data: data, key: profileKeyAtStart) else { guard let decryptedData: Data = decryptData(data: data, key: profileKeyAtStart) else {
OWSLogger.warn("Avatar data for \(profile.id) could not be decrypted.") Log.warn("Avatar data for \(profile.id) could not be decrypted.")
return return
} }
try? decryptedData.write(to: URL(fileURLWithPath: filePath), options: [.atomic]) try? decryptedData.write(to: URL(fileURLWithPath: filePath), options: [.atomic])
guard UIImage(contentsOfFile: filePath) != nil else { guard UIImage(contentsOfFile: filePath) != nil else {
OWSLogger.warn("Avatar image for \(profile.id) could not be loaded.") Log.warn("Avatar image for \(profile.id) could not be loaded.")
return return
} }
@ -317,7 +317,7 @@ public struct ProfileManager {
profileAvatarCache.mutate { $0[fileName] = nil } profileAvatarCache.mutate { $0[fileName] = nil }
} }
OWSLogger.verbose(existingProfileUrl != nil ? Log.debug(existingProfileUrl != nil ?
"Updating local profile on service with cleared avatar." : "Updating local profile on service with cleared avatar." :
"Updating local profile on service with no avatar." "Updating local profile on service with no avatar."
) )
@ -441,7 +441,7 @@ public struct ProfileManager {
// * Encrypt it // * Encrypt it
// * Upload it to asset service // * Upload it to asset service
// * Send asset service info to Signal Service // * Send asset service info to Signal Service
OWSLogger.verbose("Updating local profile on service with new avatar.") Log.debug("Updating local profile on service with new avatar.")
let fileName: String = UUID().uuidString.appendingFileExtension(fileExtension) let fileName: String = UUID().uuidString.appendingFileExtension(fileExtension)
let filePath: String = ProfileManager.profileAvatarFilepath(filename: fileName) let filePath: String = ProfileManager.profileAvatarFilepath(filename: fileName)

@ -258,7 +258,6 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
}, },
migrationsCompletion: { [weak self] result, needsConfigSync in migrationsCompletion: { [weak self] result, needsConfigSync in
switch result { switch result {
// Only 'NSLog' works in the extension - viewable via Console.app
case .failure(let error): case .failure(let error):
Log.error("Failed to complete migrations: \(error).") Log.error("Failed to complete migrations: \(error).")
self?.completeSilenty() self?.completeSilenty()

@ -26,10 +26,6 @@ final class SAEScreenLockViewController: ScreenLockViewController {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
deinit {
OWSLogger.verbose("Dealloc: \(type(of: self))")
}
// MARK: - UI // MARK: - UI
private lazy var titleLabel: UILabel = { private lazy var titleLabel: UILabel = {
@ -107,21 +103,21 @@ final class SAEScreenLockViewController: ScreenLockViewController {
// If we're already showing the auth UI; abort. // If we're already showing the auth UI; abort.
if self.isShowingAuthUI { return } if self.isShowingAuthUI { return }
OWSLogger.info("try to unlock screen lock") Log.info("try to unlock screen lock")
isShowingAuthUI = true isShowingAuthUI = true
ScreenLock.shared.tryToUnlockScreenLock( ScreenLock.shared.tryToUnlockScreenLock(
success: { [weak self] in success: { [weak self] in
AssertIsOnMainThread() AssertIsOnMainThread()
OWSLogger.info("unlock screen lock succeeded.") Log.info("unlock screen lock succeeded.")
self?.isShowingAuthUI = false self?.isShowingAuthUI = false
self?.shareViewDelegate?.shareViewWasUnlocked() self?.shareViewDelegate?.shareViewWasUnlocked()
}, },
failure: { [weak self] error in failure: { [weak self] error in
AssertIsOnMainThread() AssertIsOnMainThread()
OWSLogger.info("unlock screen lock failed.") Log.info("unlock screen lock failed.")
self?.isShowingAuthUI = false self?.isShowingAuthUI = false
self?.ensureUI() self?.ensureUI()
@ -129,7 +125,7 @@ final class SAEScreenLockViewController: ScreenLockViewController {
}, },
unexpectedFailure: { [weak self] error in unexpectedFailure: { [weak self] error in
AssertIsOnMainThread() AssertIsOnMainThread()
OWSLogger.info("unlock screen lock unexpectedly failed.") Log.info("unlock screen lock unexpectedly failed.")
self?.isShowingAuthUI = false self?.isShowingAuthUI = false
@ -142,7 +138,7 @@ final class SAEScreenLockViewController: ScreenLockViewController {
}, },
cancel: { [weak self] in cancel: { [weak self] in
AssertIsOnMainThread() AssertIsOnMainThread()
OWSLogger.info("unlock screen lock cancelled.") Log.info("unlock screen lock cancelled.")
self?.isShowingAuthUI = false self?.isShowingAuthUI = false
self?.ensureUI() self?.ensureUI()
@ -174,7 +170,7 @@ final class SAEScreenLockViewController: ScreenLockViewController {
func unlockButtonWasTapped() { func unlockButtonWasTapped() {
AssertIsOnMainThread() AssertIsOnMainThread()
OWSLogger.info("unlockButtonWasTapped") Log.info("unlockButtonWasTapped")
self.tryToPresentAuthUIToUnlockScreenLock() self.tryToPresentAuthUIToUnlockScreenLock()
} }
@ -182,7 +178,7 @@ final class SAEScreenLockViewController: ScreenLockViewController {
// MARK: - Transitions // MARK: - Transitions
@objc private func dismissPressed() { @objc private func dismissPressed() {
OWSLogger.debug("unlock screen lock cancelled.") Log.debug("unlock screen lock cancelled.")
self.cancelShareExperience() self.cancelShareExperience()
} }

@ -123,7 +123,7 @@ final class ShareAppExtensionContext: AppContext {
} }
func setStatusBarHidden(_ isHidden: Bool, animated isAnimated: Bool) { func setStatusBarHidden(_ isHidden: Bool, animated isAnimated: Bool) {
OWSLogger.info("Ignoring request to show/hide status bar since we're in an app extension") Log.info("Ignoring request to show/hide status bar since we're in an app extension")
} }
func setNetworkActivityIndicatorVisible(_ value: Bool) { func setNetworkActivityIndicatorVisible(_ value: Bool) {

@ -107,11 +107,21 @@ public extension LibSession {
guard guard
swarmSize > 0, swarmSize > 0,
let cSwarm: UnsafeMutablePointer<network_service_node> = swarmPtr let cSwarm: UnsafeMutablePointer<network_service_node> = swarmPtr
else { return resolver(Result.failure(SnodeAPIError.unableToRetrieveSwarm)) } else {
// Dispatch async so we don't hold up the libSession thread (which can block other requests)
DispatchQueue.global(qos: .default).async {
resolver(Result.failure(SnodeAPIError.unableToRetrieveSwarm))
}
return
}
var nodes: Set<Snode> = [] var nodes: Set<Snode> = []
(0..<swarmSize).forEach { index in nodes.insert(Snode(cSwarm[index])) } (0..<swarmSize).forEach { index in nodes.insert(Snode(cSwarm[index])) }
resolver(Result.success(nodes))
// Dispatch async so we don't hold up the libSession thread (which can block other requests)
DispatchQueue.global(qos: .default).async {
resolver(Result.success(nodes))
}
} }
let cWrapperPtr: UnsafeMutableRawPointer = Unmanaged.passRetained(callbackWrapper).toOpaque() let cWrapperPtr: UnsafeMutableRawPointer = Unmanaged.passRetained(callbackWrapper).toOpaque()
@ -134,11 +144,21 @@ public extension LibSession {
guard guard
nodesSize >= count, nodesSize >= count,
let cSwarm: UnsafeMutablePointer<network_service_node> = nodesPtr let cSwarm: UnsafeMutablePointer<network_service_node> = nodesPtr
else { return resolver(Result.failure(SnodeAPIError.unableToRetrieveSwarm)) } else {
// Dispatch async so we don't hold up the libSession thread (which can block other requests)
DispatchQueue.global(qos: .default).async {
resolver(Result.failure(SnodeAPIError.unableToRetrieveSwarm))
}
return
}
var nodes: Set<Snode> = [] var nodes: Set<Snode> = []
(0..<nodesSize).forEach { index in nodes.insert(Snode(cSwarm[index])) } (0..<nodesSize).forEach { index in nodes.insert(Snode(cSwarm[index])) }
resolver(Result.success(nodes))
// Dispatch async so we don't hold up the libSession thread (which can block other requests)
DispatchQueue.global(qos: .default).async {
resolver(Result.success(nodes))
}
} }
let cWrapperPtr: UnsafeMutableRawPointer = Unmanaged.passRetained(callbackWrapper).toOpaque() let cWrapperPtr: UnsafeMutableRawPointer = Unmanaged.passRetained(callbackWrapper).toOpaque()
@ -179,9 +199,15 @@ public extension LibSession {
return Deferred { return Deferred {
Future<(ResponseInfoType, Data?), Error> { resolver in Future<(ResponseInfoType, Data?), Error> { resolver in
let callbackWrapper: CWrapper<NetworkCallback> = CWrapper { success, timeout, statusCode, data in let callbackWrapper: CWrapper<NetworkCallback> = CWrapper { success, timeout, statusCode, data in
switch processError(success, timeout, statusCode, data, using: dependencies) { let maybeError: Error? = processError(success, timeout, statusCode, data, using: dependencies)
case .some(let error): resolver(Result.failure(error))
case .none: resolver(Result.success((Network.ResponseInfo(code: Int(statusCode), headers: [:]), data))) // Dispatch async so we don't hold up the libSession thread (which can block other requests)
DispatchQueue.global(qos: .default).async {
switch maybeError {
case .some(let error): resolver(Result.failure(error))
case .none:
resolver(Result.success((Network.ResponseInfo(code: Int(statusCode), headers: [:]), data)))
}
} }
} }
let cWrapperPtr: UnsafeMutableRawPointer = Unmanaged.passRetained(callbackWrapper).toOpaque() let cWrapperPtr: UnsafeMutableRawPointer = Unmanaged.passRetained(callbackWrapper).toOpaque()
@ -191,7 +217,7 @@ public extension LibSession {
case .snode(let snode): case .snode(let snode):
let cSwarmPublicKey: UnsafePointer<CChar>? = swarmPublicKey.map { let cSwarmPublicKey: UnsafePointer<CChar>? = swarmPublicKey.map {
// Quick way to drop '05' prefix if present // Quick way to drop '05' prefix if present
$0.suffix(64).cArray.nullTerminated().unsafeCopy() $0.suffix(64).cString(using: .utf8)?.unsafeCopy()
} }
callbackWrapper.addUnsafePointerToCleanup(cSwarmPublicKey) callbackWrapper.addUnsafePointerToCleanup(cSwarmPublicKey)
@ -211,30 +237,46 @@ public extension LibSession {
) )
case .server(let method, let scheme, let host, let endpoint, let port, let headers, let x25519PublicKey): case .server(let method, let scheme, let host, let endpoint, let port, let headers, let x25519PublicKey):
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: [])
guard
cHeaderKeysContent.count == cHeaderValuesContent.count,
cHeaderKeysContent.allSatisfy({ $0 != nil }),
cHeaderValuesContent.allSatisfy({ $0 != nil })
else {
cHeaderKeysContent.forEach { $0?.deallocate() }
cHeaderValuesContent.forEach { $0?.deallocate() }
cWrapperPtr.deallocate()
return resolver(Result.failure(LibSessionError.invalidCConversion))
}
// Convert the other types
let targetScheme: String = (scheme ?? "https") let targetScheme: String = (scheme ?? "https")
let cMethod: UnsafePointer<CChar>? = (method ?? "GET").cArray let cMethod: UnsafePointer<CChar>? = (method ?? "GET")
.nullTerminated() .cString(using: .utf8)?
.unsafeCopy() .unsafeCopy()
let cTargetScheme: UnsafePointer<CChar>? = targetScheme.cArray let cTargetScheme: UnsafePointer<CChar>? = targetScheme
.nullTerminated() .cString(using: .utf8)?
.unsafeCopy() .unsafeCopy()
let cHost: UnsafePointer<CChar>? = host.cArray let cHost: UnsafePointer<CChar>? = host
.nullTerminated() .cString(using: .utf8)?
.unsafeCopy() .unsafeCopy()
let cEndpoint: UnsafePointer<CChar>? = endpoint.cArray let cEndpoint: UnsafePointer<CChar>? = endpoint
.nullTerminated() .cString(using: .utf8)?
.unsafeCopy() .unsafeCopy()
let cX25519Pubkey: UnsafePointer<CChar>? = x25519PublicKey let cX25519Pubkey: UnsafePointer<CChar>? = x25519PublicKey
.suffix(64) // Quick way to drop '05' prefix if present .suffix(64) // Quick way to drop '05' prefix if present
.cArray .cString(using: .utf8)?
.nullTerminated()
.unsafeCopy()
let headerInfo: [(key: String, value: String)]? = headers?.map { ($0.key, $0.value) }
let cHeaderKeysContent: [UnsafePointer<CChar>?] = (headerInfo ?? [])
.map { $0.key.cArray.nullTerminated() }
.unsafeCopy()
let cHeaderValuesContent: [UnsafePointer<CChar>?] = (headerInfo ?? [])
.map { $0.value.cArray.nullTerminated() }
.unsafeCopy() .unsafeCopy()
let cHeaderKeys: UnsafeMutablePointer<UnsafePointer<CChar>?>? = cHeaderKeysContent let cHeaderKeys: UnsafeMutablePointer<UnsafePointer<CChar>?>? = cHeaderKeysContent
.unsafeCopy() .unsafeCopy()
@ -304,10 +346,14 @@ public extension LibSession {
// Otherwise create a new network // Otherwise create a new network
var error: [CChar] = [CChar](repeating: 0, count: 256) var error: [CChar] = [CChar](repeating: 0, count: 256)
var network: UnsafeMutablePointer<network_object>? var network: UnsafeMutablePointer<network_object>?
let cCachePath: [CChar] = snodeCachePath.cArray.nullTerminated()
guard let cCachePath: [CChar] = snodeCachePath.cString(using: .utf8) else {
Log.error("[LibQuic] Unable to create network object: \(LibSessionError.invalidCConversion)")
return nil
}
guard network_init(&network, cCachePath, Features.useTestnet, true, &error) else { guard network_init(&network, cCachePath, Features.useTestnet, true, &error) else {
SNLog("[LibQuic Error] Unable to create network object: \(String(cString: error))") Log.error("[LibQuic] Unable to create network object: \(String(cString: error))")
return nil return nil
} }
@ -336,12 +382,15 @@ public extension LibSession {
private static func updateNetworkStatus(cStatus: CONNECTION_STATUS) { private static func updateNetworkStatus(cStatus: CONNECTION_STATUS) {
let status: NetworkStatus = NetworkStatus(status: cStatus) let status: NetworkStatus = NetworkStatus(status: cStatus)
SNLog("Network status changed to: \(status)") // Dispatch async so we don't hold up the libSession thread that triggered the update
lastNetworkStatus.mutate { lastNetworkStatus in DispatchQueue.global(qos: .default).async {
lastNetworkStatus = status Log.info("Network status changed to: \(status)")
lastNetworkStatus.mutate { lastNetworkStatus in
networkStatusCallbacks.wrappedValue.forEach { _, callback in lastNetworkStatus = status
callback(status)
networkStatusCallbacks.wrappedValue.forEach { _, callback in
callback(status)
}
} }
} }
} }
@ -374,11 +423,14 @@ public extension LibSession {
// Need to free the cPathsPtr as we are the owner // Need to free the cPathsPtr as we are the owner
cPathsPtr?.deallocate() cPathsPtr?.deallocate()
lastPaths.mutate { lastPaths in // Dispatch async so we don't hold up the libSession thread that triggered the update
lastPaths = paths DispatchQueue.global(qos: .default).async {
lastPaths.mutate { lastPaths in
pathsChangedCallbacks.wrappedValue.forEach { id, callback in lastPaths = paths
callback(paths, id)
pathsChangedCallbacks.wrappedValue.forEach { id, callback in
callback(paths, id)
}
} }
} }
} }
@ -401,14 +453,14 @@ public extension LibSession {
case (400, .some(let responseString)): return NetworkError.badRequest(error: responseString, rawData: data) case (400, .some(let responseString)): return NetworkError.badRequest(error: responseString, rawData: data)
case (401, _): case (401, _):
SNLog("Unauthorised (Failed to verify the signature).") Log.warn("Unauthorised (Failed to verify the signature).")
return NetworkError.unauthorised return NetworkError.unauthorised
case (404, _): return NetworkError.notFound case (404, _): return NetworkError.notFound
/// A snode will return a `406` but onion requests v4 seems to return `425` so handle both /// A snode will return a `406` but onion requests v4 seems to return `425` so handle both
case (406, _), (425, _): case (406, _), (425, _):
SNLog("The user's clock is out of sync with the service node network.") Log.warn("The user's clock is out of sync with the service node network.")
return SnodeAPIError.clockOutOfSync return SnodeAPIError.clockOutOfSync
case (421, _): return SnodeAPIError.unassociatedPubkey case (421, _): return SnodeAPIError.unassociatedPubkey

@ -34,14 +34,33 @@ public extension Collection {
} }
} }
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)
}
}
}
public extension Collection where Element == [CChar] { 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 /// 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 /// 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. /// need to call `deallocate()` on each child.
func unsafeCopy() -> [UnsafePointer<CChar>?] { func unsafeCopyCStringArray() throws -> [UnsafePointer<CChar>?] {
return self.map { value in return try self.map { value in
let copy = UnsafeMutableBufferPointer<CChar>.allocate(capacity: value.count) let copy = UnsafeMutableBufferPointer<CChar>.allocate(capacity: value.underestimatedCount)
_ = copy.initialize(from: value) 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 UnsafePointer(copy.baseAddress)
} }
} }
@ -51,10 +70,12 @@ 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 /// 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 /// 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. /// need to call `deallocate()` on each child.
func unsafeCopy() -> [UnsafePointer<UInt8>?] { func unsafeCopyUInt8Array() throws -> [UnsafePointer<UInt8>?] {
return self.map { value in return try self.map { value in
let copy = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: value.count) let copy = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: value.underestimatedCount)
_ = copy.initialize(from: value) 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) return UnsafePointer(copy.baseAddress)
} }
} }

@ -11,6 +11,7 @@ public enum LibSessionError: LocalizedError {
case userDoesNotExist case userDoesNotExist
case getOrConstructFailedUnexpectedly case getOrConstructFailedUnexpectedly
case processingLoopLimitReached case processingLoopLimitReached
case invalidCConversion
case libSessionError(String) case libSessionError(String)
case unknown case unknown
@ -32,6 +33,7 @@ public enum LibSessionError: LocalizedError {
case .userDoesNotExist: return "User does not exist." case .userDoesNotExist: return "User does not exist."
case .getOrConstructFailedUnexpectedly: return "'getOrConstruct' failed unexpectedly." case .getOrConstructFailedUnexpectedly: return "'getOrConstruct' failed unexpectedly."
case .processingLoopLimitReached: return "Processing loop limit reached." case .processingLoopLimitReached: return "Processing loop limit reached."
case .invalidCConversion: return "Invalid conversation to C type."
case .libSessionError(let error): return "\(error)\(error.hasSuffix(".") ? "" : ".")" case .libSessionError(let error): return "\(error)\(error.hasSuffix(".") ? "" : ".")"
case .unknown: return "An unknown error occurred." case .unknown: return "An unknown error occurred."

@ -25,7 +25,7 @@ public extension Network {
self.requests = requests.map { Child(request: $0) } self.requests = requests.map { Child(request: $0) }
if requests.count > BatchRequest.childRequestLimit { if requests.count > BatchRequest.childRequestLimit {
SNLog("[BatchRequest] Constructed request with \(requests.count) subrequests when the limit is \(BatchRequest.childRequestLimit)") Log.warn("[BatchRequest] Constructed request with \(requests.count) subrequests when the limit is \(BatchRequest.childRequestLimit)")
} }
} }

Loading…
Cancel
Save