// Copyright 2021-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Adapted from server/rate_counter.go in the NATS server Go source.
namespace ZB.MOM.NatsNet.Server.Internal;
///
/// A sliding-window rate limiter that allows at most limit events
/// per window.
/// Mirrors rateCounter in server/rate_counter.go.
///
public sealed class RateCounter
{
private readonly long _limit;
private long _count;
private ulong _blocked;
private DateTime _end;
// Exposed for tests (mirrors direct field access in rate_counter_test.go).
public TimeSpan Interval;
private readonly object _lock = new();
public RateCounter(long limit)
{
_limit = limit;
Interval = TimeSpan.FromSeconds(1);
}
///
/// Factory wrapper for Go parity.
/// Mirrors newRateCounter.
///
public static RateCounter NewRateCounter(long limit) => new(limit);
///
/// Returns true if the event is within the rate limit for the current window.
/// Mirrors rateCounter.allow.
///
public bool Allow()
{
var now = DateTime.UtcNow;
lock (_lock)
{
if (now > _end)
{
_count = 0;
_end = now + Interval;
}
else
{
_count++;
}
var allow = _count < _limit;
if (!allow)
_blocked++;
return allow;
}
}
///
/// Returns and resets the count of blocked events since the last call.
/// Mirrors rateCounter.countBlocked.
///
public ulong CountBlocked()
{
lock (_lock)
{
var blocked = _blocked;
_blocked = 0;
return blocked;
}
}
}