Add BackupCDNCache for read credentials, metadata
This commit is contained in:
parent
87b79704a7
commit
be9340bdb8
@ -2763,6 +2763,9 @@
|
||||
D9C964092BE44D700058F143 /* XCTest+Thenable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C964072BE44D510058F143 /* XCTest+Thenable.swift */; };
|
||||
D9C964102BE451CE0058F143 /* TSMessageStorageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C9640F2BE451CE0058F143 /* TSMessageStorageTest.swift */; };
|
||||
D9C964142BE45A030058F143 /* SignedPreKeyDeletionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C964132BE45A030058F143 /* SignedPreKeyDeletionTests.swift */; };
|
||||
D9C96F712E21A6BE00CA885A /* BackupCDNReadCredential.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C96F702E21A6B900CA885A /* BackupCDNReadCredential.swift */; };
|
||||
D9C96F752E21AA5800CA885A /* BackupCDNCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C96F742E21AA4900CA885A /* BackupCDNCache.swift */; };
|
||||
D9C96F772E21ADF000CA885A /* BackupCDNMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C96F762E21ADBE00CA885A /* BackupCDNMetadata.swift */; };
|
||||
D9CA61482C2E2D0000F99EA3 /* BackupArchiveAdHocCallArchiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9CA61472C2E2D0000F99EA3 /* BackupArchiveAdHocCallArchiver.swift */; };
|
||||
D9CA614B2C2F675E00F99EA3 /* PrivateStoryThreadDeletionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9CA614A2C2F675E00F99EA3 /* PrivateStoryThreadDeletionManager.swift */; };
|
||||
D9CA8AB02B698DFF00787167 /* DeletedCallRecordCleanupManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9CA8AAF2B698DFF00787167 /* DeletedCallRecordCleanupManager.swift */; };
|
||||
@ -6720,6 +6723,9 @@
|
||||
D9C964072BE44D510058F143 /* XCTest+Thenable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTest+Thenable.swift"; sourceTree = "<group>"; };
|
||||
D9C9640F2BE451CE0058F143 /* TSMessageStorageTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSMessageStorageTest.swift; sourceTree = "<group>"; };
|
||||
D9C964132BE45A030058F143 /* SignedPreKeyDeletionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignedPreKeyDeletionTests.swift; sourceTree = "<group>"; };
|
||||
D9C96F702E21A6B900CA885A /* BackupCDNReadCredential.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupCDNReadCredential.swift; sourceTree = "<group>"; };
|
||||
D9C96F742E21AA4900CA885A /* BackupCDNCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupCDNCache.swift; sourceTree = "<group>"; };
|
||||
D9C96F762E21ADBE00CA885A /* BackupCDNMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupCDNMetadata.swift; sourceTree = "<group>"; };
|
||||
D9CA61472C2E2D0000F99EA3 /* BackupArchiveAdHocCallArchiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupArchiveAdHocCallArchiver.swift; sourceTree = "<group>"; };
|
||||
D9CA614A2C2F675E00F99EA3 /* PrivateStoryThreadDeletionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateStoryThreadDeletionManager.swift; sourceTree = "<group>"; };
|
||||
D9CA8AAF2B698DFF00787167 /* DeletedCallRecordCleanupManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedCallRecordCleanupManager.swift; sourceTree = "<group>"; };
|
||||
@ -9626,6 +9632,9 @@
|
||||
667BB2042C58073600E79B57 /* Attachments */,
|
||||
66A1F4E02E035BE40095DE4B /* BackupExportJob */,
|
||||
D9388C8F2DA4751A0048D4F9 /* Settings */,
|
||||
D9C96F742E21AA4900CA885A /* BackupCDNCache.swift */,
|
||||
D9C96F762E21ADBE00CA885A /* BackupCDNMetadata.swift */,
|
||||
D9C96F702E21A6B900CA885A /* BackupCDNReadCredential.swift */,
|
||||
C1A0F79C2B9F57340009DC0D /* BackupKeyMaterial.swift */,
|
||||
C1A0F79E2B9F59920009DC0D /* BackupKeyMaterialImpl.swift */,
|
||||
C14391122BD1C0DF00ED6FCB /* BackupRequestManager.swift */,
|
||||
@ -17748,6 +17757,9 @@
|
||||
66734F012CA1ED3F00558494 /* BackupAttachmentUploadScheduler.swift in Sources */,
|
||||
66C7952D2C9B78E900C13937 /* BackupAttachmentUploadStore.swift in Sources */,
|
||||
C18806342BD8080B0024044A /* BackupAuthCredentialManager.swift in Sources */,
|
||||
D9C96F752E21AA5800CA885A /* BackupCDNCache.swift in Sources */,
|
||||
D9C96F772E21ADF000CA885A /* BackupCDNMetadata.swift in Sources */,
|
||||
D9C96F712E21A6BE00CA885A /* BackupCDNReadCredential.swift in Sources */,
|
||||
66A1F4E22E035C020095DE4B /* BackupExportJob.swift in Sources */,
|
||||
D923DF9C2DC135D200CDAFC3 /* BackupIdManager.swift in Sources */,
|
||||
C1A0F79D2B9F57340009DC0D /* BackupKeyMaterial.swift in Sources */,
|
||||
|
||||
138
SignalServiceKit/Backups/BackupCDNCache.swift
Normal file
138
SignalServiceKit/Backups/BackupCDNCache.swift
Normal file
@ -0,0 +1,138 @@
|
||||
//
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
struct BackupCDNCache {
|
||||
private enum Constants {
|
||||
static let cdnMetadataLifetime: TimeInterval = BackupCDNReadCredential.lifetime
|
||||
}
|
||||
|
||||
private let kvStore: KeyValueStore
|
||||
|
||||
init() {
|
||||
self.kvStore = KeyValueStore(collection: "BackupCDNCache")
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
func wipe(tx: DBWriteTransaction) {
|
||||
kvStore.removeAll(transaction: tx)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
private static func backupCDNAuthCredentialKey(
|
||||
cdnNumber: Int32,
|
||||
authType: BackupAuthCredentialType,
|
||||
) -> String {
|
||||
let cdn2 = "BackupCDN2:\(authType.rawValue)"
|
||||
let cdn3 = "BackupCDN3:\(authType.rawValue)"
|
||||
|
||||
switch cdnNumber {
|
||||
case 2:
|
||||
return cdn2
|
||||
case 3:
|
||||
return cdn3
|
||||
default:
|
||||
owsFailDebug("Unexpected CDN number \(cdnNumber): assuming CDN3!")
|
||||
return cdn3
|
||||
}
|
||||
}
|
||||
|
||||
func backupCDNReadCredential(
|
||||
cdnNumber: Int32,
|
||||
authType: BackupAuthCredentialType,
|
||||
now: Date,
|
||||
tx: DBReadTransaction,
|
||||
) -> BackupCDNReadCredential? {
|
||||
do {
|
||||
let cachedCredential: BackupCDNReadCredential? = try kvStore.getCodableValue(
|
||||
forKey: Self.backupCDNAuthCredentialKey(cdnNumber: cdnNumber, authType: authType),
|
||||
transaction: tx
|
||||
)
|
||||
|
||||
if
|
||||
let cachedCredential,
|
||||
!cachedCredential.isExpired(now: now)
|
||||
{
|
||||
return cachedCredential
|
||||
}
|
||||
} catch {
|
||||
Logger.warn("Failed to deserialize BackupCDNReadCredential!")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setBackupCDNReadCredential(
|
||||
_ backupCDNReadCredential: BackupCDNReadCredential,
|
||||
cdnNumber: Int32,
|
||||
authType: BackupAuthCredentialType,
|
||||
tx: DBWriteTransaction,
|
||||
) {
|
||||
do {
|
||||
try kvStore.setCodable(
|
||||
backupCDNReadCredential,
|
||||
key: Self.backupCDNAuthCredentialKey(cdnNumber: cdnNumber, authType: authType),
|
||||
transaction: tx
|
||||
)
|
||||
} catch {
|
||||
Logger.warn("Failed to serialize BackupCDNReadCredential! \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
private static func backupCDNMetadataKeys(authType: BackupAuthCredentialType) -> (
|
||||
metadata: String,
|
||||
metadataSavedDate: String
|
||||
) {
|
||||
return (
|
||||
"BackupCDNMetadata:\(authType.rawValue)",
|
||||
"BackupCDNMetadataSavedDate:\(authType.rawValue)",
|
||||
)
|
||||
}
|
||||
|
||||
func backupCDNMetadata(
|
||||
authType: BackupAuthCredentialType,
|
||||
now: Date,
|
||||
tx: DBReadTransaction,
|
||||
) -> BackupCDNMetadata? {
|
||||
let (metadataKey, metadataSavedDateKey) = Self.backupCDNMetadataKeys(authType: authType)
|
||||
|
||||
if
|
||||
let metadataSavedDate = kvStore.getDate(metadataSavedDateKey, transaction: tx),
|
||||
now > metadataSavedDate.addingTimeInterval(Constants.cdnMetadataLifetime)
|
||||
{
|
||||
// It's been long enough that we should skip the cached value.
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
if let metadata: BackupCDNMetadata = try kvStore.getCodableValue(forKey: metadataKey, transaction: tx) {
|
||||
return metadata
|
||||
}
|
||||
} catch {
|
||||
Logger.warn("Failed to deserialize BackupCDNMetadata! \(error)")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setBackupCDNMetadata(
|
||||
_ backupCDNMetadata: BackupCDNMetadata,
|
||||
authType: BackupAuthCredentialType,
|
||||
dateProvider: DateProvider,
|
||||
tx: DBWriteTransaction,
|
||||
) {
|
||||
let (metadataKey, metadataSavedDateKey) = Self.backupCDNMetadataKeys(authType: authType)
|
||||
|
||||
do {
|
||||
try kvStore.setCodable(backupCDNMetadata, key: metadataKey, transaction: tx)
|
||||
kvStore.setDate(dateProvider(), key: metadataSavedDateKey, transaction: tx)
|
||||
} catch {
|
||||
Logger.warn("Failed to serialize BackupCDNMetadata! \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
28
SignalServiceKit/Backups/BackupCDNMetadata.swift
Normal file
28
SignalServiceKit/Backups/BackupCDNMetadata.swift
Normal file
@ -0,0 +1,28 @@
|
||||
//
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
/// Backup info provided by the server that is cached locally, so this can be discarded and
|
||||
/// refreshed at any time.
|
||||
///
|
||||
/// `cdn`, `backupDir`, and `mediaDir` should be static as long as backup state doesn't
|
||||
/// significantly change (e.g. - changing subscription level, re-enabling backups after the grace period)
|
||||
struct BackupCDNMetadata: Codable, Equatable {
|
||||
/// The CDN type where the message backup is stored. Media may be stored elsewhere.
|
||||
let cdn: Int32
|
||||
|
||||
/// The base directory of your backup data on the cdn. The message backup can befound in the
|
||||
/// returned cdn at /backupDir/backupName and stored media can be found at /backupDir/mediaDir/mediaId
|
||||
let backupDir: String
|
||||
|
||||
/// The prefix path component for media objects on a cdn. Stored media for mediaId
|
||||
/// can be found at /backupDir/mediaDir/mediaId.
|
||||
let mediaDir: String
|
||||
|
||||
/// The name of the most recent message backup on the cdn. The backup is at /backupDir/backupName
|
||||
let backupName: String
|
||||
|
||||
/// The amount of space used to store media
|
||||
let usedSpace: Int64
|
||||
}
|
||||
23
SignalServiceKit/Backups/BackupCDNReadCredential.swift
Normal file
23
SignalServiceKit/Backups/BackupCDNReadCredential.swift
Normal file
@ -0,0 +1,23 @@
|
||||
//
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
struct BackupCDNReadCredential: Codable {
|
||||
static let lifetime: TimeInterval = .day
|
||||
|
||||
let createDate: Date
|
||||
let headers: HttpHeaders
|
||||
|
||||
func isExpired(now: Date) -> Bool {
|
||||
return now > createDate.addingTimeInterval(Self.lifetime)
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.headers = try container.decode(HttpHeaders.self, forKey: .headers)
|
||||
|
||||
// createDate will default to current date, but can be overwritten during decodable initialization
|
||||
self.createDate = try container.decodeIfPresent(Date.self, forKey: .createDate) ?? Date()
|
||||
}
|
||||
}
|
||||
@ -142,38 +142,8 @@ extension BackupRequestManager {
|
||||
|
||||
public struct BackupRequestManagerImpl: BackupRequestManager {
|
||||
|
||||
private enum Constants {
|
||||
static let keyValueStoreCollectionName = "BackupRequestManager"
|
||||
|
||||
static let cdnNumberOfDaysFetchIntervalInSeconds: TimeInterval = .day
|
||||
private static let keyValueStoreCdn2CredentialKey = "Cdn2Credential:"
|
||||
private static let keyValueStoreCdn3CredentialKey = "Cdn3Credential:"
|
||||
|
||||
static func cdnCredentialCacheKey(for cdn: Int32, auth: BackupServiceAuth) -> String {
|
||||
switch cdn {
|
||||
case 2:
|
||||
return Constants.keyValueStoreCdn2CredentialKey + auth.type.rawValue
|
||||
case 3:
|
||||
return Constants.keyValueStoreCdn3CredentialKey + auth.type.rawValue
|
||||
default:
|
||||
owsFailDebug("Invalid CDN version requested")
|
||||
return Constants.keyValueStoreCdn3CredentialKey + auth.type.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
static let backupInfoNumberOfDaysFetchIntervalInSeconds: TimeInterval = .day
|
||||
private static let keyValueStoreBackupInfoKeyPrefix = "BackupInfo:"
|
||||
private static let keyValueStoreLastBackupInfoFetchTimeKeyPrefix = "LastBackupInfoFetchTime:"
|
||||
|
||||
static func backupInfoCacheInfo(for auth: BackupServiceAuth) -> (infoKey: String, lastfetchTimeKey: String) {
|
||||
(
|
||||
keyValueStoreBackupInfoKeyPrefix + auth.type.rawValue,
|
||||
keyValueStoreLastBackupInfoFetchTimeKeyPrefix + auth.type.rawValue
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private let backupAuthCredentialManager: BackupAuthCredentialManager
|
||||
private let backupCDNCache: BackupCDNCache
|
||||
private let backupKeyMaterial: BackupKeyMaterial
|
||||
private let dateProvider: DateProvider
|
||||
private let db: any DB
|
||||
@ -182,16 +152,18 @@ public struct BackupRequestManagerImpl: BackupRequestManager {
|
||||
|
||||
init(
|
||||
backupAuthCredentialManager: BackupAuthCredentialManager,
|
||||
backupCDNCache: BackupCDNCache,
|
||||
backupKeyMaterial: BackupKeyMaterial,
|
||||
dateProvider: @escaping DateProvider,
|
||||
db: any DB,
|
||||
networkManager: NetworkManager
|
||||
) {
|
||||
self.backupAuthCredentialManager = backupAuthCredentialManager
|
||||
self.backupCDNCache = backupCDNCache
|
||||
self.backupKeyMaterial = backupKeyMaterial
|
||||
self.dateProvider = dateProvider
|
||||
self.db = db
|
||||
self.kvStore = KeyValueStore(collection: Constants.keyValueStoreCollectionName)
|
||||
self.kvStore = KeyValueStore(collection: "BackupRequestManager")
|
||||
self.networkManager = networkManager
|
||||
}
|
||||
|
||||
@ -247,82 +219,32 @@ public struct BackupRequestManagerImpl: BackupRequestManager {
|
||||
|
||||
// MARK: - Backup Info
|
||||
|
||||
/// Backup info provided by the server that is cached locally, so this can be discarded and
|
||||
/// refreshed at any time.
|
||||
///
|
||||
/// `cdn`, `backupDir`, and `mediaDir` should be static as long as backup state doesn't
|
||||
/// significantly change (e.g. - changing subscription level, re-enabling backups after the grace period)
|
||||
fileprivate struct BackupRemoteInfo: Codable, Equatable {
|
||||
/// The CDN type where the message backup is stored. Media may be stored elsewhere.
|
||||
public let cdn: Int32
|
||||
|
||||
/// The base directory of your backup data on the cdn. The message backup can befound in the
|
||||
/// returned cdn at /backupDir/backupName and stored media can be found at /backupDir/mediaDir/mediaId
|
||||
public let backupDir: String
|
||||
|
||||
/// The prefix path component for media objects on a cdn. Stored media for mediaId
|
||||
/// can be found at /backupDir/mediaDir/mediaId.
|
||||
public let mediaDir: String
|
||||
|
||||
/// The name of the most recent message backup on the cdn. The backup is at /backupDir/backupName
|
||||
public let backupName: String
|
||||
|
||||
/// The amount of space used to store media
|
||||
let usedSpace: Int64
|
||||
}
|
||||
|
||||
/// Fetch details about the current backup
|
||||
private func fetchBackupInfo(auth: BackupServiceAuth) async throws -> BackupRemoteInfo {
|
||||
let cacheInfo = Constants.backupInfoCacheInfo(for: auth)
|
||||
let cachedBackupInfo = db.read { tx -> BackupRemoteInfo? in
|
||||
let lastInfoFetchTime = kvStore.getDate(
|
||||
cacheInfo.lastfetchTimeKey,
|
||||
transaction: tx
|
||||
) ?? .distantPast
|
||||
|
||||
// Refresh backup info after 24 hours
|
||||
if abs(lastInfoFetchTime.timeIntervalSinceNow) < Constants.backupInfoNumberOfDaysFetchIntervalInSeconds {
|
||||
do {
|
||||
if let backupInfo: BackupRemoteInfo = try kvStore.getCodableValue(
|
||||
forKey: cacheInfo.infoKey,
|
||||
transaction: tx
|
||||
) {
|
||||
return backupInfo
|
||||
}
|
||||
} catch {
|
||||
// Failure to deserialize this object should be ok since it's simply
|
||||
// a cache of the remote info and can be refetched. But still worth
|
||||
// a log entry in case something results in repeated errors.
|
||||
Logger.debug("Couldn't decode backup info, fetch remotely")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
private func fetchBackupCDNMetadata(auth: BackupServiceAuth) async throws -> BackupCDNMetadata {
|
||||
if let cachedCDNMetadata = db.read(block: { tx in
|
||||
backupCDNCache.backupCDNMetadata(
|
||||
authType: auth.type,
|
||||
now: dateProvider(),
|
||||
tx: tx
|
||||
)
|
||||
}) {
|
||||
return cachedCDNMetadata
|
||||
}
|
||||
|
||||
if let cachedBackupInfo {
|
||||
return cachedBackupInfo
|
||||
}
|
||||
|
||||
let backupInfo: BackupRemoteInfo = try await executeBackupService(
|
||||
let cdnMetadata: BackupCDNMetadata = try await executeBackupService(
|
||||
auth: auth,
|
||||
requestFactory: OWSRequestFactory.backupInfoRequest(auth:)
|
||||
)
|
||||
|
||||
try await db.awaitableWrite { tx in
|
||||
try kvStore.setCodable(
|
||||
backupInfo,
|
||||
key: cacheInfo.infoKey,
|
||||
transaction: tx
|
||||
)
|
||||
|
||||
kvStore.setDate(
|
||||
dateProvider(),
|
||||
key: cacheInfo.lastfetchTimeKey,
|
||||
transaction: tx
|
||||
await db.awaitableWrite { tx in
|
||||
backupCDNCache.setBackupCDNMetadata(
|
||||
cdnMetadata,
|
||||
authType: auth.type,
|
||||
dateProvider: dateProvider,
|
||||
tx: tx
|
||||
)
|
||||
}
|
||||
|
||||
return backupInfo
|
||||
return cdnMetadata
|
||||
}
|
||||
|
||||
// TODO: [Backups] Call this regularly, or move it somewhere it is called regularly
|
||||
@ -340,45 +262,39 @@ public struct BackupRequestManagerImpl: BackupRequestManager {
|
||||
private func fetchCDNReadCredentials(
|
||||
cdn: Int32,
|
||||
auth: BackupServiceAuth
|
||||
) async throws -> CDNReadCredential {
|
||||
let cacheKey = Constants.cdnCredentialCacheKey(for: cdn, auth: auth)
|
||||
let result = db.read { tx -> CDNReadCredential? in
|
||||
do {
|
||||
if
|
||||
let backupAuthCredential: CDNReadCredential = try kvStore.getCodableValue(forKey: cacheKey, transaction: tx),
|
||||
backupAuthCredential.isExpired.negated
|
||||
{
|
||||
return backupAuthCredential
|
||||
}
|
||||
} catch {
|
||||
// Failure to deserialize this object should be ok since the credential
|
||||
// can be refetched. But still worth a log entry in case something
|
||||
// results in repeated errors.
|
||||
Logger.info("Couldn't decode backup info, fetch remotely")
|
||||
}
|
||||
return nil
|
||||
) async throws -> BackupCDNReadCredential {
|
||||
if let cachedCDNReadCredential = db.read(block: { tx in
|
||||
backupCDNCache.backupCDNReadCredential(
|
||||
cdnNumber: cdn,
|
||||
authType: auth.type,
|
||||
now: dateProvider(),
|
||||
tx: tx
|
||||
)
|
||||
}) {
|
||||
return cachedCDNReadCredential
|
||||
}
|
||||
|
||||
if let result {
|
||||
return result
|
||||
}
|
||||
|
||||
let authCredential: CDNReadCredential = try await executeBackupService(
|
||||
let cdnReadCredential: BackupCDNReadCredential = try await executeBackupService(
|
||||
auth: auth,
|
||||
requestFactory: { OWSRequestFactory.fetchCDNCredentials(auth: $0, cdn: cdn) }
|
||||
requestFactory: { OWSRequestFactory.fetchBackupCDNCredentials(auth: $0, cdn: cdn) }
|
||||
)
|
||||
|
||||
try await db.awaitableWrite { tx in
|
||||
try kvStore.setCodable(authCredential, key: cacheKey, transaction: tx)
|
||||
await db.awaitableWrite { tx in
|
||||
backupCDNCache.setBackupCDNReadCredential(
|
||||
cdnReadCredential,
|
||||
cdnNumber: cdn,
|
||||
authType: auth.type,
|
||||
tx: tx
|
||||
)
|
||||
}
|
||||
|
||||
return authCredential
|
||||
return cdnReadCredential
|
||||
}
|
||||
|
||||
public func fetchBackupRequestMetadata(auth: BackupServiceAuth) async throws -> BackupReadCredential {
|
||||
let info = try await fetchBackupInfo(auth: auth)
|
||||
let authCredential = try await fetchCDNReadCredentials(cdn: info.cdn, auth: auth)
|
||||
return BackupReadCredential(credential: authCredential, info: info)
|
||||
let metadata = try await fetchBackupCDNMetadata(auth: auth)
|
||||
let authCredential = try await fetchCDNReadCredentials(cdn: metadata.cdn, auth: auth)
|
||||
return BackupReadCredential(credential: authCredential, metadata: metadata)
|
||||
}
|
||||
|
||||
public func fetchMediaTierCdnRequestMetadata(
|
||||
@ -386,9 +302,9 @@ public struct BackupRequestManagerImpl: BackupRequestManager {
|
||||
auth: BackupServiceAuth
|
||||
) async throws -> MediaTierReadCredential {
|
||||
owsAssertDebug(auth.type == .media)
|
||||
let info = try await fetchBackupInfo(auth: auth)
|
||||
let metadata = try await fetchBackupCDNMetadata(auth: auth)
|
||||
let authCredential = try await fetchCDNReadCredentials(cdn: cdn, auth: auth)
|
||||
return MediaTierReadCredential(cdn: cdn, credential: authCredential, info: info)
|
||||
return MediaTierReadCredential(cdn: cdn, credential: authCredential, metadata: metadata)
|
||||
}
|
||||
|
||||
public func copyToMediaTier(
|
||||
@ -501,43 +417,24 @@ public struct BackupRequestManagerImpl: BackupRequestManager {
|
||||
}
|
||||
}
|
||||
|
||||
private struct CDNReadCredential: Codable {
|
||||
private static let cdnCredentialLifetimeInSeconds: TimeInterval = .day
|
||||
|
||||
let createDate: Date
|
||||
let headers: HttpHeaders
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.headers = try container.decode(HttpHeaders.self, forKey: .headers)
|
||||
|
||||
// createDate will default to current date, but can be overwritten during decodable initialization
|
||||
self.createDate = try container.decodeIfPresent(Date.self, forKey: .createDate) ?? Date()
|
||||
}
|
||||
|
||||
var isExpired: Bool {
|
||||
return abs(createDate.timeIntervalSinceNow) >= CDNReadCredential.cdnCredentialLifetimeInSeconds
|
||||
}
|
||||
}
|
||||
|
||||
public struct MediaTierReadCredential {
|
||||
|
||||
public let cdn: Int32
|
||||
private let credential: CDNReadCredential
|
||||
private let info: BackupRequestManagerImpl.BackupRemoteInfo
|
||||
private let credential: BackupCDNReadCredential
|
||||
private let metadata: BackupCDNMetadata
|
||||
|
||||
fileprivate init(
|
||||
cdn: Int32,
|
||||
credential: CDNReadCredential,
|
||||
info: BackupRequestManagerImpl.BackupRemoteInfo
|
||||
credential: BackupCDNReadCredential,
|
||||
metadata: BackupCDNMetadata,
|
||||
) {
|
||||
self.cdn = cdn
|
||||
self.credential = credential
|
||||
self.info = info
|
||||
self.metadata = metadata
|
||||
}
|
||||
|
||||
var isExpired: Bool {
|
||||
return credential.isExpired
|
||||
return credential.isExpired(now: Date())
|
||||
}
|
||||
|
||||
var cdnAuthHeaders: HttpHeaders {
|
||||
@ -545,29 +442,29 @@ public struct MediaTierReadCredential {
|
||||
}
|
||||
|
||||
func mediaTierUrlPrefix() -> String {
|
||||
return "backups/\(info.backupDir)/\(info.mediaDir)"
|
||||
return "backups/\(metadata.backupDir)/\(metadata.mediaDir)"
|
||||
}
|
||||
}
|
||||
|
||||
public struct BackupReadCredential {
|
||||
|
||||
private let credential: CDNReadCredential
|
||||
private let info: BackupRequestManagerImpl.BackupRemoteInfo
|
||||
private let credential: BackupCDNReadCredential
|
||||
private let metadata: BackupCDNMetadata
|
||||
|
||||
fileprivate init(
|
||||
credential: CDNReadCredential,
|
||||
info: BackupRequestManagerImpl.BackupRemoteInfo
|
||||
credential: BackupCDNReadCredential,
|
||||
metadata: BackupCDNMetadata
|
||||
) {
|
||||
self.credential = credential
|
||||
self.info = info
|
||||
self.metadata = metadata
|
||||
}
|
||||
|
||||
var isExpired: Bool {
|
||||
return credential.isExpired
|
||||
return credential.isExpired(now: Date())
|
||||
}
|
||||
|
||||
var cdn: Int32 {
|
||||
return info.cdn
|
||||
return metadata.cdn
|
||||
}
|
||||
|
||||
var cdnAuthHeaders: HttpHeaders {
|
||||
@ -575,6 +472,6 @@ public struct BackupReadCredential {
|
||||
}
|
||||
|
||||
func backupLocationUrl() -> String {
|
||||
return "backups/\(info.backupDir)/\(info.backupName)"
|
||||
return "backups/\(metadata.backupDir)/\(metadata.backupName)"
|
||||
}
|
||||
}
|
||||
|
||||
@ -369,6 +369,7 @@ public class AppSetup {
|
||||
db: db,
|
||||
networkManager: networkManager
|
||||
),
|
||||
backupCDNCache: BackupCDNCache(),
|
||||
backupKeyMaterial: backupKeyMaterial,
|
||||
dateProvider: dateProvider,
|
||||
db: db,
|
||||
|
||||
@ -60,7 +60,7 @@ extension OWSRequestFactory {
|
||||
return request
|
||||
}
|
||||
|
||||
public static func fetchCDNCredentials(auth: BackupServiceAuth, cdn: Int32) -> TSRequest {
|
||||
public static func fetchBackupCDNCredentials(auth: BackupServiceAuth, cdn: Int32) -> TSRequest {
|
||||
var request = TSRequest(
|
||||
url: URL(string: "v1/archives/auth/read?cdn=\(cdn)")!,
|
||||
method: "GET",
|
||||
|
||||
@ -9,14 +9,14 @@ import LibSignalClient
|
||||
class AuthCredentialStore {
|
||||
private let callLinkAuthCredentialStore: KeyValueStore
|
||||
private let groupAuthCredentialStore: KeyValueStore
|
||||
private let backupAuthCredentialStore: KeyValueStore
|
||||
private let mediaAuthCredentialStore: KeyValueStore
|
||||
private let backupMessagesAuthCredentialStore: KeyValueStore
|
||||
private let backupMediaAuthCredentialStore: KeyValueStore
|
||||
|
||||
init() {
|
||||
self.callLinkAuthCredentialStore = KeyValueStore(collection: "CallLinkAuthCredential")
|
||||
self.groupAuthCredentialStore = KeyValueStore(collection: "GroupsV2Impl.authCredentialStoreStore")
|
||||
self.backupAuthCredentialStore = KeyValueStore(collection: "BackupAuthCredential")
|
||||
self.mediaAuthCredentialStore = KeyValueStore(collection: "MediaAuthCredential")
|
||||
self.backupMessagesAuthCredentialStore = KeyValueStore(collection: "BackupAuthCredential")
|
||||
self.backupMediaAuthCredentialStore = KeyValueStore(collection: "MediaAuthCredential")
|
||||
}
|
||||
|
||||
private static func callLinkAuthCredentialKey(for redemptionTime: UInt64) -> String {
|
||||
@ -98,7 +98,11 @@ class AuthCredentialStore {
|
||||
redemptionTime: UInt64,
|
||||
tx: DBReadTransaction
|
||||
) -> BackupAuthCredential? {
|
||||
let store = credentialType == .messages ? backupAuthCredentialStore : mediaAuthCredentialStore
|
||||
let store: KeyValueStore = switch credentialType {
|
||||
case .media: backupMediaAuthCredentialStore
|
||||
case .messages: backupMessagesAuthCredentialStore
|
||||
}
|
||||
|
||||
do {
|
||||
return try store.getData(
|
||||
Self.backupAuthCredentialKey(for: redemptionTime),
|
||||
@ -118,7 +122,11 @@ class AuthCredentialStore {
|
||||
redemptionTime: UInt64,
|
||||
tx: DBWriteTransaction
|
||||
) {
|
||||
let store = credentialType == .messages ? backupAuthCredentialStore : mediaAuthCredentialStore
|
||||
let store: KeyValueStore = switch credentialType {
|
||||
case .media: backupMediaAuthCredentialStore
|
||||
case .messages: backupMessagesAuthCredentialStore
|
||||
}
|
||||
|
||||
store.setData(
|
||||
credential.serialize(),
|
||||
key: Self.backupAuthCredentialKey(for: redemptionTime),
|
||||
@ -127,7 +135,11 @@ class AuthCredentialStore {
|
||||
}
|
||||
|
||||
func removeAllBackupAuthCredentials(ofType credentialType: BackupAuthCredentialType, tx: DBWriteTransaction) {
|
||||
let store = credentialType == .messages ? backupAuthCredentialStore : mediaAuthCredentialStore
|
||||
let store: KeyValueStore = switch credentialType {
|
||||
case .media: backupMediaAuthCredentialStore
|
||||
case .messages: backupMessagesAuthCredentialStore
|
||||
}
|
||||
|
||||
store.removeAll(transaction: tx)
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user