// // CourierStoreTests.swift // bitchat // // This is free and unencumbered software released into the public domain. // For more information, see // import Testing import Foundation import BitFoundation @testable import bitchat struct CourierStoreTests { private static let baseDate = Date(timeIntervalSince1970: 1_750_000_000) private func makeStore(now: Date = baseDate) -> CourierStore { CourierStore(persistsToDisk: false, now: { now }) } /// Store whose clock can be advanced by tests. private final class Clock { var now: Date init(_ now: Date) { self.now = now } } private func makeEnvelope( recipientKey: Data = Data(repeating: 0xB0, count: 32), sealedAt: Date = baseDate, lifetime: TimeInterval = 60 * 60, ciphertext: Data = Data((0..<96).map { _ in UInt8.random(in: 0...255) }) ) -> CourierEnvelope { CourierEnvelope( recipientTag: CourierEnvelope.recipientTag( noiseStaticKey: recipientKey, epochDay: CourierEnvelope.epochDay(for: sealedAt) ), expiry: UInt64((sealedAt.timeIntervalSince1970 + lifetime) * 1000), ciphertext: ciphertext ) } private let depositorA = Data(repeating: 0xA1, count: 32) private let depositorB = Data(repeating: 0xA2, count: 32) // MARK: - Deposit and handover @Test func depositThenTakeForRecipient() { let store = makeStore() let recipientKey = Data(repeating: 0xB0, count: 32) let envelope = makeEnvelope(recipientKey: recipientKey) #expect(store.deposit(envelope, from: depositorA)) let taken = store.takeEnvelopes(for: recipientKey) #expect(taken == [envelope]) // Handover removes the envelope. #expect(store.takeEnvelopes(for: recipientKey).isEmpty) } @Test func takeIgnoresOtherRecipients() { let store = makeStore() let envelope = makeEnvelope(recipientKey: Data(repeating: 0xB0, count: 32)) store.deposit(envelope, from: depositorA) #expect(store.takeEnvelopes(for: Data(repeating: 0xCC, count: 32)).isEmpty) #expect(store.takeEnvelopes(for: Data(repeating: 0xB0, count: 32)).count == 1) } @Test func duplicateDepositIsIdempotent() { let store = makeStore() let recipientKey = Data(repeating: 0xB0, count: 32) let envelope = makeEnvelope(recipientKey: recipientKey) #expect(store.deposit(envelope, from: depositorA)) #expect(store.deposit(envelope, from: depositorA)) #expect(store.takeEnvelopes(for: recipientKey).count == 1) } // MARK: - Validity @Test func rejectsExpiredAndOversizedAndMalformed() { let store = makeStore() let expired = makeEnvelope(sealedAt: Self.baseDate.addingTimeInterval(-7200), lifetime: 3600) #expect(!store.deposit(expired, from: depositorA)) let oversized = makeEnvelope(ciphertext: Data(repeating: 0, count: CourierEnvelope.maxCiphertextBytes + 1)) #expect(!store.deposit(oversized, from: depositorA)) let badTag = CourierEnvelope( recipientTag: Data(repeating: 0, count: 4), expiry: UInt64((Self.baseDate.timeIntervalSince1970 + 3600) * 1000), ciphertext: Data(repeating: 1, count: 16) ) #expect(!store.deposit(badTag, from: depositorA)) } @Test func rejectsExpiryBeyondPolicyLifetime() { let store = makeStore() let pinned = makeEnvelope(lifetime: 7 * 24 * 60 * 60) #expect(!store.deposit(pinned, from: depositorA)) } // MARK: - Quotas @Test func perDepositorQuota() { let store = makeStore() for _ in 0.. give 1, keep 1). let courierY = Data(repeating: 0xC2, count: 32) let sprayedToY = store.takeSprayCopies(for: courierY) #expect(sprayedToY.count == 1) #expect(sprayedToY.first?.copies == 1) // Budget exhausted (carry-only): nothing left to spray. #expect(store.takeSprayCopies(for: Data(repeating: 0xC3, count: 32)).isEmpty) // The carried original is still deliverable. #expect(store.takeEnvelopes(for: recipientKey).count == 1) } @Test func carryOnlyEnvelopesAreNeverSprayed() { let store = makeStore() #expect(store.deposit(makeEnvelope(), from: depositorA)) #expect(store.takeSprayCopies(for: Data(repeating: 0xC1, count: 32)).isEmpty) } @Test func duplicateDepositKeepsLargerSprayBudget() { let store = makeStore() let recipientKey = Data(repeating: 0xB0, count: 32) let ciphertext = Data(repeating: 0x42, count: 96) let carryOnly = makeEnvelope(recipientKey: recipientKey, ciphertext: ciphertext) #expect(store.deposit(carryOnly, from: depositorA)) #expect(store.deposit(carryOnly.withCopies(4), from: depositorB)) let sprayed = store.takeSprayCopies(for: Data(repeating: 0xC1, count: 32)) #expect(sprayed.first?.copies == 2) } // MARK: - Remote handover (relayed announces) @Test func remoteHandoverIsNonDestructiveAndCooledDown() { let store = makeStore() let recipientKey = Data(repeating: 0xB0, count: 32) let envelope = makeEnvelope(recipientKey: recipientKey).withCopies(4) #expect(store.deposit(envelope, from: depositorA)) let first = store.envelopesForRemoteHandover(recipientNoiseKey: recipientKey, cooldown: 600) #expect(first.count == 1) // The flooded copy carries no spray budget. #expect(first.first?.copies == 1) // Non-destructive: the envelope is still carried... #expect(!store.isEmpty) // ...and inside the cooldown it is not re-flooded. #expect(store.envelopesForRemoteHandover(recipientNoiseKey: recipientKey, cooldown: 600).isEmpty) // A direct encounter still hands it over destructively. #expect(store.takeEnvelopes(for: recipientKey).count == 1) #expect(store.isEmpty) } // MARK: - Legacy persistence @Test func legacyPersistedFileLoadsAsFavoriteCarryOnly() throws { let fileURL = FileManager.default.temporaryDirectory .appendingPathComponent("courier-legacy-\(UUID().uuidString).json") defer { try? FileManager.default.removeItem(at: fileURL) } // Envelope persisted by a pre-tier/pre-spray build: no tier, copies, // or spray bookkeeping fields. let recipientKey = Data(repeating: 0xB0, count: 32) let envelope = makeEnvelope(recipientKey: recipientKey) let legacy: [[String: Any]] = [[ "recipientTag": envelope.recipientTag.base64EncodedString(), "expiry": envelope.expiry, "ciphertext": envelope.ciphertext.base64EncodedString(), "depositorNoiseKey": depositorA.base64EncodedString(), "storedAt": Self.baseDate.timeIntervalSinceReferenceDate ]] let data = try JSONSerialization.data(withJSONObject: legacy) try data.write(to: fileURL) let store = CourierStore(persistsToDisk: true, fileURL: fileURL, now: { Self.baseDate }) // Carry-only, so never sprayed... #expect(store.takeSprayCopies(for: Data(repeating: 0xC1, count: 32)).isEmpty) // ...but still delivered on encounter. #expect(store.takeEnvelopes(for: recipientKey).count == 1) } }