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 1 month ago
parent dd5716e831
commit 733694d464

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

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

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

@ -188,7 +188,7 @@ public enum PushRegistrationError: Error {
.map { tokenData -> String in
if self.isSusceptibleToFailedPushRegistration {
// 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()

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

@ -162,6 +162,7 @@ public enum MessageSendJob: JobExecutor {
// Store the sentTimestamp from the message in case it fails due to a clockOutOfSync error
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`
/// 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):
switch error {
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
// call `handleFailedMessageSend` to update the statuses correctly

@ -283,9 +283,11 @@ internal extension LibSession {
// Update the name
try targetContacts
.forEach { info in
var sessionId: [CChar] = info.id.cArray.nullTerminated()
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
/// 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))")
@ -382,10 +384,10 @@ internal extension LibSession {
// contacts are new/invalid, and if so, fetch any profile data we have for them
let newContactIds: [String] = targetContacts
.compactMap { contactData -> String? in
var cContactId: [CChar] = contactData.id.cArray.nullTerminated()
var contact: contacts_contact = contacts_contact()
guard
var cContactId: [CChar] = contactData.id.cString(using: .utf8),
contacts_get(conf, &contact, &cContactId),
String(libSessionVal: contact.name, nullIfEmpty: true) != nil
else { return contactData.id }
@ -557,7 +559,7 @@ public extension LibSession {
publicKey: getUserHexEncodedPublicKey(db)
) { conf 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
contacts_erase(conf, &cSessionId)

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

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

@ -405,7 +405,7 @@ internal extension LibSession {
try legacyGroups
.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 {
/// 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
@ -460,13 +460,13 @@ internal extension LibSession {
let membersIdsToAdd: Set<String> = memberIds.subtracting(existingMemberIds)
let membersIdsToRemove: Set<String> = existingMemberIds.subtracting(memberIds)
membersIdsToAdd.forEach { memberId in
var cProfileId: [CChar] = memberId.cArray.nullTerminated()
try membersIdsToAdd.forEach { memberId in
var cProfileId: [CChar] = try memberId.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
ugroups_legacy_member_add(userGroup, &cProfileId, false)
}
membersIdsToRemove.forEach { memberId in
var cProfileId: [CChar] = memberId.cArray.nullTerminated()
try membersIdsToRemove.forEach { memberId in
var cProfileId: [CChar] = try memberId.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
ugroups_legacy_member_remove(userGroup, &cProfileId)
}
}
@ -480,13 +480,13 @@ internal extension LibSession {
let adminIdsToAdd: Set<String> = adminIds.subtracting(existingAdminIds)
let adminIdsToRemove: Set<String> = existingAdminIds.subtracting(adminIds)
adminIdsToAdd.forEach { adminId in
var cProfileId: [CChar] = adminId.cArray.nullTerminated()
try adminIdsToAdd.forEach { adminId in
var cProfileId: [CChar] = try adminId.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
ugroups_legacy_member_add(userGroup, &cProfileId, true)
}
adminIdsToRemove.forEach { adminId in
var cProfileId: [CChar] = adminId.cArray.nullTerminated()
try adminIdsToRemove.forEach { adminId in
var cProfileId: [CChar] = try adminId.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
ugroups_legacy_member_remove(userGroup, &cProfileId)
}
}
@ -512,9 +512,15 @@ internal extension LibSession {
try communities
.forEach { community in
var cBaseUrl: [CChar] = community.urlInfo.server.cArray.nullTerminated()
var cRoom: [CChar] = community.urlInfo.roomToken.cArray.nullTerminated()
var cPubkey: [UInt8] = Data(hex: community.urlInfo.publicKey).cArray
guard
var cBaseUrl: [CChar] = community.urlInfo.server.cString(using: .utf8),
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()
guard user_groups_get_or_construct_community(conf, &userCommunity, &cBaseUrl, &cRoom, &cPubkey) else {
@ -569,8 +575,8 @@ public extension LibSession {
for: .userGroups,
publicKey: getUserHexEncodedPublicKey(db)
) { conf in
var cBaseUrl: [CChar] = server.cArray.nullTerminated()
var cRoom: [CChar] = roomToken.cArray.nullTerminated()
var cBaseUrl: [CChar] = try server.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
var cRoom: [CChar] = try roomToken.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
// Don't care if the community doesn't exist
user_groups_erase_community(conf, &cBaseUrl, &cRoom)
@ -610,7 +616,7 @@ public extension LibSession {
) { conf in
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)
// 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)
) { conf 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
user_groups_erase_legacy_group(conf, &cGroupId)

@ -178,7 +178,7 @@ internal extension LibSession {
guard conf != nil else { throw LibSessionError.nilConfigObject }
// 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)
// 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
.config(for: variant, publicKey: publicKey)
.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
var cHash: [CChar] = serverHash.cArray.nullTerminated()
config_confirm_pushed(conf, seqNo, &cHash)
// Update the result to indicate whether the config needs to be dumped
@ -407,13 +409,28 @@ public extension LibSession {
.config(for: key, publicKey: publicKey)
.mutate { conf in
// Merge the messages
var mergeHashes: [UnsafePointer<CChar>?] = value
.map { message in message.serverHash.cArray.nullTerminated() }
.unsafeCopy()
var mergeData: [UnsafePointer<UInt8>?] = value
.map { message -> [UInt8] in message.data.bytes }
.unsafeCopy()
var mergeSize: [Int] = value.map { $0.data.count }
var mergeHashes: [UnsafePointer<CChar>?] = (try? (value
.compactMap { message in message.serverHash.cString(using: .utf8) }
.unsafeCopyCStringArray()))
.defaulting(to: [])
var mergeData: [UnsafePointer<UInt8>?] = (try? (value
.map { message -> [UInt8] in Array(message.data) }
.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>?
try CExceptionHelper.performSafely {
mergedHashesPtr = config_merge(
@ -424,8 +441,6 @@ public extension LibSession {
value.count
)
}
mergeHashes.forEach { $0?.deallocate() }
mergeData.forEach { $0?.deallocate() }
// Get the list of hashes from the config (to determine which were successful)
let mergedHashes: [String] = mergedHashesPtr
@ -537,12 +552,12 @@ fileprivate extension LibSession {
public extension LibSession {
static func parseCommunity(url: String) -> (room: String, server: String, publicKey: String)? {
var cFullUrl: [CChar] = url.cArray.nullTerminated()
var cBaseUrl: [CChar] = [CChar](repeating: 0, count: COMMUNITY_BASE_URL_MAX_LENGTH)
var cRoom: [CChar] = [CChar](repeating: 0, count: COMMUNITY_ROOM_MAX_LENGTH)
var cPubkey: [UInt8] = [UInt8](repeating: 0, count: OpenGroup.pubkeyByteLength)
guard
var cFullUrl: [CChar] = url.cString(using: .utf8),
community_parse_full_url(&cFullUrl, &cBaseUrl, &cRoom, &cPubkey) &&
!String(cString: cRoom).isEmpty &&
!String(cString: cBaseUrl).isEmpty &&
@ -559,10 +574,14 @@ public extension LibSession {
return (room, baseUrl, pubkeyHex)
}
static func communityUrlFor(server: String, roomToken: String, publicKey: String) -> String {
var cBaseUrl: [CChar] = server.cArray.nullTerminated()
var cRoom: [CChar] = roomToken.cArray.nullTerminated()
var cPubkey: [UInt8] = Data(hex: publicKey).cArray
static func communityUrlFor(server: String?, roomToken: String?, publicKey: String?) -> String? {
guard
var cBaseUrl: [CChar] = server?.cString(using: .utf8),
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)
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 .attachmentsNotUploaded: return "Attachments for this message have not been uploaded (MessageSenderError.attachmentsNotUploaded)."
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
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
if !prunedIds.isEmpty {

@ -213,7 +213,7 @@ public struct ProfileManager {
let filePath: String = ProfileManager.profileAvatarFilepath(filename: fileName)
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) }
let useOldServer: Bool = (profileUrlStringAtStart.contains(FileServerAPI.oldServer))
@ -240,12 +240,12 @@ public struct ProfileManager {
!latestProfileKey.isEmpty,
latestProfileKey == profileKeyAtStart
else {
OWSLogger.warn("Ignoring avatar download for obsolete user profile.")
Log.warn("Ignoring avatar download for obsolete user profile.")
return
}
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 {
self.downloadAvatar(for: latestProfile)
@ -254,14 +254,14 @@ public struct ProfileManager {
}
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
}
try? decryptedData.write(to: URL(fileURLWithPath: filePath), options: [.atomic])
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
}
@ -317,7 +317,7 @@ public struct ProfileManager {
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 no avatar."
)
@ -441,7 +441,7 @@ public struct ProfileManager {
// * Encrypt it
// * Upload it to asset 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 filePath: String = ProfileManager.profileAvatarFilepath(filename: fileName)

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

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

@ -123,7 +123,7 @@ final class ShareAppExtensionContext: AppContext {
}
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) {

@ -107,11 +107,21 @@ public extension LibSession {
guard
swarmSize > 0,
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> = []
(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()
@ -134,11 +144,21 @@ public extension LibSession {
guard
nodesSize >= count,
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> = []
(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()
@ -179,9 +199,15 @@ public extension LibSession {
return Deferred {
Future<(ResponseInfoType, Data?), Error> { resolver in
let callbackWrapper: CWrapper<NetworkCallback> = CWrapper { success, timeout, statusCode, data in
switch 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)))
let maybeError: Error? = processError(success, timeout, statusCode, data, using: dependencies)
// 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()
@ -191,7 +217,7 @@ public extension LibSession {
case .snode(let snode):
let cSwarmPublicKey: UnsafePointer<CChar>? = swarmPublicKey.map {
// Quick way to drop '05' prefix if present
$0.suffix(64).cArray.nullTerminated().unsafeCopy()
$0.suffix(64).cString(using: .utf8)?.unsafeCopy()
}
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):
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 cMethod: UnsafePointer<CChar>? = (method ?? "GET").cArray
.nullTerminated()
let cMethod: UnsafePointer<CChar>? = (method ?? "GET")
.cString(using: .utf8)?
.unsafeCopy()
let cTargetScheme: UnsafePointer<CChar>? = targetScheme.cArray
.nullTerminated()
let cTargetScheme: UnsafePointer<CChar>? = targetScheme
.cString(using: .utf8)?
.unsafeCopy()
let cHost: UnsafePointer<CChar>? = host.cArray
.nullTerminated()
let cHost: UnsafePointer<CChar>? = host
.cString(using: .utf8)?
.unsafeCopy()
let cEndpoint: UnsafePointer<CChar>? = endpoint.cArray
.nullTerminated()
let cEndpoint: UnsafePointer<CChar>? = endpoint
.cString(using: .utf8)?
.unsafeCopy()
let cX25519Pubkey: UnsafePointer<CChar>? = x25519PublicKey
.suffix(64) // Quick way to drop '05' prefix if present
.cArray
.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() }
.cString(using: .utf8)?
.unsafeCopy()
let cHeaderKeys: UnsafeMutablePointer<UnsafePointer<CChar>?>? = cHeaderKeysContent
.unsafeCopy()
@ -304,10 +346,14 @@ public extension LibSession {
// Otherwise create a new network
var error: [CChar] = [CChar](repeating: 0, count: 256)
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 {
SNLog("[LibQuic Error] Unable to create network object: \(String(cString: error))")
Log.error("[LibQuic] Unable to create network object: \(String(cString: error))")
return nil
}
@ -336,12 +382,15 @@ public extension LibSession {
private static func updateNetworkStatus(cStatus: CONNECTION_STATUS) {
let status: NetworkStatus = NetworkStatus(status: cStatus)
SNLog("Network status changed to: \(status)")
lastNetworkStatus.mutate { lastNetworkStatus in
lastNetworkStatus = status
networkStatusCallbacks.wrappedValue.forEach { _, callback in
callback(status)
// Dispatch async so we don't hold up the libSession thread that triggered the update
DispatchQueue.global(qos: .default).async {
Log.info("Network status changed to: \(status)")
lastNetworkStatus.mutate { lastNetworkStatus in
lastNetworkStatus = 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
cPathsPtr?.deallocate()
lastPaths.mutate { lastPaths in
lastPaths = paths
pathsChangedCallbacks.wrappedValue.forEach { id, callback in
callback(paths, id)
// Dispatch async so we don't hold up the libSession thread that triggered the update
DispatchQueue.global(qos: .default).async {
lastPaths.mutate { lastPaths in
lastPaths = paths
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 (401, _):
SNLog("Unauthorised (Failed to verify the signature).")
Log.warn("Unauthorised (Failed to verify the signature).")
return NetworkError.unauthorised
case (404, _): return NetworkError.notFound
/// A snode will return a `406` but onion requests v4 seems to return `425` so handle both
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
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] {
/// 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 unsafeCopy() -> [UnsafePointer<CChar>?] {
return self.map { value in
let copy = UnsafeMutableBufferPointer<CChar>.allocate(capacity: value.count)
_ = copy.initialize(from: value)
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)
}
}
@ -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
/// 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 unsafeCopy() -> [UnsafePointer<UInt8>?] {
return self.map { value in
let copy = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: value.count)
_ = copy.initialize(from: value)
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)
}
}

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

@ -25,7 +25,7 @@ public extension Network {
self.requests = requests.map { Child(request: $0) }
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