You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
131 lines
3.8 KiB
131 lines
3.8 KiB
package blossom |
|
|
|
import ( |
|
"sync" |
|
"time" |
|
) |
|
|
|
// BandwidthState tracks upload bandwidth for an identity |
|
type BandwidthState struct { |
|
BucketBytes int64 // Current token bucket level (bytes available) |
|
LastUpdate time.Time // Last time bucket was updated |
|
} |
|
|
|
// BandwidthLimiter implements token bucket rate limiting for uploads. |
|
// Each identity gets a bucket that replenishes at dailyLimit/day rate. |
|
// Uploads consume tokens from the bucket. |
|
type BandwidthLimiter struct { |
|
mu sync.Mutex |
|
states map[string]*BandwidthState // keyed by pubkey hex or IP |
|
dailyLimit int64 // bytes per day |
|
burstLimit int64 // max bucket size (burst capacity) |
|
refillRate float64 // bytes per second refill rate |
|
} |
|
|
|
// NewBandwidthLimiter creates a new bandwidth limiter. |
|
// dailyLimitMB is the average daily limit in megabytes. |
|
// burstLimitMB is the maximum burst capacity in megabytes. |
|
func NewBandwidthLimiter(dailyLimitMB, burstLimitMB int64) *BandwidthLimiter { |
|
dailyBytes := dailyLimitMB * 1024 * 1024 |
|
burstBytes := burstLimitMB * 1024 * 1024 |
|
|
|
return &BandwidthLimiter{ |
|
states: make(map[string]*BandwidthState), |
|
dailyLimit: dailyBytes, |
|
burstLimit: burstBytes, |
|
refillRate: float64(dailyBytes) / 86400.0, // bytes per second |
|
} |
|
} |
|
|
|
// CheckAndConsume checks if an upload of the given size is allowed for the identity, |
|
// and if so, consumes the tokens. Returns true if allowed, false if rate limited. |
|
// The identity should be pubkey hex for authenticated users, or IP for anonymous. |
|
func (bl *BandwidthLimiter) CheckAndConsume(identity string, sizeBytes int64) bool { |
|
bl.mu.Lock() |
|
defer bl.mu.Unlock() |
|
|
|
now := time.Now() |
|
state, exists := bl.states[identity] |
|
|
|
if !exists { |
|
// New identity starts with full burst capacity |
|
state = &BandwidthState{ |
|
BucketBytes: bl.burstLimit, |
|
LastUpdate: now, |
|
} |
|
bl.states[identity] = state |
|
} else { |
|
// Refill bucket based on elapsed time |
|
elapsed := now.Sub(state.LastUpdate).Seconds() |
|
refill := int64(elapsed * bl.refillRate) |
|
state.BucketBytes += refill |
|
if state.BucketBytes > bl.burstLimit { |
|
state.BucketBytes = bl.burstLimit |
|
} |
|
state.LastUpdate = now |
|
} |
|
|
|
// Check if upload fits in bucket |
|
if state.BucketBytes >= sizeBytes { |
|
state.BucketBytes -= sizeBytes |
|
return true |
|
} |
|
|
|
return false |
|
} |
|
|
|
// GetAvailable returns the currently available bytes for an identity. |
|
func (bl *BandwidthLimiter) GetAvailable(identity string) int64 { |
|
bl.mu.Lock() |
|
defer bl.mu.Unlock() |
|
|
|
state, exists := bl.states[identity] |
|
if !exists { |
|
return bl.burstLimit // New users have full capacity |
|
} |
|
|
|
// Calculate current level with refill |
|
now := time.Now() |
|
elapsed := now.Sub(state.LastUpdate).Seconds() |
|
refill := int64(elapsed * bl.refillRate) |
|
available := state.BucketBytes + refill |
|
if available > bl.burstLimit { |
|
available = bl.burstLimit |
|
} |
|
|
|
return available |
|
} |
|
|
|
// GetTimeUntilAvailable returns how long until the given bytes will be available. |
|
func (bl *BandwidthLimiter) GetTimeUntilAvailable(identity string, sizeBytes int64) time.Duration { |
|
available := bl.GetAvailable(identity) |
|
if available >= sizeBytes { |
|
return 0 |
|
} |
|
|
|
needed := sizeBytes - available |
|
seconds := float64(needed) / bl.refillRate |
|
return time.Duration(seconds * float64(time.Second)) |
|
} |
|
|
|
// Cleanup removes entries that have fully replenished (at burst limit). |
|
func (bl *BandwidthLimiter) Cleanup() { |
|
bl.mu.Lock() |
|
defer bl.mu.Unlock() |
|
|
|
now := time.Now() |
|
for key, state := range bl.states { |
|
elapsed := now.Sub(state.LastUpdate).Seconds() |
|
refill := int64(elapsed * bl.refillRate) |
|
if state.BucketBytes+refill >= bl.burstLimit { |
|
delete(bl.states, key) |
|
} |
|
} |
|
} |
|
|
|
// Stats returns the number of tracked identities. |
|
func (bl *BandwidthLimiter) Stats() int { |
|
bl.mu.Lock() |
|
defer bl.mu.Unlock() |
|
return len(bl.states) |
|
}
|
|
|