You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

fakelag.go 2.2KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. // Copyright (c) 2018 Shivaram Lingamneni <slingamn@cs.stanford.edu>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "time"
  6. )
  7. // fakelag is a system for artificially delaying commands when a user issues
  8. // them too rapidly
  9. type FakelagState uint
  10. const (
  11. // initially, the client is "bursting" and can send n commands without
  12. // encountering fakelag
  13. FakelagBursting FakelagState = iota
  14. // after that, they're "throttled" and we sleep in between commands until
  15. // they're spaced sufficiently far apart
  16. FakelagThrottled
  17. )
  18. // this is intentionally not threadsafe, because it should only be touched
  19. // from the loop that accepts the client's input and runs commands
  20. type Fakelag struct {
  21. config FakelagConfig
  22. nowFunc func() time.Time
  23. sleepFunc func(time.Duration)
  24. state FakelagState
  25. burstCount uint // number of messages sent in the current burst
  26. lastTouch time.Time
  27. }
  28. func (fl *Fakelag) Initialize(config FakelagConfig) {
  29. fl.config = config
  30. fl.nowFunc = time.Now
  31. fl.sleepFunc = time.Sleep
  32. fl.state = FakelagBursting
  33. }
  34. // register a new command, sleep if necessary to delay it
  35. func (fl *Fakelag) Touch() {
  36. if !fl.config.Enabled {
  37. return
  38. }
  39. now := fl.nowFunc()
  40. // XXX if lastTouch.IsZero(), treat it as "very far in the past", which is fine
  41. elapsed := now.Sub(fl.lastTouch)
  42. fl.lastTouch = now
  43. if fl.state == FakelagBursting {
  44. // determine if the previous burst is over
  45. if elapsed > fl.config.Cooldown {
  46. fl.burstCount = 0
  47. }
  48. fl.burstCount++
  49. if fl.burstCount > fl.config.BurstLimit {
  50. // reset burst window for next time
  51. fl.burstCount = 0
  52. // transition to throttling
  53. fl.state = FakelagThrottled
  54. // continue to throttling logic
  55. } else {
  56. return
  57. }
  58. }
  59. if fl.state == FakelagThrottled {
  60. if elapsed > fl.config.Cooldown {
  61. // let them burst again
  62. fl.state = FakelagBursting
  63. return
  64. }
  65. // space them out by at least window/messagesperwindow
  66. sleepDuration := time.Duration((int64(fl.config.Window) / int64(fl.config.MessagesPerWindow)) - int64(elapsed))
  67. if sleepDuration > 0 {
  68. fl.sleepFunc(sleepDuration)
  69. // the touch time should take into account the time we slept
  70. fl.lastTouch = fl.nowFunc()
  71. }
  72. }
  73. }