forked-synapse/synapse/api/ratelimiting.py

94 lines
3.4 KiB
Python
Raw Normal View History

2016-01-06 23:26:29 -05:00
# Copyright 2014-2016 OpenMarket Ltd
#
# 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.
2020-01-20 12:34:13 -05:00
from collections import OrderedDict
from typing import Any, Optional, Tuple
2014-09-01 12:54:54 -04:00
from synapse.api.errors import LimitExceededError
2014-09-01 12:54:54 -04:00
class Ratelimiter(object):
2014-09-02 10:06:20 -04:00
"""
Ratelimit message sending by user.
"""
2014-09-01 12:54:54 -04:00
def __init__(self):
2020-01-20 12:34:13 -05:00
self.message_counts = (
OrderedDict()
) # type: OrderedDict[Any, Tuple[float, int, Optional[float]]]
2014-09-01 12:54:54 -04:00
def can_do_action(self, key, time_now_s, rate_hz, burst_count, update=True):
"""Can the entity (e.g. user or IP address) perform the action?
2014-09-02 10:06:20 -04:00
Args:
key: The key we should use when rate limiting. Can be a user ID
(when sending events), an IP address, etc.
2014-09-02 10:06:20 -04:00
time_now_s: The time now.
rate_hz: The long term number of messages a user can send in a
2014-09-02 10:06:20 -04:00
second.
burst_count: How many messages the user can send before being
limited.
update (bool): Whether to update the message rates or not. This is
useful to check if a message would be allowed to be sent before
its ready to be actually sent.
2014-09-02 10:06:20 -04:00
Returns:
A pair of a bool indicating if they can send a message now and a
time in seconds of when they can next send a message.
"""
self.prune_message_counts(time_now_s)
message_count, time_start, _ignored = self.message_counts.get(
2019-06-20 05:32:02 -04:00
key, (0.0, time_now_s, None)
2014-09-01 12:54:54 -04:00
)
2014-09-02 10:06:20 -04:00
time_delta = time_now_s - time_start
sent_count = message_count - time_delta * rate_hz
2014-09-02 10:06:20 -04:00
if sent_count < 0:
allowed = True
time_start = time_now_s
2019-06-20 05:32:02 -04:00
message_count = 1.0
elif sent_count > burst_count - 1.0:
2014-09-01 12:54:54 -04:00
allowed = False
else:
allowed = True
message_count += 1
2014-09-02 10:06:20 -04:00
if update:
2019-06-20 05:32:02 -04:00
self.message_counts[key] = (message_count, time_start, rate_hz)
2014-09-02 10:06:20 -04:00
if rate_hz > 0:
2019-06-20 05:32:02 -04:00
time_allowed = time_start + (message_count - burst_count + 1) / rate_hz
2014-09-02 10:06:20 -04:00
if time_allowed < time_now_s:
time_allowed = time_now_s
else:
time_allowed = -1
return allowed, time_allowed
def prune_message_counts(self, time_now_s):
for key in list(self.message_counts.keys()):
2019-06-20 05:32:02 -04:00
message_count, time_start, rate_hz = self.message_counts[key]
2014-09-02 10:06:20 -04:00
time_delta = time_now_s - time_start
if message_count - time_delta * rate_hz > 0:
2014-09-02 10:06:20 -04:00
break
else:
del self.message_counts[key]
def ratelimit(self, key, time_now_s, rate_hz, burst_count, update=True):
allowed, time_allowed = self.can_do_action(
key, time_now_s, rate_hz, burst_count, update
)
if not allowed:
raise LimitExceededError(
2019-06-20 05:32:02 -04:00
retry_after_ms=int(1000 * (time_allowed - time_now_s))
)