Path: blob/main/SignalServiceKit/Backups/BackupCDNCredentialStore.swift
1 views
//
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
public struct BackupCDNCredentialStore {
private enum Constants {
static let cdnMetadataLifetime: TimeInterval = BackupCDNReadCredential.lifetime
}
private let kvStore: KeyValueStore
public init() {
self.kvStore = KeyValueStore(collection: "BackupCDNCredentialStore")
}
// MARK: -
public func wipe(tx: DBWriteTransaction) {
kvStore.removeAll(transaction: tx)
}
// MARK: -
private static func backupCDNAuthCredentialKey(
cdnNumber: Int32,
authType: BackupAuthCredentialType,
) -> String {
return "BackupCDN\(cdnNumber):\(authType.rawValue)"
}
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,
currentBackupPlan: BackupPlan,
tx: DBWriteTransaction,
) {
switch currentBackupPlan {
case .disabled:
// This can happen during registration when fetching backup info to download the backup
return
case .disabling, .free, .paid, .paidExpiringSoon, .paidAsTester:
break
}
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,
now: Date,
currentBackupPlan: BackupPlan,
tx: DBWriteTransaction,
) {
switch currentBackupPlan {
case .disabled:
// This can happen during registration when fetching backup info to download the backup
return
case .disabling, .free, .paid, .paidExpiringSoon, .paidAsTester:
break
}
let (metadataKey, metadataSavedDateKey) = Self.backupCDNMetadataKeys(authType: authType)
do {
try kvStore.setCodable(backupCDNMetadata, key: metadataKey, transaction: tx)
kvStore.setDate(now, key: metadataSavedDateKey, transaction: tx)
} catch {
Logger.warn("Failed to serialize BackupCDNMetadata! \(error)")
}
}
}