session.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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. id string
  25. idle bool
  26. idleTime time.Time
  27. invisible bool
  28. msgCh chan wire.SNACMessage
  29. mutex sync.RWMutex
  30. nowFn func() time.Time
  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. // SetID sets the user's session ID.
  80. func (s *Session) SetID(ID string) {
  81. s.mutex.Lock()
  82. defer s.mutex.Unlock()
  83. s.id = ID
  84. }
  85. // ID returns the user's session ID.
  86. func (s *Session) ID() string {
  87. s.mutex.RLock()
  88. defer s.mutex.RUnlock()
  89. return s.id
  90. }
  91. // SetSignonTime sets the user's sign-ontime.
  92. func (s *Session) SetSignonTime(t time.Time) {
  93. s.mutex.Lock()
  94. defer s.mutex.Unlock()
  95. s.signonTime = t
  96. }
  97. // SetIdle sets the user's idle state.
  98. func (s *Session) SetIdle(dur time.Duration) {
  99. s.mutex.Lock()
  100. defer s.mutex.Unlock()
  101. s.idle = true
  102. // set the time the user became idle
  103. s.idleTime = s.nowFn().Add(-dur)
  104. }
  105. // UnsetIdle removes the user's idle state.
  106. func (s *Session) UnsetIdle() {
  107. s.mutex.Lock()
  108. defer s.mutex.Unlock()
  109. s.idle = false
  110. }
  111. // SetAwayMessage sets the user's away message.
  112. func (s *Session) SetAwayMessage(awayMessage string) {
  113. s.mutex.Lock()
  114. defer s.mutex.Unlock()
  115. s.awayMessage = awayMessage
  116. }
  117. // AwayMessage returns the user's away message.
  118. func (s *Session) AwayMessage() string {
  119. s.mutex.RLock()
  120. defer s.mutex.RUnlock()
  121. return s.awayMessage
  122. }
  123. // SetChatRoomCookie sets the chatRoomCookie for the chat room the user is currently in.
  124. func (s *Session) SetChatRoomCookie(cookie string) {
  125. s.mutex.Lock()
  126. defer s.mutex.Unlock()
  127. s.chatRoomCookie = cookie
  128. }
  129. // ChatRoomCookie gets the chatRoomCookie for the chat room the user is currently in.
  130. func (s *Session) ChatRoomCookie() string {
  131. s.mutex.RLock()
  132. defer s.mutex.RUnlock()
  133. return s.chatRoomCookie
  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. // invisibility status
  159. if s.invisible {
  160. tlvs.Append(wire.NewTLV(wire.OServiceUserInfoStatus, wire.OServiceUserFlagInvisible))
  161. } else {
  162. tlvs.Append(wire.NewTLV(wire.OServiceUserInfoStatus, uint16(0)))
  163. }
  164. // idle status
  165. if s.idle {
  166. tlvs.Append(wire.NewTLV(wire.OServiceUserInfoIdleTime, uint16(s.nowFn().Sub(s.idleTime).Seconds())))
  167. } else {
  168. tlvs.Append(wire.NewTLV(wire.OServiceUserInfoIdleTime, uint16(0)))
  169. }
  170. // capabilities (buddy icon, chat, etc...)
  171. if len(s.caps) > 0 {
  172. tlvs.Append(wire.NewTLV(wire.OServiceUserInfoOscarCaps, s.caps))
  173. }
  174. return tlvs
  175. }
  176. // SetCaps sets capability UUIDs that represent the features the client
  177. // supports. If set, capability metadata appears in the user info TLV list.
  178. func (s *Session) SetCaps(caps [][16]byte) {
  179. s.mutex.Lock()
  180. defer s.mutex.Unlock()
  181. s.caps = caps
  182. }
  183. // Caps retrieves user capabilities.
  184. func (s *Session) Caps() [][16]byte {
  185. s.mutex.RLock()
  186. defer s.mutex.RUnlock()
  187. return s.caps
  188. }
  189. func (s *Session) Warning() uint16 {
  190. s.mutex.RLock()
  191. defer s.mutex.RUnlock()
  192. var w uint16
  193. w = s.warning
  194. return w
  195. }
  196. // ReceiveMessage returns a channel of messages relayed via this session. It
  197. // may only be read by one consumer. The channel never closes; call this method
  198. // in a select block along with Closed in order to detect session closure.
  199. func (s *Session) ReceiveMessage() chan wire.SNACMessage {
  200. return s.msgCh
  201. }
  202. // RelayMessage receives a SNAC message from a user and passes it on
  203. // asynchronously to the consumer of this session's messages. It returns
  204. // SessSendStatus to indicate whether the message was successfully sent or
  205. // not. This method is non-blocking.
  206. func (s *Session) RelayMessage(msg wire.SNACMessage) SessSendStatus {
  207. s.mutex.RLock()
  208. defer s.mutex.RUnlock()
  209. if s.closed {
  210. return SessSendClosed
  211. }
  212. select {
  213. case s.msgCh <- msg:
  214. return SessSendOK
  215. case <-s.stopCh:
  216. return SessSendClosed
  217. default:
  218. return SessQueueFull
  219. }
  220. }
  221. // Close shuts down the session's ability to relay messages. Once invoked,
  222. // RelayMessage returns SessQueueFull and Closed returns a closed channel.
  223. // It is not possible to re-open message relaying once closed. It is safe to
  224. // call from multiple go routines.
  225. func (s *Session) Close() {
  226. s.mutex.Lock()
  227. defer s.mutex.Unlock()
  228. if s.closed {
  229. return
  230. }
  231. close(s.stopCh)
  232. s.closed = true
  233. }
  234. // Closed blocks until the session is closed.
  235. func (s *Session) Closed() <-chan struct{} {
  236. return s.stopCh
  237. }