4
0

snac_cache.go 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. package toc
  2. import (
  3. "sync"
  4. "time"
  5. "github.com/mk6i/open-oscar-server/wire"
  6. )
  7. // snacCacheTTL is how long a sent SNAC waits for a reply before it is dropped
  8. // from the cache. Requests the server drops, such as rate-limited ones, never
  9. // get a reply.
  10. const snacCacheTTL = 60 * time.Second
  11. // snacCache tracks the SNACs a TOC connection sends to the OSCAR server by
  12. // request ID, so that a reply can be matched to the request that produced it.
  13. type snacCache struct {
  14. mu sync.Mutex
  15. lastID uint32
  16. entries map[uint32]sentSNAC
  17. timeNow func() time.Time
  18. }
  19. // sentSNAC is a SNAC request awaiting a reply.
  20. type sentSNAC struct {
  21. // frame is the request's SNAC frame.
  22. frame wire.SNACFrame
  23. // data holds whatever the reply handler needs to know about the request.
  24. data any
  25. sentAt time.Time
  26. }
  27. func newSNACCache() *snacCache {
  28. return &snacCache{
  29. entries: make(map[uint32]sentSNAC),
  30. timeNow: time.Now,
  31. }
  32. }
  33. // add assigns the next request ID to frame and records the request along with
  34. // data. Entries older than snacCacheTTL are swept out.
  35. func (c *snacCache) add(frame *wire.SNACFrame, data any) {
  36. c.mu.Lock()
  37. defer c.mu.Unlock()
  38. now := c.timeNow()
  39. for id, entry := range c.entries {
  40. if now.Sub(entry.sentAt) > snacCacheTTL {
  41. delete(c.entries, id)
  42. }
  43. }
  44. c.lastID++
  45. if c.lastID >= wire.ReqIDFromServer {
  46. c.lastID = 1
  47. }
  48. frame.RequestID = c.lastID
  49. c.entries[frame.RequestID] = sentSNAC{
  50. frame: *frame,
  51. data: data,
  52. sentAt: now,
  53. }
  54. }
  55. // take removes and returns the request that reply answers. A reply only
  56. // matches a request in the same food group, so that a relayed SNAC carrying
  57. // another user's request ID is not mistaken for a reply.
  58. func (c *snacCache) take(reply wire.SNACFrame) (sentSNAC, bool) {
  59. c.mu.Lock()
  60. defer c.mu.Unlock()
  61. entry, ok := c.entries[reply.RequestID]
  62. if !ok || entry.frame.FoodGroup != reply.FoodGroup {
  63. return sentSNAC{}, false
  64. }
  65. delete(c.entries, reply.RequestID)
  66. return entry, true
  67. }
  68. // remove drops the request with the given ID.
  69. func (c *snacCache) remove(requestID uint32) {
  70. c.mu.Lock()
  71. defer c.mu.Unlock()
  72. delete(c.entries, requestID)
  73. }