session.go 8.2 KB

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