Remove an unused rate limiter
This commit is contained in:
parent
f3457502a6
commit
3a1c716c73
|
@ -164,28 +164,4 @@ public class RateLimitsConfiguration {
|
|||
return leakRatePerMinute;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CardinalityRateLimitConfiguration {
|
||||
@JsonProperty
|
||||
private int maxCardinality;
|
||||
|
||||
@JsonProperty
|
||||
private Duration ttl;
|
||||
|
||||
public CardinalityRateLimitConfiguration() {
|
||||
}
|
||||
|
||||
public CardinalityRateLimitConfiguration(int maxCardinality, Duration ttl) {
|
||||
this.maxCardinality = maxCardinality;
|
||||
this.ttl = ttl;
|
||||
}
|
||||
|
||||
public int getMaxCardinality() {
|
||||
return maxCardinality;
|
||||
}
|
||||
|
||||
public Duration getTtl() {
|
||||
return ttl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
package org.whispersystems.textsecuregcm.configuration.dynamic;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration.CardinalityRateLimitConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration.RateLimitConfiguration;
|
||||
import java.time.Duration;
|
||||
|
||||
public class DynamicRateLimitsConfiguration {
|
||||
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.limits;
|
||||
|
||||
import java.time.Duration;
|
||||
import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration.CardinalityRateLimitConfiguration;
|
||||
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
|
||||
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
|
||||
|
||||
/**
|
||||
* A cardinality rate limiter prevents an actor from taking some action if that actor has attempted to take that action
|
||||
* on too many targets in a fixed period of time. Behind the scenes, we estimate the target count using a
|
||||
* hyper-log-log data structure; as a consequence, the number of targets is an approximation, and this rate limiter
|
||||
* should not be used in cases where precise time or target limits are required.
|
||||
*/
|
||||
public class CardinalityRateLimiter {
|
||||
|
||||
private final FaultTolerantRedisCluster cacheCluster;
|
||||
|
||||
private final String name;
|
||||
|
||||
private final Duration ttl;
|
||||
private final int defaultMaxCardinality;
|
||||
|
||||
public CardinalityRateLimiter(final FaultTolerantRedisCluster cacheCluster, final String name, final Duration ttl, final int defaultMaxCardinality) {
|
||||
this.cacheCluster = cacheCluster;
|
||||
|
||||
this.name = name;
|
||||
|
||||
this.ttl = ttl;
|
||||
this.defaultMaxCardinality = defaultMaxCardinality;
|
||||
}
|
||||
|
||||
public void validate(final String key, final String target, final int maxCardinality) throws RateLimitExceededException {
|
||||
|
||||
final boolean rateLimitExceeded = cacheCluster.withCluster(connection -> {
|
||||
final String hllKey = getHllKey(key);
|
||||
|
||||
final boolean changed = connection.sync().pfadd(hllKey, target) == 1;
|
||||
final long cardinality = connection.sync().pfcount(hllKey);
|
||||
|
||||
final boolean mayNeedExpiration = changed && cardinality == 1;
|
||||
|
||||
// If the set already existed, we can assume it already had an expiration time and can save a round trip by
|
||||
// skipping the ttl check.
|
||||
if (mayNeedExpiration && connection.sync().ttl(hllKey) == -1) {
|
||||
connection.sync().expire(hllKey, ttl.toSeconds());
|
||||
}
|
||||
|
||||
return cardinality > maxCardinality;
|
||||
});
|
||||
|
||||
if (rateLimitExceeded) {
|
||||
long remainingTtl = getRemainingTtl(key);
|
||||
throw new RateLimitExceededException(remainingTtl >= 0 ? Duration.ofSeconds(remainingTtl) : null);
|
||||
}
|
||||
}
|
||||
|
||||
private String getHllKey(final String key) {
|
||||
return "hll_rate_limit::" + name + "::" + key;
|
||||
}
|
||||
|
||||
public Duration getInitialTtl() {
|
||||
return ttl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the remaining ttl for the specified key
|
||||
*
|
||||
* @param key with timeout to check
|
||||
* @return the ttl, or negative in the case of error
|
||||
*/
|
||||
public long getRemainingTtl(final String key) {
|
||||
// ttl() returns -2 if key does not exist, -1 if key has no expiration
|
||||
return cacheCluster.withCluster(connection -> connection.sync().ttl(getHllKey(key)));
|
||||
}
|
||||
|
||||
public int getDefaultMaxCardinality() {
|
||||
return defaultMaxCardinality;
|
||||
}
|
||||
|
||||
public boolean hasConfiguration(final CardinalityRateLimitConfiguration configuration) {
|
||||
return defaultMaxCardinality == configuration.getMaxCardinality() && ttl.equals(configuration.getTtl());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.limits;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import java.time.Duration;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
|
||||
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
|
||||
|
||||
class CardinalityRateLimiterTest {
|
||||
|
||||
@RegisterExtension
|
||||
static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
|
||||
|
||||
@Test
|
||||
void testValidate() {
|
||||
final int maxCardinality = 10;
|
||||
final CardinalityRateLimiter rateLimiter =
|
||||
new CardinalityRateLimiter(REDIS_CLUSTER_EXTENSION.getRedisCluster(), "test", Duration.ofDays(1),
|
||||
maxCardinality);
|
||||
|
||||
final String source = "+18005551234";
|
||||
int validatedAttempts = 0;
|
||||
int blockedAttempts = 0;
|
||||
|
||||
for (int i = 0; i < maxCardinality * 2; i++) {
|
||||
try {
|
||||
rateLimiter.validate(source, String.valueOf(i), rateLimiter.getDefaultMaxCardinality());
|
||||
validatedAttempts++;
|
||||
} catch (final RateLimitExceededException e) {
|
||||
blockedAttempts++;
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue(validatedAttempts >= maxCardinality);
|
||||
assertTrue(blockedAttempts > 0);
|
||||
|
||||
final String secondSource = "+18005554321";
|
||||
|
||||
try {
|
||||
rateLimiter.validate(secondSource, "test", rateLimiter.getDefaultMaxCardinality());
|
||||
} catch (final RateLimitExceededException e) {
|
||||
fail("New source should not trigger a rate limit exception on first attempted validation");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue