feat: import Chinese-localized Buzz source snapshot
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Signed-off-by: cls_宁波本机 <908705107@qq.com>
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
use buzz_auth::{LimitType, RateLimiter};
|
||||
use buzz_core::TenantContext;
|
||||
use nostr::PublicKey;
|
||||
|
||||
// Desktop startup establishes several independent live subscriptions at once.
|
||||
// Preserve the configured average rate while allowing that bounded burst. This
|
||||
// is still a fixed-window limiter, so a Redis-backed token bucket would be a
|
||||
// better long-term fit for smoother refill behavior.
|
||||
const WS_BURST_WINDOW_SECS: u64 = 5;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum AdmissionError {
|
||||
Exceeded { reset_in_secs: u64 },
|
||||
Unavailable,
|
||||
}
|
||||
|
||||
pub(crate) async fn check_principal<L: RateLimiter>(
|
||||
limiter: &L,
|
||||
tenant: &TenantContext,
|
||||
pubkey: &PublicKey,
|
||||
limit_type: LimitType,
|
||||
window_secs: u64,
|
||||
limit: u64,
|
||||
) -> Result<(), AdmissionError> {
|
||||
match limiter
|
||||
.check_and_increment(tenant, pubkey, limit_type, window_secs, limit)
|
||||
.await
|
||||
{
|
||||
Ok(result) if result.allowed => Ok(()),
|
||||
Ok(result) => Err(AdmissionError::Exceeded {
|
||||
reset_in_secs: result.reset_in_secs,
|
||||
}),
|
||||
Err(error) => {
|
||||
tracing::warn!(error = %error, "shared rate-limit admission unavailable");
|
||||
Err(AdmissionError::Unavailable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ws_admission_budget(per_second_limit: u64) -> (u64, u64) {
|
||||
(
|
||||
WS_BURST_WINDOW_SECS,
|
||||
per_second_limit.saturating_mul(WS_BURST_WINDOW_SECS),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::net::IpAddr;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use buzz_auth::{AuthError, RateLimitResult, RateLimiter};
|
||||
use buzz_core::CommunityId;
|
||||
use nostr::Keys;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::*;
|
||||
|
||||
enum StubOutcome {
|
||||
Denied,
|
||||
Failed,
|
||||
}
|
||||
|
||||
struct StubLimiter {
|
||||
outcome: StubOutcome,
|
||||
calls: AtomicUsize,
|
||||
}
|
||||
|
||||
impl RateLimiter for StubLimiter {
|
||||
async fn check_and_increment(
|
||||
&self,
|
||||
_ctx: &TenantContext,
|
||||
_pubkey: &PublicKey,
|
||||
_limit_type: LimitType,
|
||||
_window_secs: u64,
|
||||
_limit: u64,
|
||||
) -> Result<RateLimitResult, AuthError> {
|
||||
self.calls.fetch_add(1, Ordering::Relaxed);
|
||||
match self.outcome {
|
||||
StubOutcome::Denied => Ok(RateLimitResult::denied(11, 10, 1)),
|
||||
StubOutcome::Failed => Err(AuthError::Internal("redis unavailable".to_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
async fn check_ip_connection(
|
||||
&self,
|
||||
_ip: &IpAddr,
|
||||
_window_secs: u64,
|
||||
_limit: u64,
|
||||
) -> Result<RateLimitResult, AuthError> {
|
||||
match self.outcome {
|
||||
StubOutcome::Denied => Ok(RateLimitResult::denied(11, 10, 1)),
|
||||
StubOutcome::Failed => Err(AuthError::Internal("redis unavailable".to_owned())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tenant() -> TenantContext {
|
||||
TenantContext::resolved(
|
||||
CommunityId::from_uuid(Uuid::from_u128(1)),
|
||||
"relay.example.com",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn websocket_budget_preserves_rate_with_a_bounded_burst() {
|
||||
assert_eq!(ws_admission_budget(10), (5, 50));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn websocket_budget_saturates_on_overflow() {
|
||||
assert_eq!(ws_admission_budget(u64::MAX), (5, u64::MAX));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn denied_shared_counter_rejects_admission() {
|
||||
let limiter = StubLimiter {
|
||||
outcome: StubOutcome::Denied,
|
||||
calls: AtomicUsize::new(0),
|
||||
};
|
||||
let keys = Keys::generate();
|
||||
|
||||
let result = check_principal(
|
||||
&limiter,
|
||||
&tenant(),
|
||||
&keys.public_key(),
|
||||
LimitType::WsEvents,
|
||||
1,
|
||||
10,
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(result, Err(AdmissionError::Exceeded { reset_in_secs: 1 }));
|
||||
assert_eq!(limiter.calls.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn shared_counter_failure_rejects_admission() {
|
||||
let limiter = StubLimiter {
|
||||
outcome: StubOutcome::Failed,
|
||||
calls: AtomicUsize::new(0),
|
||||
};
|
||||
let keys = Keys::generate();
|
||||
|
||||
let result = check_principal(
|
||||
&limiter,
|
||||
&tenant(),
|
||||
&keys.public_key(),
|
||||
LimitType::ApiCalls,
|
||||
60,
|
||||
300,
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(result, Err(AdmissionError::Unavailable));
|
||||
assert_eq!(limiter.calls.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user