mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 12:25:19 +00:00
78 lines
2.2 KiB
Swift
78 lines
2.2 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
|
|
}
|
|
|
|
mutating func allow(senderKey: String, contentKey: String, now: Date = Date()) -> Bool {
|
|
var senderBucket = senderBuckets[senderKey] ?? TokenBucket(
|
|
capacity: senderCapacity,
|
|
tokens: senderCapacity,
|
|
refillPerSec: senderRefill,
|
|
lastRefill: now
|
|
)
|
|
let 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
|
|
}
|
|
|
|
mutating func reset() {
|
|
senderBuckets.removeAll()
|
|
contentBuckets.removeAll()
|
|
}
|
|
}
|