mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 17:25:20 +00:00
Periphery 3.7.4 audit of both schemes (macOS + iOS, intersected so platform-specific code is never touched), with test targets indexed and the share extension built. 277 dead declarations removed or demoted: dead forwarding wrappers (ChatViewModel+Nostr/+PrivateChat), removed- feature remnants (autocomplete command suggestions, back-swipe tuning, MediaSendError, GeohashParticipantTracker), unused Tor dormancy bindings, assign-only properties, unused parameters (renamed to _), and redundant public accessibility. 13 orphaned localization keys deleted across all 29 locales (old pre-#1392 location-notes UI, app_info warnings). Two real tests were flagged as unused because they never ran: Swift Testing methods missing @Test (NostrProtocolTests. testAckRoundTripNIP44V2_Delivered, NotificationStreamAssemblerTests. testAssemblesCompressedLargeFrame). Re-armed both; they pass. Deliberately kept, now recorded in .periphery.baseline.json: iOS-only code invisible to the CI macOS scan, C FFI signatures, keep-alive NWPathMonitor reference, InboundEventKey.eventID (dedup semantics), wifiBulk capability bit (reserved for Wi-Fi bulk work, used by BitFoundation package tests), and the String secureClear cluster (exercised by package tests). New: .periphery.yml config and an advisory Dead Code CI job (mirrors the SwiftLint precedent from #1361) that fails on findings not in the committed baseline. Verified: full macOS app suite, BitFoundation (119) and BitLogger (13) package tests green; periphery scan --strict exits clean. Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
83 lines
2.7 KiB
Swift
83 lines
2.7 KiB
Swift
//
|
|
// MessageRateLimiter.swift
|
|
// bitchat
|
|
//
|
|
// Handles per-sender and per-content token buckets for public message intake.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
struct MessageRateLimiter {
|
|
private struct TokenBucket {
|
|
var capacity: Double
|
|
var tokens: Double
|
|
var refillPerSec: Double
|
|
var lastRefill: Date
|
|
|
|
mutating func allow(cost: Double = 1.0, now: Date = Date()) -> Bool {
|
|
let dt = now.timeIntervalSince(lastRefill)
|
|
if dt > 0 {
|
|
tokens = min(capacity, tokens + dt * refillPerSec)
|
|
lastRefill = now
|
|
}
|
|
if tokens >= cost {
|
|
tokens -= cost
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
private var senderBuckets: [String: TokenBucket] = [:]
|
|
private var contentBuckets: [String: TokenBucket] = [:]
|
|
|
|
private let senderCapacity: Double
|
|
private let senderRefill: Double
|
|
private let contentCapacity: Double
|
|
private let contentRefill: Double
|
|
|
|
init(
|
|
senderCapacity: Double,
|
|
senderRefillPerSec: Double,
|
|
contentCapacity: Double,
|
|
contentRefillPerSec: Double
|
|
) {
|
|
self.senderCapacity = senderCapacity
|
|
self.senderRefill = senderRefillPerSec
|
|
self.contentCapacity = contentCapacity
|
|
self.contentRefill = contentRefillPerSec
|
|
}
|
|
|
|
/// - Parameter powBits: validated NIP-13 difficulty of the event
|
|
/// (`NostrPoW.validatedDifficulty`; 0 for mesh or no-PoW events).
|
|
/// At or above `NostrPoW.rateLimitBypassBits` the per-sender bucket is
|
|
/// skipped entirely — each such message paid for itself with work — but
|
|
/// the per-content flood bucket still applies.
|
|
mutating func allow(senderKey: String, contentKey: String, powBits: Int = 0, now: Date = Date()) -> Bool {
|
|
let senderAllowed: Bool
|
|
if powBits >= NostrPoW.rateLimitBypassBits {
|
|
senderAllowed = true
|
|
} else {
|
|
var senderBucket = senderBuckets[senderKey] ?? TokenBucket(
|
|
capacity: senderCapacity,
|
|
tokens: senderCapacity,
|
|
refillPerSec: senderRefill,
|
|
lastRefill: now
|
|
)
|
|
senderAllowed = senderBucket.allow(now: now)
|
|
senderBuckets[senderKey] = senderBucket
|
|
}
|
|
|
|
var contentBucket = contentBuckets[contentKey] ?? TokenBucket(
|
|
capacity: contentCapacity,
|
|
tokens: contentCapacity,
|
|
refillPerSec: contentRefill,
|
|
lastRefill: now
|
|
)
|
|
let contentAllowed = contentBucket.allow(now: now)
|
|
contentBuckets[contentKey] = contentBucket
|
|
|
|
return senderAllowed && contentAllowed
|
|
}
|
|
}
|