1 module bancho.ratelimit;
2 
3 import core.time;
4 
5 import vibe.core.core;
6 import vibe.core.sync;
7 
8 /// Params:
9 /// 	messages = Maximum allowed messages in the given timespan
10 /// 	timespan = Timespan to check messages in
11 struct ChatRatelimiter(int maxMessages, Duration timespan)
12 {
13 	/// Delay to always apply to avoid large gaps
14 	Duration baseDelay = timespan / maxMessages / 2; // default to half an average cycle so if messages are going to be spammed, half of timespan is the maximum idle time
15 	/// Log of all sent message times in ms, because that's what bancho uses.
16 	long[maxMessages] times;
17 	/// Index where to insert in times array right now.
18 	int cycle;
19 	InterruptibleTaskMutex mutex;
20 
21 	void load()
22 	{
23 		mutex = new InterruptibleTaskMutex();
24 		first = MonoTime.currTime();
25 	}
26 
27 	private MonoTime first;
28 	private long now()
29 	{
30 		return (MonoTime.currTime() - first).total!"msecs";
31 	}
32 
33 	void putWait(bool put)
34 	{
35 		synchronized (mutex)
36 		{
37 			auto first = times[(cycle + 1) % $];
38 			auto sinceLast = (now - times[(cycle + $ - 1) % $]).msecs;
39 			if (sinceLast > timespan)
40 			{
41 				// do nothing
42 			}
43 			else if (sinceLast > timespan - baseDelay)
44 			{
45 				// sleep remaining of baseDelay
46 				sleep(timespan - sinceLast);
47 			}
48 			else
49 			{
50 				sleep(baseDelay);
51 				auto totalTime = (now - first).msecs;
52 				if (totalTime < timespan)
53 					sleep(timespan - totalTime);
54 			}
55 			if (put)
56 			{
57 				times[cycle] = now;
58 				cycle = (cycle + 1) % times.length;
59 			}
60 		}
61 	}
62 
63 	/// Returns true if a message can be sent instantly.
64 	bool peekInstant()
65 	{
66 		if (!mutex.tryLock())
67 			return false;
68 		mutex.unlock();
69 		auto sinceLast = (now - times[(cycle + $ - 1) % $]).msecs;
70 		return sinceLast > timespan;
71 	}
72 
73 	/// Returns true if a message can be sent instantly or with the baseDelay.
74 	bool peekBase()
75 	{
76 		if (!mutex.tryLock())
77 			return false;
78 		mutex.unlock();
79 		auto sinceLast = (now - times[(cycle + $ - 1) % $]).msecs;
80 		return sinceLast - baseDelay > timespan;
81 	}
82 }
83 
84 // public rooms: 60 messages / minute
85 // private channels: 300 messages / minute
86 struct BanchoRatelimiter(int publicMax = 6, Duration publicTime = 6.seconds,
87 		int privateMax = 5, Duration privateTime = 1.seconds)
88 {
89 	ChatRatelimiter!(publicMax, publicTime) publicLimit;
90 	ChatRatelimiter!(privateMax, privateTime) privateLimit;
91 
92 	void load()
93 	{
94 		publicLimit.load();
95 		privateLimit.load();
96 	}
97 
98 	void putWait(string channel, bool put)
99 	{
100 		isPublic(channel) ? publicLimit.putWait(put) : privateLimit.putWait(put);
101 	}
102 
103 	bool peekInstant(string channel)
104 	{
105 		return isPublic(channel) ? publicLimit.peekInstant() : privateLimit.peekInstant();
106 	}
107 
108 	bool peekBase(string channel)
109 	{
110 		return isPublic(channel) ? publicLimit.peekBase() : privateLimit.peekBase();
111 	}
112 
113 }
114 
115 private bool isPublic(string channel)
116 {
117 	if (channel.length && channel[0] == '#')
118 	{
119 		// channel, so public
120 		return true;
121 	}
122 	else
123 	{
124 		// doesn't start with #, must be a player, so private
125 		return false;
126 	}
127 }