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 }