session.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. package state
  2. import (
  3. "sync"
  4. "time"
  5. "github.com/mk6i/retro-aim-server/wire"
  6. )
  7. // SessSendStatus is the result of sending a message to a user.
  8. type SessSendStatus int
  9. const (
  10. // SessSendOK indicates message was sent to recipient
  11. SessSendOK SessSendStatus = iota
  12. // SessSendClosed indicates send did not complete because session is closed
  13. SessSendClosed
  14. // SessQueueFull indicates send failed due to full queue -- client is likely
  15. // dead
  16. SessQueueFull
  17. )
  18. // Session represents a user's current session. Unless stated otherwise, all
  19. // methods may be safely accessed by multiple goroutines.
  20. type Session struct {
  21. awayMessage string
  22. chatRoomCookie string
  23. closed bool
  24. idle bool
  25. idleTime time.Time
  26. invisible bool
  27. msgCh chan wire.SNACMessage
  28. mutex sync.RWMutex
  29. nowFn func() time.Time
  30. signonComplete bool
  31. screenName string
  32. signonTime time.Time
  33. stopCh chan struct{}
  34. warning uint16
  35. caps [][16]byte
  36. }
  37. // NewSession returns a new instance of Session. By default, the user may have
  38. // up to 1000 pending messages before blocking.
  39. func NewSession() *Session {
  40. return &Session{
  41. msgCh: make(chan wire.SNACMessage, 1000),
  42. nowFn: time.Now,
  43. stopCh: make(chan struct{}),
  44. signonTime: time.Now(),
  45. caps: make([][16]byte, 0),
  46. }
  47. }
  48. // IncrementWarning increments the user's warning level. To decrease, pass a
  49. // negative increment value.
  50. func (s *Session) IncrementWarning(incr uint16) {
  51. s.mutex.Lock()
  52. defer s.mutex.Unlock()
  53. s.warning += incr
  54. }
  55. // SetInvisible toggles the user's invisibility status.
  56. func (s *Session) SetInvisible(invisible bool) {
  57. s.mutex.Lock()
  58. defer s.mutex.Unlock()
  59. s.invisible = invisible
  60. }
  61. // Invisible returns true if the user is idle.
  62. func (s *Session) Invisible() bool {
  63. s.mutex.RLock()
  64. defer s.mutex.RUnlock()
  65. return s.invisible
  66. }
  67. // SetScreenName sets the user's screen name.
  68. func (s *Session) SetScreenName(screenName string) {
  69. s.mutex.Lock()
  70. defer s.mutex.Unlock()
  71. s.screenName = screenName
  72. }
  73. // ScreenName returns the user's screen name.
  74. func (s *Session) ScreenName() string {
  75. s.mutex.RLock()
  76. defer s.mutex.RUnlock()
  77. return s.screenName
  78. }
  79. // SetSignonTime sets the user's sign-ontime.
  80. func (s *Session) SetSignonTime(t time.Time) {
  81. s.mutex.Lock()
  82. defer s.mutex.Unlock()
  83. s.signonTime = t
  84. }
  85. // SetIdle sets the user's idle state.
  86. func (s *Session) SetIdle(dur time.Duration) {
  87. s.mutex.Lock()
  88. defer s.mutex.Unlock()
  89. s.idle = true
  90. // set the time the user became idle
  91. s.idleTime = s.nowFn().Add(-dur)
  92. }
  93. // UnsetIdle removes the user's idle state.
  94. func (s *Session) UnsetIdle() {
  95. s.mutex.Lock()
  96. defer s.mutex.Unlock()
  97. s.idle = false
  98. }
  99. // SetAwayMessage sets the user's away message.
  100. func (s *Session) SetAwayMessage(awayMessage string) {
  101. s.mutex.Lock()
  102. defer s.mutex.Unlock()
  103. s.awayMessage = awayMessage
  104. }
  105. // AwayMessage returns the user's away message.
  106. func (s *Session) AwayMessage() string {
  107. s.mutex.RLock()
  108. defer s.mutex.RUnlock()
  109. return s.awayMessage
  110. }
  111. // SetChatRoomCookie sets the chatRoomCookie for the chat room the user is currently in.
  112. func (s *Session) SetChatRoomCookie(cookie string) {
  113. s.mutex.Lock()
  114. defer s.mutex.Unlock()
  115. s.chatRoomCookie = cookie
  116. }
  117. // ChatRoomCookie gets the chatRoomCookie for the chat room the user is currently in.
  118. func (s *Session) ChatRoomCookie() string {
  119. s.mutex.RLock()
  120. defer s.mutex.RUnlock()
  121. return s.chatRoomCookie
  122. }
  123. // SignonComplete indicates whether the client has completed the sign-on sequence.
  124. func (s *Session) SignonComplete() bool {
  125. s.mutex.RLock()
  126. defer s.mutex.RUnlock()
  127. return s.signonComplete
  128. }
  129. // SetSignonComplete indicates that the client has completed the sign-on sequence.
  130. func (s *Session) SetSignonComplete() {
  131. s.mutex.Lock()
  132. defer s.mutex.Unlock()
  133. s.signonComplete = true
  134. }
  135. // TLVUserInfo returns a TLV list containing session information required by
  136. // multiple SNAC message types that convey user information.
  137. func (s *Session) TLVUserInfo() wire.TLVUserInfo {
  138. s.mutex.RLock()
  139. defer s.mutex.RUnlock()
  140. return wire.TLVUserInfo{
  141. ScreenName: s.screenName,
  142. WarningLevel: s.warning,
  143. TLVBlock: wire.TLVBlock{
  144. TLVList: s.userInfo(),
  145. },
  146. }
  147. }
  148. func (s *Session) userInfo() wire.TLVList {
  149. tlvs := wire.TLVList{}
  150. // sign-in timestamp
  151. tlvs.Append(wire.NewTLV(wire.OServiceUserInfoSignonTOD, uint32(s.signonTime.Unix())))
  152. // away message status
  153. if s.awayMessage != "" {
  154. tlvs.Append(wire.NewTLV(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree|wire.OServiceUserFlagUnavailable))
  155. } else {
  156. tlvs.Append(wire.NewTLV(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree))
  157. }
  158. // reflects invisibility toggle status back to toggling client
  159. if s.invisible {
  160. tlvs.Append(wire.NewTLV(wire.OServiceUserInfoStatus, wire.OServiceUserStatusInvisible))
  161. } else {
  162. tlvs.Append(wire.NewTLV(wire.OServiceUserInfoStatus, uint32(0)))
  163. }
  164. // idle status
  165. if s.idle {
  166. tlvs.Append(wire.NewTLV(wire.OServiceUserInfoIdleTime, uint16(s.nowFn().Sub(s.idleTime).Minutes())))
  167. }
  168. // capabilities (buddy icon, chat, etc...)
  169. if len(s.caps) > 0 {
  170. tlvs.Append(wire.NewTLV(wire.OServiceUserInfoOscarCaps, s.caps))
  171. }
  172. return tlvs
  173. }
  174. // SetCaps sets capability UUIDs that represent the features the client
  175. // supports. If set, capability metadata appears in the user info TLV list.
  176. func (s *Session) SetCaps(caps [][16]byte) {
  177. s.mutex.Lock()
  178. defer s.mutex.Unlock()
  179. s.caps = caps
  180. }
  181. // Caps retrieves user capabilities.
  182. func (s *Session) Caps() [][16]byte {
  183. s.mutex.RLock()
  184. defer s.mutex.RUnlock()
  185. return s.caps
  186. }
  187. func (s *Session) Warning() uint16 {
  188. s.mutex.RLock()
  189. defer s.mutex.RUnlock()
  190. var w uint16
  191. w = s.warning
  192. return w
  193. }
  194. // ReceiveMessage returns a channel of messages relayed via this session. It
  195. // may only be read by one consumer. The channel never closes; call this method
  196. // in a select block along with Closed in order to detect session closure.
  197. func (s *Session) ReceiveMessage() chan wire.SNACMessage {
  198. return s.msgCh
  199. }
  200. // RelayMessage receives a SNAC message from a user and passes it on
  201. // asynchronously to the consumer of this session's messages. It returns
  202. // SessSendStatus to indicate whether the message was successfully sent or
  203. // not. This method is non-blocking.
  204. func (s *Session) RelayMessage(msg wire.SNACMessage) SessSendStatus {
  205. s.mutex.RLock()
  206. defer s.mutex.RUnlock()
  207. if s.closed {
  208. return SessSendClosed
  209. }
  210. select {
  211. case s.msgCh <- msg:
  212. return SessSendOK
  213. case <-s.stopCh:
  214. return SessSendClosed
  215. default:
  216. return SessQueueFull
  217. }
  218. }
  219. // Close shuts down the session's ability to relay messages. Once invoked,
  220. // RelayMessage returns SessQueueFull and Closed returns a closed channel.
  221. // It is not possible to re-open message relaying once closed. It is safe to
  222. // call from multiple go routines.
  223. func (s *Session) Close() {
  224. s.mutex.Lock()
  225. defer s.mutex.Unlock()
  226. if s.closed {
  227. return
  228. }
  229. close(s.stopCh)
  230. s.closed = true
  231. }
  232. // Closed blocks until the session is closed.
  233. func (s *Session) Closed() <-chan struct{} {
  234. return s.stopCh
  235. }