Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions bitchat/Utils/MessageDeduplicator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ final class MessageDeduplicator {
private let lock = NSLock()
private let maxAge: TimeInterval
private let maxCount: Int
private let dateProvider: () -> Date

/// Initialize with default config from TransportConfig
convenience init() {
Expand All @@ -26,9 +27,14 @@ final class MessageDeduplicator {
}

/// Initialize with custom config for content deduplication
init(maxAge: TimeInterval, maxCount: Int) {
init(
maxAge: TimeInterval,
maxCount: Int,
dateProvider: @escaping () -> Date = Date.init
) {
self.maxAge = maxAge
self.maxCount = maxCount
self.dateProvider = dateProvider
}

/// Check if message is duplicate and add if not.
Expand All @@ -38,7 +44,7 @@ final class MessageDeduplicator {
lock.lock()
defer { lock.unlock() }

let now = Date()
let now = dateProvider()
cleanupOldEntries(before: now.addingTimeInterval(-maxAge))

if lookup[id] != nil {
Expand All @@ -57,10 +63,13 @@ final class MessageDeduplicator {
lock.lock()
defer { lock.unlock() }

let now = dateProvider()
cleanupOldEntries(before: now.addingTimeInterval(-maxAge))

if lookup[id] == nil {
let now = Date()
entries.append(Entry(id: id, timestamp: now))
lookup[id] = now
trimIfNeeded()
}
}

Expand Down Expand Up @@ -106,7 +115,7 @@ final class MessageDeduplicator {
lock.lock()
defer { lock.unlock() }

cleanupOldEntries(before: Date().addingTimeInterval(-maxAge))
cleanupOldEntries(before: dateProvider().addingTimeInterval(-maxAge))

// Shrink capacity if significantly oversized
if entries.capacity > maxCount * 2 && entries.count < maxCount {
Expand Down
54 changes: 54 additions & 0 deletions bitchatTests/MessageDeduplicatorTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// MessageDeduplicatorTests.swift
// bitchatTests
//
// Tests for MessageDeduplicator.
// This is free and unencumbered software released into the public domain.
//

import Foundation
import Testing
@testable import bitchat

@Suite("Message Deduplicator")
struct MessageDeduplicatorTests {
@Test func markProcessed_enforcesMaximumCount() {
let now = Date(timeIntervalSince1970: 1_000)
let deduplicator = MessageDeduplicator(
maxAge: 300,
maxCount: 4,
dateProvider: { now }
)

for id in ["a", "b", "c", "d", "e"] {
deduplicator.markProcessed(id)
}

#expect(!deduplicator.contains("a"))
#expect(!deduplicator.contains("b"))
#expect(deduplicator.contains("c"))
#expect(deduplicator.contains("d"))
#expect(deduplicator.contains("e"))
}

@Test func markProcessed_cleansExpiredEntriesBeforeCountTrim() {
var now = Date(timeIntervalSince1970: 1_000)
let deduplicator = MessageDeduplicator(
maxAge: 300,
maxCount: 4,
dateProvider: { now }
)

deduplicator.markProcessed("expired")
now.addTimeInterval(301)

for id in ["b", "c", "d", "e"] {
deduplicator.markProcessed(id)
}

#expect(deduplicator.contains("b"))
#expect(deduplicator.contains("c"))
#expect(deduplicator.contains("d"))
#expect(deduplicator.contains("e"))
}
}