Updated the code to decode and use updated notifications
Made the JobQueue execution type explicit Fixed a bug where legacy group's might not be unsubscribed from
This commit is contained in:
parent
61ad85b97b
commit
09ab977861
|
@ -654,6 +654,7 @@
|
|||
FD6A7A692818BE7300035AC1 /* RetrieveDefaultOpenGroupRoomsJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6A7A682818BE7300035AC1 /* RetrieveDefaultOpenGroupRoomsJob.swift */; };
|
||||
FD6A7A6B2818C17C00035AC1 /* UpdateProfilePictureJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6A7A6A2818C17C00035AC1 /* UpdateProfilePictureJob.swift */; };
|
||||
FD6A7A6D2818C61500035AC1 /* _002_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6A7A6C2818C61500035AC1 /* _002_SetupStandardJobs.swift */; };
|
||||
FD6E4C8A2A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6E4C892A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift */; };
|
||||
FD705A92278D051200F16121 /* ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A91278D051200F16121 /* ReusableView.swift */; };
|
||||
FD7115EB28C5D78E00B47552 /* ThreadSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115EA28C5D78E00B47552 /* ThreadSettingsViewModel.swift */; };
|
||||
FD7115F228C6CB3900B47552 /* _010_AddThreadIdToFTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115F128C6CB3900B47552 /* _010_AddThreadIdToFTS.swift */; };
|
||||
|
@ -817,6 +818,7 @@
|
|||
FDD2506E283711D600198BDA /* DifferenceKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */; };
|
||||
FDD250702837199200198BDA /* GarbageCollectionJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */; };
|
||||
FDD250722837234B00198BDA /* MediaGalleryNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */; };
|
||||
FDD82C3F2A205D0A00425F05 /* ProcessResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD82C3E2A205D0A00425F05 /* ProcessResult.swift */; };
|
||||
FDDC08F229A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDC08F129A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift */; };
|
||||
FDDCBDA829E776BF00303C38 /* seed2-2023-2y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA229E776BF00303C38 /* seed2-2023-2y.crt */; };
|
||||
FDDCBDA929E776BF00303C38 /* seed1-2023-2y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA329E776BF00303C38 /* seed1-2023-2y.crt */; };
|
||||
|
@ -910,6 +912,9 @@
|
|||
FDF848F329413DB0007DCAE5 /* ImagePickerHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F229413DB0007DCAE5 /* ImagePickerHandler.swift */; };
|
||||
FDF848F529413EEC007DCAE5 /* SessionCell+Styling.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F429413EEC007DCAE5 /* SessionCell+Styling.swift */; };
|
||||
FDF848F729414477007DCAE5 /* CurrentUserPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F629414477007DCAE5 /* CurrentUserPoller.swift */; };
|
||||
FDFBB74B2A1EFF4900CA7350 /* Bencode.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFBB74A2A1EFF4900CA7350 /* Bencode.swift */; };
|
||||
FDFBB74D2A1F3C4E00CA7350 /* NotificationMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFBB74C2A1F3C4E00CA7350 /* NotificationMetadata.swift */; };
|
||||
FDFBB7542A2023EB00CA7350 /* BencodeSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFBB7532A2023EB00CA7350 /* BencodeSpec.swift */; };
|
||||
FDFC4D9A29F0C51500992FB6 /* String+Trimming.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D22553860900C340D1 /* String+Trimming.swift */; };
|
||||
FDFC4E1929F1F9A600992FB6 /* libsession-util.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FDFC4E1829F1F9A600992FB6 /* libsession-util.xcframework */; };
|
||||
FDFD645927F26C6800808CA1 /* Array+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D12553860800C340D1 /* Array+Utilities.swift */; };
|
||||
|
@ -1802,6 +1807,7 @@
|
|||
FD6A7A682818BE7300035AC1 /* RetrieveDefaultOpenGroupRoomsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetrieveDefaultOpenGroupRoomsJob.swift; sourceTree = "<group>"; };
|
||||
FD6A7A6A2818C17C00035AC1 /* UpdateProfilePictureJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateProfilePictureJob.swift; sourceTree = "<group>"; };
|
||||
FD6A7A6C2818C61500035AC1 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = "<group>"; };
|
||||
FD6E4C892A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyUnsubscribeRequest.swift; sourceTree = "<group>"; };
|
||||
FD705A91278D051200F16121 /* ReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReusableView.swift; sourceTree = "<group>"; };
|
||||
FD7115EA28C5D78E00B47552 /* ThreadSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadSettingsViewModel.swift; sourceTree = "<group>"; };
|
||||
FD7115EF28C5D7DE00B47552 /* SessionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionHeaderView.swift; sourceTree = "<group>"; };
|
||||
|
@ -1958,6 +1964,7 @@
|
|||
FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DifferenceKit+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GarbageCollectionJob.swift; sourceTree = "<group>"; };
|
||||
FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGalleryNavigationController.swift; sourceTree = "<group>"; };
|
||||
FDD82C3E2A205D0A00425F05 /* ProcessResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessResult.swift; sourceTree = "<group>"; };
|
||||
FDDC08F129A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSessionTypeConversionUtilitiesSpec.swift; sourceTree = "<group>"; };
|
||||
FDDCBDA229E776BF00303C38 /* seed2-2023-2y.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "seed2-2023-2y.crt"; sourceTree = "<group>"; };
|
||||
FDDCBDA329E776BF00303C38 /* seed1-2023-2y.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "seed1-2023-2y.crt"; sourceTree = "<group>"; };
|
||||
|
@ -2054,6 +2061,9 @@
|
|||
FDF848F229413DB0007DCAE5 /* ImagePickerHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePickerHandler.swift; sourceTree = "<group>"; };
|
||||
FDF848F429413EEC007DCAE5 /* SessionCell+Styling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SessionCell+Styling.swift"; sourceTree = "<group>"; };
|
||||
FDF848F629414477007DCAE5 /* CurrentUserPoller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrentUserPoller.swift; sourceTree = "<group>"; };
|
||||
FDFBB74A2A1EFF4900CA7350 /* Bencode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bencode.swift; sourceTree = "<group>"; };
|
||||
FDFBB74C2A1F3C4E00CA7350 /* NotificationMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationMetadata.swift; sourceTree = "<group>"; };
|
||||
FDFBB7532A2023EB00CA7350 /* BencodeSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BencodeSpec.swift; sourceTree = "<group>"; };
|
||||
FDFC4E1829F1F9A600992FB6 /* libsession-util.xcframework */ = {isa = PBXFileReference; explicitFileType = wrapper.xcframework; includeInIndex = 0; path = "libsession-util.xcframework"; sourceTree = BUILD_DIR; };
|
||||
FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGeneralCache.swift; sourceTree = "<group>"; };
|
||||
|
@ -3612,6 +3622,7 @@
|
|||
FD09796527F6B0A800936362 /* Utilities */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FDFBB74A2A1EFF4900CA7350 /* Bencode.swift */,
|
||||
FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */,
|
||||
FD09796A27F6C67500936362 /* Failable.swift */,
|
||||
FD09797127FAA2F500936362 /* Optional+Utilities.swift */,
|
||||
|
@ -4045,6 +4056,7 @@
|
|||
FD37EA1228AB3F60003AE748 /* Database */,
|
||||
FD83B9B927CF20A5005E1583 /* General */,
|
||||
FD9B30F1293EA0AF008DEE3E /* Networking */,
|
||||
FDFBB7522A2023DE00CA7350 /* Utilities */,
|
||||
);
|
||||
path = SessionUtilitiesKitTests;
|
||||
sourceTree = "<group>";
|
||||
|
@ -4163,6 +4175,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
FDC13D482A16EC20007267C7 /* Service.swift */,
|
||||
FDD82C3E2A205D0A00425F05 /* ProcessResult.swift */,
|
||||
FDC13D4F2A16EE50007267C7 /* PushNotificationAPIEndpoint.swift */,
|
||||
);
|
||||
path = Types;
|
||||
|
@ -4225,9 +4238,11 @@
|
|||
FDC13D4A2A16ECBA007267C7 /* SubscribeResponse.swift */,
|
||||
FDC13D552A171FE4007267C7 /* UnsubscribeRequest.swift */,
|
||||
FDC13D572A17207D007267C7 /* UnsubscribeResponse.swift */,
|
||||
FD6E4C892A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift */,
|
||||
FDC13D532A16FF29007267C7 /* LegacyGroupRequest.swift */,
|
||||
FDC4382E27B383AF00C60D73 /* LegacyPushServerResponse.swift */,
|
||||
FDC13D592A1721C5007267C7 /* LegacyNotifyRequest.swift */,
|
||||
FDFBB74C2A1F3C4E00CA7350 /* NotificationMetadata.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
|
@ -4425,6 +4440,14 @@
|
|||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FDFBB7522A2023DE00CA7350 /* Utilities */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FDFBB7532A2023EB00CA7350 /* BencodeSpec.swift */,
|
||||
);
|
||||
path = Utilities;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FDFDE122282D04E30098B17F /* Transitions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -5644,6 +5667,7 @@
|
|||
C3D9E4C02567767F0040E4F3 /* DataSource.m in Sources */,
|
||||
C32C5DD2256DD9E5003C73A2 /* LRUCache.swift in Sources */,
|
||||
FD71160228C8255900B47552 /* UIControl+Combine.swift in Sources */,
|
||||
FDFBB74B2A1EFF4900CA7350 /* Bencode.swift in Sources */,
|
||||
FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */,
|
||||
FDF8487929405906007DCAE5 /* HTTPQueryParam.swift in Sources */,
|
||||
FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */,
|
||||
|
@ -5758,6 +5782,7 @@
|
|||
FD245C5A2850660100B966DD /* LinkPreviewDraft.swift in Sources */,
|
||||
FDD250702837199200198BDA /* GarbageCollectionJob.swift in Sources */,
|
||||
FD6A7A6B2818C17C00035AC1 /* UpdateProfilePictureJob.swift in Sources */,
|
||||
FDD82C3F2A205D0A00425F05 /* ProcessResult.swift in Sources */,
|
||||
FD716E6A2850327900C96BF4 /* EndCallMode.swift in Sources */,
|
||||
FDF0B75C2807F41D004C14C5 /* MessageSender+Convenience.swift in Sources */,
|
||||
7B81682A28B6F1420069F315 /* ReactionResponse.swift in Sources */,
|
||||
|
@ -5771,6 +5796,7 @@
|
|||
FD245C57285065F100B966DD /* Poller.swift in Sources */,
|
||||
FDA8EAFE280E8B78002B68E5 /* FailedMessageSendsJob.swift in Sources */,
|
||||
FD245C6A2850666F00B966DD /* FileServerAPI.swift in Sources */,
|
||||
FDFBB74D2A1F3C4E00CA7350 /* NotificationMetadata.swift in Sources */,
|
||||
FDC4386927B4E6B800C60D73 /* String+Utlities.swift in Sources */,
|
||||
FD716E6628502EE200C96BF4 /* CurrentCallProtocol.swift in Sources */,
|
||||
FD8ECF9029381FC200C0D1BB /* SessionUtil+UserProfile.swift in Sources */,
|
||||
|
@ -5808,6 +5834,7 @@
|
|||
FDC6D6F32860607300B04575 /* Environment.swift in Sources */,
|
||||
C3A3A171256E1D25004D228D /* SSKReachabilityManager.swift in Sources */,
|
||||
FD245C59285065FC00B966DD /* ControlMessage.swift in Sources */,
|
||||
FD6E4C8A2A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift in Sources */,
|
||||
B8DE1FB626C22FCB0079C9CE /* CallMessage.swift in Sources */,
|
||||
FD245C50285065C700B966DD /* VisibleMessage+Quote.swift in Sources */,
|
||||
FD8ECF892935AB7200C0D1BB /* SessionUtilError.swift in Sources */,
|
||||
|
@ -6155,6 +6182,7 @@
|
|||
FD2AAAF228ED57B500A49611 /* SynchronousStorage.swift in Sources */,
|
||||
FD078E4927E02576000769AF /* CommonMockedExtensions.swift in Sources */,
|
||||
FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */,
|
||||
FDFBB7542A2023EB00CA7350 /* BencodeSpec.swift in Sources */,
|
||||
FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */,
|
||||
FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
|
||||
FD23EA6328ED0B260058676E /* CombineExtensions.swift in Sources */,
|
||||
|
|
|
@ -24,6 +24,7 @@ public protocol SodiumType {
|
|||
public protocol AeadXChaCha20Poly1305IetfType {
|
||||
var KeyBytes: Int { get }
|
||||
var ABytes: Int { get }
|
||||
var NonceBytes: Int { get }
|
||||
|
||||
func encrypt(message: Bytes, secretKey: Bytes, nonce: Bytes, additionalData: Bytes?) -> Bytes?
|
||||
func decrypt(authenticatedCipherText: Bytes, secretKey: Bytes, nonce: Bytes, additionalData: Bytes?) -> Bytes?
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
extension PushNotificationAPI {
|
||||
struct NotificationMetadata: Codable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case accountId = "@"
|
||||
case hash = "#"
|
||||
case namespace = "n"
|
||||
case dataLength = "l"
|
||||
case dataTooLong = "B"
|
||||
}
|
||||
|
||||
/// Account ID (such as Session ID or closed group ID) where the message arrived.
|
||||
let accountId: String
|
||||
|
||||
/// The hash of the message in the swarm.
|
||||
let hash: String
|
||||
|
||||
/// The swarm namespace in which this message arrived.
|
||||
let namespace: Int
|
||||
|
||||
/// The length of the message data. This is always included, even if the message content
|
||||
/// itself was too large to fit into the push notification.
|
||||
let dataLength: Int
|
||||
|
||||
/// This will be `true` if the data was omitted because it was too long to fit in a push
|
||||
/// notification (around 2.5kB of raw data), in which case the push notification includes
|
||||
/// only this metadata but not the message content itself.
|
||||
let dataTooLong: Bool
|
||||
}
|
||||
}
|
||||
|
||||
extension PushNotificationAPI.NotificationMetadata {
|
||||
init(from decoder: Decoder) throws {
|
||||
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
self = PushNotificationAPI.NotificationMetadata(
|
||||
accountId: try container.decode(String.self, forKey: .accountId),
|
||||
hash: try container.decode(String.self, forKey: .hash),
|
||||
namespace: try container.decode(Int.self, forKey: .namespace),
|
||||
dataLength: try container.decode(Int.self, forKey: .dataLength),
|
||||
dataTooLong: ((try? container.decode(Bool.self, forKey: .dataTooLong)) ?? false)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -54,13 +54,13 @@ public enum PushNotificationAPI {
|
|||
}
|
||||
|
||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db)
|
||||
let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType]
|
||||
.defaulting(to: Preferences.NotificationPreviewType.defaultPreviewType)
|
||||
|
||||
let request: SubscribeRequest = SubscribeRequest(
|
||||
pubkey: currentUserPublicKey,
|
||||
namespaces: [.default],
|
||||
includeMessageData: (previewType == .nameAndPreview), // TODO: Test resubscribing when changing the type
|
||||
// Note: Unfortunately we always need the message content because without the content
|
||||
// control messages can't be distinguished from visible messages which results in the
|
||||
// 'generic' notification being shown when receiving things like typing indicator updates
|
||||
includeMessageData: true,
|
||||
serviceInfo: SubscribeRequest.ServiceInfo(
|
||||
token: hexEncodedToken
|
||||
),
|
||||
|
@ -349,14 +349,6 @@ public enum PushNotificationAPI {
|
|||
) -> AnyPublisher<Void, Error> {
|
||||
let isUsingFullAPNs = UserDefaults.standard[.isUsingFullAPNs]
|
||||
|
||||
// TODO: Need to validate if this is actually desired behaviour - would this check prevent the app from unsubscribing if the user switches off fast mode??? (this is what the app is currently doing)
|
||||
// TODO: This flag seems like it might actually be buggy... should double check it
|
||||
guard isUsingFullAPNs else {
|
||||
return Just(())
|
||||
.setFailureType(to: Error.self)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
return PushNotificationAPI
|
||||
.send(
|
||||
request: PushNotificationAPIRequest(
|
||||
|
@ -385,11 +377,70 @@ public enum PushNotificationAPI {
|
|||
.map { _ in () }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
// MARK: - Notification Handling
|
||||
|
||||
public static func processNotification(
|
||||
notificationContent: UNNotificationContent,
|
||||
dependencies: SMKDependencies = SMKDependencies()
|
||||
) -> (envelope: SNProtoEnvelope?, result: ProcessResult) {
|
||||
// Make sure the notification is from the updated push server
|
||||
guard notificationContent.userInfo["spns"] != nil else {
|
||||
guard
|
||||
let base64EncodedData: String = notificationContent.userInfo["ENCRYPTED_DATA"] as? String,
|
||||
let data: Data = Data(base64Encoded: base64EncodedData),
|
||||
let envelope: SNProtoEnvelope = try? MessageWrapper.unwrap(data: data)
|
||||
else { return (nil, .legacyFailure) }
|
||||
|
||||
// We only support legacy notifications for legacy group conversations
|
||||
guard envelope.type == .closedGroupMessage else { return (envelope, .legacyForceSilent) }
|
||||
|
||||
return (envelope, .legacySuccess)
|
||||
}
|
||||
|
||||
guard
|
||||
let base64EncodedEncString: String = notificationContent.userInfo["enc_payload"] as? String,
|
||||
let encData: Data = Data(base64Encoded: base64EncodedEncString),
|
||||
let notificationsEncryptionKey: Data = try? getOrGenerateEncryptionKey(),
|
||||
encData.count > dependencies.aeadXChaCha20Poly1305Ietf.NonceBytes
|
||||
else { return (nil, .failure) }
|
||||
|
||||
let nonce: Data = encData[0..<dependencies.aeadXChaCha20Poly1305Ietf.NonceBytes]
|
||||
let payload: Data = encData[dependencies.aeadXChaCha20Poly1305Ietf.NonceBytes...]
|
||||
|
||||
guard
|
||||
let paddedData: [UInt8] = dependencies.aeadXChaCha20Poly1305Ietf.decrypt(
|
||||
authenticatedCipherText: payload.bytes,
|
||||
secretKey: notificationsEncryptionKey.bytes,
|
||||
nonce: nonce.bytes
|
||||
)
|
||||
else { return (nil, .failure) }
|
||||
|
||||
let decryptedData: Data = Data(paddedData.reversed().drop(while: { $0 == 0 }).reversed())
|
||||
|
||||
// Decode the decrypted data
|
||||
guard let notification: BencodeResponse<NotificationMetadata> = try? Bencode.decodeResponse(from: decryptedData) else {
|
||||
return (nil, .failure)
|
||||
}
|
||||
|
||||
// If the metadata says that the message was too large then we should show the generic
|
||||
// notification (this is a valid case)
|
||||
guard !notification.info.dataTooLong else { return (nil, .success) }
|
||||
|
||||
// Check that the body we were given is valid
|
||||
guard
|
||||
let notificationData: Data = notification.data,
|
||||
notification.info.dataLength == notificationData.count,
|
||||
let envelope = try? MessageWrapper.unwrap(data: notificationData)
|
||||
else { return (nil, .failure) }
|
||||
|
||||
// Success, we have the notification content
|
||||
return (envelope, .success)
|
||||
}
|
||||
|
||||
// MARK: - Security
|
||||
|
||||
@discardableResult private static func getOrGenerateEncryptionKey() throws -> Data {
|
||||
// TODO: May want to work this differently (will break after a phone restart if the device hasn't been unlocked yet)
|
||||
do {
|
||||
var encryptionKey: Data = try SSKDefaultKeychainStorage.shared.data(
|
||||
forService: keychainService,
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
public extension PushNotificationAPI {
|
||||
enum ProcessResult {
|
||||
case success
|
||||
case failure
|
||||
case legacySuccess
|
||||
case legacyFailure
|
||||
case legacyForceSilent
|
||||
}
|
||||
}
|
|
@ -5,5 +5,6 @@ import Foundation
|
|||
extension PushNotificationAPI {
|
||||
enum Service: String, Codable {
|
||||
case apns
|
||||
case sandbox = "apns-sandbox" // Use for push notifications in Testnet
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import Sodium
|
|||
class MockAeadXChaCha20Poly1305Ietf: Mock<AeadXChaCha20Poly1305IetfType>, AeadXChaCha20Poly1305IetfType {
|
||||
var KeyBytes: Int = 32
|
||||
var ABytes: Int = 16
|
||||
var NonceBytes: Int = 24
|
||||
|
||||
func encrypt(message: Bytes, secretKey: Bytes, nonce: Bytes, additionalData: Bytes?) -> Bytes? {
|
||||
return accept(args: [message, secretKey, nonce, additionalData]) as? Bytes
|
||||
|
|
|
@ -57,12 +57,22 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
|
|||
)
|
||||
}
|
||||
|
||||
let (maybeEnvelope, result) = PushNotificationAPI.processNotification(
|
||||
notificationContent: notificationContent
|
||||
)
|
||||
|
||||
guard
|
||||
let base64EncodedData: String = notificationContent.userInfo["ENCRYPTED_DATA"] as? String,
|
||||
let data: Data = Data(base64Encoded: base64EncodedData),
|
||||
let envelope = try? MessageWrapper.unwrap(data: data)
|
||||
(result == .success || result == .legacySuccess),
|
||||
let envelope: SNProtoEnvelope = maybeEnvelope
|
||||
else {
|
||||
return self.handleFailure(for: notificationContent)
|
||||
switch result {
|
||||
// If we got an explicit failure, or we got a success but no content then show
|
||||
// the fallback notification
|
||||
case .success, .legacySuccess, .failure, .legacyFailure:
|
||||
return self.handleFailure(for: notificationContent)
|
||||
|
||||
case .legacyForceSilent: return
|
||||
}
|
||||
}
|
||||
|
||||
// HACK: It is important to use write synchronously here to avoid a race condition
|
||||
|
|
|
@ -784,50 +784,15 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
}
|
||||
|
||||
public static func process(bencodedData data: Data) -> (info: ResponseInfoType, body: Data?)? {
|
||||
// The data will be in the form of `l123:jsone` or `l123:json456:bodye` so we need to break
|
||||
// the data into parts to properly process it
|
||||
guard let responseString: String = String(data: data, encoding: .ascii), responseString.starts(with: "l") else {
|
||||
guard let response: BencodeResponse<HTTP.ResponseInfo> = try? Bencode.decodeResponse(from: data) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let stringParts: [String.SubSequence] = responseString.split(separator: ":")
|
||||
|
||||
guard stringParts.count > 1, let infoLength: Int = Int(stringParts[0].suffix(from: stringParts[0].index(stringParts[0].startIndex, offsetBy: 1))) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let infoStringStartIndex: String.Index = responseString.index(responseString.startIndex, offsetBy: "l\(infoLength):".count)
|
||||
let infoStringEndIndex: String.Index = responseString.index(infoStringStartIndex, offsetBy: infoLength)
|
||||
let infoString: String = String(responseString[infoStringStartIndex..<infoStringEndIndex])
|
||||
|
||||
guard let infoStringData: Data = infoString.data(using: .utf8), let responseInfo: HTTP.ResponseInfo = try? JSONDecoder().decode(HTTP.ResponseInfo.self, from: infoStringData) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Custom handle a clock out of sync error (v4 returns '425' but included the '406' just
|
||||
// in case)
|
||||
guard responseInfo.code != 406 && responseInfo.code != 425 else { return nil }
|
||||
guard responseInfo.code != 401 else { return nil }
|
||||
guard response.info.code != 406 && response.info.code != 425 else { return nil }
|
||||
guard response.info.code != 401 else { return nil }
|
||||
|
||||
// If there is no data in the response then just return the ResponseInfo
|
||||
guard responseString.count > "l\(infoLength)\(infoString)e".count else {
|
||||
return (responseInfo, nil)
|
||||
}
|
||||
|
||||
// Extract the response data as well
|
||||
let dataString: String = String(responseString.suffix(from: infoStringEndIndex))
|
||||
let dataStringParts: [String.SubSequence] = dataString.split(separator: ":")
|
||||
|
||||
guard dataStringParts.count > 1, let finalDataLength: Int = Int(dataStringParts[0]), let suffixData: Data = "e".data(using: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let dataBytes: Array<UInt8> = Array(data)
|
||||
let dataEndIndex: Int = (dataBytes.count - suffixData.count)
|
||||
let dataStartIndex: Int = (dataEndIndex - finalDataLength)
|
||||
let finalDataBytes: ArraySlice<UInt8> = dataBytes[dataStartIndex..<dataEndIndex]
|
||||
let finalData: Data = Data(finalDataBytes)
|
||||
|
||||
return (responseInfo, finalData)
|
||||
return (response.info, response.data)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ public final class JobRunner {
|
|||
private static let blockingQueue: Atomic<JobQueue?> = Atomic(
|
||||
JobQueue(
|
||||
type: .blocking,
|
||||
executionType: .serial,
|
||||
qos: .default,
|
||||
jobVariants: [],
|
||||
onQueueDrained: {
|
||||
|
@ -85,6 +86,7 @@ public final class JobRunner {
|
|||
)
|
||||
let attachmentDownloadQueue: JobQueue = JobQueue(
|
||||
type: .attachmentDownload,
|
||||
executionType: .serial,
|
||||
qos: .utility,
|
||||
jobVariants: [
|
||||
jobVariants.remove(.attachmentDownload)
|
||||
|
@ -92,6 +94,7 @@ public final class JobRunner {
|
|||
)
|
||||
let generalQueue: JobQueue = JobQueue(
|
||||
type: .general(number: 0),
|
||||
executionType: .serial,
|
||||
qos: .utility,
|
||||
jobVariants: Array(jobVariants)
|
||||
)
|
||||
|
@ -509,7 +512,7 @@ private final class JobQueue {
|
|||
|
||||
init(
|
||||
type: QueueType,
|
||||
executionType: ExecutionType = .serial,
|
||||
executionType: ExecutionType,
|
||||
qos: DispatchQoS,
|
||||
jobVariants: [Job.Variant],
|
||||
onQueueDrained: (() -> ())? = nil
|
||||
|
|
|
@ -0,0 +1,263 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol BencodableType {
|
||||
associatedtype ValueType: BencodableType
|
||||
|
||||
static var isCollection: Bool { get }
|
||||
static var isDictionary: Bool { get }
|
||||
}
|
||||
|
||||
public struct BencodeResponse<T: Codable> {
|
||||
public let info: T
|
||||
public let data: Data?
|
||||
}
|
||||
|
||||
extension BencodeResponse: Equatable where T: Equatable {}
|
||||
|
||||
public enum Bencode {
|
||||
private enum Element: Character {
|
||||
case number0 = "0"
|
||||
case number1 = "1"
|
||||
case number2 = "2"
|
||||
case number3 = "3"
|
||||
case number4 = "4"
|
||||
case number5 = "5"
|
||||
case number6 = "6"
|
||||
case number7 = "7"
|
||||
case number8 = "8"
|
||||
case number9 = "9"
|
||||
case intIndicator = "i"
|
||||
case listIndicator = "l"
|
||||
case dictIndicator = "d"
|
||||
case endIndicator = "e"
|
||||
case separator = ":"
|
||||
|
||||
init?(_ byte: UInt8?) {
|
||||
guard
|
||||
let byte: UInt8 = byte,
|
||||
let byteString: String = String(data: Data([byte]), encoding: .utf8),
|
||||
let character: Character = byteString.first,
|
||||
let result: Element = Element(rawValue: character)
|
||||
else { return nil }
|
||||
|
||||
self = result
|
||||
}
|
||||
}
|
||||
|
||||
private struct BencodeString {
|
||||
let value: String?
|
||||
let rawValue: Data
|
||||
}
|
||||
|
||||
// MARK: - Functions
|
||||
|
||||
public static func decodeResponse<T>(
|
||||
from data: Data,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> BencodeResponse<T> where T: Decodable {
|
||||
guard
|
||||
let result: [Data] = try? decode([Data].self, from: data),
|
||||
let responseData: Data = result.first
|
||||
else { throw HTTPError.parsingFailed }
|
||||
|
||||
return BencodeResponse(
|
||||
info: try responseData.decoded(as: T.self, using: dependencies),
|
||||
data: (result.count > 1 ? result.last : nil)
|
||||
)
|
||||
}
|
||||
|
||||
public static func decode<T: BencodableType>(_ type: T.Type, from data: Data) throws -> T {
|
||||
guard
|
||||
let decodedData: (value: Any, remainingData: Data) = decodeData(data),
|
||||
decodedData.remainingData.isEmpty == true // Ensure there is no left over data
|
||||
else { throw HTTPError.parsingFailed }
|
||||
|
||||
return try recursiveCast(type, from: decodedData.value)
|
||||
}
|
||||
|
||||
// MARK: - Logic
|
||||
|
||||
private static func decodeData(_ data: Data) -> (value: Any, remainingData: Data)? {
|
||||
switch Element(data.first) {
|
||||
case .number0, .number1, .number2, .number3, .number4,
|
||||
.number5, .number6, .number7, .number8, .number9:
|
||||
return decodeString(data)
|
||||
|
||||
case .intIndicator: return decodeInt(data)
|
||||
case .listIndicator: return decodeList(data)
|
||||
case .dictIndicator: return decodeDict(data)
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode a string element from iterator assumed to have structure `{length}:{data}`
|
||||
private static func decodeString(_ data: Data) -> (value: BencodeString, remainingData: Data)? {
|
||||
var mutableData: Data = data
|
||||
var lengthData: [UInt8] = []
|
||||
|
||||
// Remove bytes until we hit the separator
|
||||
while let next: UInt8 = mutableData.popFirst(), Element(next) != .separator {
|
||||
lengthData.append(next)
|
||||
}
|
||||
|
||||
// Need to reset the index of the data (it maintains the index after popping/slicing)
|
||||
// See https://forums.swift.org/t/data-subscript/57195 for more info
|
||||
mutableData = Data(mutableData)
|
||||
|
||||
guard
|
||||
let lengthString: String = String(data: Data(lengthData), encoding: .ascii),
|
||||
let length: Int = Int(lengthString, radix: 10),
|
||||
mutableData.count >= length
|
||||
else { return nil }
|
||||
|
||||
// Need to reset the index of the data (it maintains the index after popping/slicing)
|
||||
// See https://forums.swift.org/t/data-subscript/57195 for more info
|
||||
return (
|
||||
BencodeString(
|
||||
value: String(data: mutableData[0..<length], encoding: .ascii),
|
||||
rawValue: mutableData[0..<length]
|
||||
),
|
||||
Data(mutableData.dropFirst(length))
|
||||
)
|
||||
}
|
||||
|
||||
/// Decode an int element from iterator assumed to have structure `i{int}e`
|
||||
private static func decodeInt(_ data: Data) -> (value: Int, remainingData: Data)? {
|
||||
var mutableData: Data = data
|
||||
var intData: [UInt8] = []
|
||||
_ = mutableData.popFirst() // drop `i`
|
||||
|
||||
// Pop until after `e`
|
||||
while let next: UInt8 = mutableData.popFirst(), Element(next) != .endIndicator {
|
||||
intData.append(next)
|
||||
}
|
||||
|
||||
guard
|
||||
let intString: String = String(data: Data(intData), encoding: .ascii),
|
||||
let result: Int = Int(intString, radix: 10)
|
||||
else { return nil }
|
||||
|
||||
// Need to reset the index of the data (it maintains the index after popping/slicing)
|
||||
// See https://forums.swift.org/t/data-subscript/57195 for more info
|
||||
return (result, Data(mutableData))
|
||||
}
|
||||
|
||||
/// Decode a list element from iterator assumed to have structure `l{data}e`
|
||||
private static func decodeList(_ data: Data) -> ([Any], Data)? {
|
||||
var mutableData: Data = data
|
||||
var listElements: [Any] = []
|
||||
_ = mutableData.popFirst() // drop `l`
|
||||
|
||||
while !mutableData.isEmpty, let next: UInt8 = mutableData.first, Element(next) != .endIndicator {
|
||||
guard let result = decodeData(mutableData) else { break }
|
||||
|
||||
listElements.append(result.value)
|
||||
mutableData = result.remainingData
|
||||
}
|
||||
|
||||
_ = mutableData.popFirst() // drop `e`
|
||||
|
||||
// Need to reset the index of the data (it maintains the index after popping/slicing)
|
||||
// See https://forums.swift.org/t/data-subscript/57195 for more info
|
||||
return (listElements, Data(mutableData))
|
||||
}
|
||||
|
||||
/// Decode a dict element from iterator assumed to have structure `d{data}e`
|
||||
private static func decodeDict(_ data: Data) -> ([String: Any], Data)? {
|
||||
var mutableData: Data = data
|
||||
var dictElements: [String: Any] = [:]
|
||||
_ = mutableData.popFirst() // drop `d`
|
||||
|
||||
while !mutableData.isEmpty, let next: UInt8 = mutableData.first, Element(next) != .endIndicator {
|
||||
guard
|
||||
let keyResult = decodeString(mutableData),
|
||||
let key: String = keyResult.value.value,
|
||||
let valueResult = decodeData(keyResult.remainingData)
|
||||
else { return nil }
|
||||
|
||||
dictElements[key] = valueResult.value
|
||||
mutableData = valueResult.remainingData
|
||||
}
|
||||
|
||||
_ = mutableData.popFirst() // drop `e`
|
||||
|
||||
// Need to reset the index of the data (it maintains the index after popping/slicing)
|
||||
// See https://forums.swift.org/t/data-subscript/57195 for more info
|
||||
return (dictElements, Data(mutableData))
|
||||
}
|
||||
|
||||
// MARK: - Internal Functions
|
||||
|
||||
private static func recursiveCast<T: BencodableType>(_ type: T.Type, from value: Any) throws -> T {
|
||||
switch (type.isCollection, type.isDictionary) {
|
||||
case (_, true):
|
||||
guard let dictValue: [String: Any] = value as? [String: Any] else { throw HTTPError.parsingFailed }
|
||||
|
||||
return try (
|
||||
dictValue.mapValues { try recursiveCast(type.ValueType.self, from: $0) } as? T ??
|
||||
{ throw HTTPError.parsingFailed }()
|
||||
)
|
||||
|
||||
case (true, _):
|
||||
guard let arrayValue: [Any] = value as? [Any] else { throw HTTPError.parsingFailed }
|
||||
|
||||
return try (
|
||||
arrayValue.map { try recursiveCast(type.ValueType.self, from: $0) } as? T ??
|
||||
{ throw HTTPError.parsingFailed }()
|
||||
)
|
||||
|
||||
default:
|
||||
switch (value, type) {
|
||||
case (let bencodeString as BencodeString, is String.Type):
|
||||
return try (bencodeString.value as? T ?? { throw HTTPError.parsingFailed }())
|
||||
|
||||
case (let bencodeString as BencodeString, is Optional<String>.Type):
|
||||
return try (bencodeString.value as? T ?? { throw HTTPError.parsingFailed }())
|
||||
|
||||
case (let bencodeString as BencodeString, _):
|
||||
return try (bencodeString.rawValue as? T ?? { throw HTTPError.parsingFailed }())
|
||||
|
||||
default: return try (value as? T ?? { throw HTTPError.parsingFailed }())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - BencodableType Extensions
|
||||
|
||||
extension Data: BencodableType {
|
||||
public typealias ValueType = Data
|
||||
|
||||
public static var isCollection: Bool { false }
|
||||
public static var isDictionary: Bool { false }
|
||||
}
|
||||
|
||||
extension Int: BencodableType {
|
||||
public typealias ValueType = Int
|
||||
|
||||
public static var isCollection: Bool { false }
|
||||
public static var isDictionary: Bool { false }
|
||||
}
|
||||
|
||||
extension String: BencodableType {
|
||||
public typealias ValueType = String
|
||||
|
||||
public static var isCollection: Bool { false }
|
||||
public static var isDictionary: Bool { false }
|
||||
}
|
||||
|
||||
extension Array: BencodableType where Element: BencodableType {
|
||||
public typealias ValueType = Element
|
||||
|
||||
public static var isCollection: Bool { true }
|
||||
public static var isDictionary: Bool { false }
|
||||
}
|
||||
|
||||
extension Dictionary: BencodableType where Key == String, Value: BencodableType {
|
||||
public typealias ValueType = Value
|
||||
|
||||
public static var isCollection: Bool { false }
|
||||
public static var isDictionary: Bool { true }
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
|
||||
@testable import SessionUtilitiesKit
|
||||
|
||||
class BencodeSpec: QuickSpec {
|
||||
struct TestType: Codable, Equatable {
|
||||
let intValue: Int
|
||||
let stringValue: String
|
||||
}
|
||||
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
describe("Bencode") {
|
||||
context("when decoding") {
|
||||
it("should decode a basic string") {
|
||||
let basicStringData: Data = "5:howdy".data(using: .utf8)!
|
||||
let result = try? Bencode.decode(String.self, from: basicStringData)
|
||||
|
||||
expect(result).to(equal("howdy"))
|
||||
}
|
||||
|
||||
it("should decode a basic integer") {
|
||||
let basicIntegerData: Data = "i3e".data(using: .utf8)!
|
||||
let result = try? Bencode.decode(Int.self, from: basicIntegerData)
|
||||
|
||||
expect(result).to(equal(3))
|
||||
}
|
||||
|
||||
it("should decode a list of integers") {
|
||||
let basicIntListData: Data = "li1ei2ee".data(using: .utf8)!
|
||||
let result = try? Bencode.decode([Int].self, from: basicIntListData)
|
||||
|
||||
expect(result).to(equal([1, 2]))
|
||||
}
|
||||
|
||||
it("should decode a basic dict") {
|
||||
let basicDictData: Data = "d4:spaml1:a1:bee".data(using: .utf8)!
|
||||
let result = try? Bencode.decode([String: [String]].self, from: basicDictData)
|
||||
|
||||
expect(result).to(equal(["spam": ["a", "b"]]))
|
||||
}
|
||||
}
|
||||
|
||||
context("when decoding a response") {
|
||||
it("decodes successfully") {
|
||||
let data: Data = "l37:{\"intValue\":100,\"stringValue\":\"Test\"}5:\u{01}\u{02}\u{03}\u{04}\u{05}e"
|
||||
.data(using: .utf8)!
|
||||
let result: BencodeResponse<TestType>? = try? Bencode.decodeResponse(from: data)
|
||||
|
||||
expect(result)
|
||||
.to(equal(
|
||||
BencodeResponse(
|
||||
info: TestType(
|
||||
intValue: 100,
|
||||
stringValue: "Test"
|
||||
),
|
||||
data: Data([1, 2, 3, 4, 5])
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
it("decodes successfully with no body") {
|
||||
let data: Data = "l37:{\"intValue\":100,\"stringValue\":\"Test\"}e"
|
||||
.data(using: .utf8)!
|
||||
let result: BencodeResponse<TestType>? = try? Bencode.decodeResponse(from: data)
|
||||
|
||||
expect(result)
|
||||
.to(equal(
|
||||
BencodeResponse(
|
||||
info: TestType(
|
||||
intValue: 100,
|
||||
stringValue: "Test"
|
||||
),
|
||||
data: nil
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
it("throws a parsing error when invalid") {
|
||||
let data: Data = "l36:{\"INVALID\":100,\"stringValue\":\"Test\"}5:\u{01}\u{02}\u{03}\u{04}\u{05}e"
|
||||
.data(using: .utf8)!
|
||||
|
||||
expect {
|
||||
let result: BencodeResponse<TestType> = try Bencode.decodeResponse(from: data)
|
||||
_ = result
|
||||
}.to(throwError(HTTPError.parsingFailed))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue