| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380 |
- package state
- import (
- "net/netip"
- "sync"
- "time"
- "github.com/mk6i/retro-aim-server/wire"
- )
- // SessSendStatus is the result of sending a message to a user.
- type SessSendStatus int
- const (
- // SessSendOK indicates message was sent to recipient
- SessSendOK SessSendStatus = iota
- // SessSendClosed indicates send did not complete because session is closed
- SessSendClosed
- // SessQueueFull indicates send failed due to full queue -- client is likely
- // dead
- SessQueueFull
- )
- // Session represents a user's current session. Unless stated otherwise, all
- // methods may be safely accessed by multiple goroutines.
- type Session struct {
- awayMessage string
- caps [][16]byte
- chatRoomCookie string
- closed bool
- displayScreenName DisplayScreenName
- identScreenName IdentScreenName
- idle bool
- idleTime time.Time
- msgCh chan wire.SNACMessage
- mutex sync.RWMutex
- nowFn func() time.Time
- signonComplete bool
- signonTime time.Time
- stopCh chan struct{}
- uin uint32
- warning uint16
- userInfoBitmask uint16
- userStatusBitmask uint32
- clientID string
- remoteAddr *netip.AddrPort
- }
- // NewSession returns a new instance of Session. By default, the user may have
- // up to 1000 pending messages before blocking.
- func NewSession() *Session {
- return &Session{
- msgCh: make(chan wire.SNACMessage, 1000),
- nowFn: time.Now,
- stopCh: make(chan struct{}),
- signonTime: time.Now(),
- caps: make([][16]byte, 0),
- userInfoBitmask: wire.OServiceUserFlagOSCARFree,
- userStatusBitmask: wire.OServiceUserStatusAvailable,
- }
- }
- // SetRemoteAddr sets the user's remote IP address
- func (s *Session) SetRemoteAddr(remoteAddr *netip.AddrPort) {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- s.remoteAddr = remoteAddr
- }
- // RemoteAddrs returns user's remote IP address
- func (s *Session) RemoteAddr() (remoteAddr *netip.AddrPort) {
- s.mutex.RLock()
- defer s.mutex.RUnlock()
- return s.remoteAddr
- }
- // SetUserInfoFlag sets a flag to and returns UserInfoBitmask
- func (s *Session) SetUserInfoFlag(flag uint16) (flags uint16) {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- s.userInfoBitmask |= flag
- return s.userInfoBitmask
- }
- // ClearUserInfoFlag clear a flag from and returns UserInfoBitmask
- func (s *Session) ClearUserInfoFlag(flag uint16) (flags uint16) {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- s.userInfoBitmask &^= flag
- return s.userInfoBitmask
- }
- // UserInfoBitmask returns UserInfoBitmask
- func (s *Session) UserInfoBitmask() (flags uint16) {
- s.mutex.RLock()
- defer s.mutex.RUnlock()
- return s.userInfoBitmask
- }
- // SetUserStatusBitmask sets the user status bitmask from the client.
- func (s *Session) SetUserStatusBitmask(bitmask uint32) {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- s.userStatusBitmask = bitmask
- }
- // IncrementWarning increments the user's warning level. To decrease, pass a
- // negative increment value.
- func (s *Session) IncrementWarning(incr uint16) {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- s.warning += incr
- }
- // Invisible returns true if the user is idle.
- func (s *Session) Invisible() bool {
- s.mutex.RLock()
- defer s.mutex.RUnlock()
- return s.userStatusBitmask&wire.OServiceUserStatusInvisible == wire.OServiceUserStatusInvisible
- }
- // SetIdentScreenName sets the user's screen name.
- func (s *Session) SetIdentScreenName(screenName IdentScreenName) {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- s.identScreenName = screenName
- }
- // IdentScreenName returns the user's screen name.
- func (s *Session) IdentScreenName() IdentScreenName {
- s.mutex.RLock()
- defer s.mutex.RUnlock()
- return s.identScreenName
- }
- // SetDisplayScreenName sets the user's screen name.
- func (s *Session) SetDisplayScreenName(displayScreenName DisplayScreenName) {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- s.displayScreenName = displayScreenName
- }
- // DisplayScreenName returns the user's screen name.
- func (s *Session) DisplayScreenName() DisplayScreenName {
- s.mutex.RLock()
- defer s.mutex.RUnlock()
- return s.displayScreenName
- }
- // SetSignonTime sets the user's sign-ontime.
- func (s *Session) SetSignonTime(t time.Time) {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- s.signonTime = t
- }
- // SignonTime reports when the user signed on
- func (s *Session) SignonTime() time.Time {
- s.mutex.RLock()
- defer s.mutex.RUnlock()
- return s.signonTime
- }
- // Idle reports the user's idle state.
- func (s *Session) Idle() bool {
- s.mutex.RLock()
- defer s.mutex.RUnlock()
- return s.idle
- }
- // IdleTime reports when the user went idle
- func (s *Session) IdleTime() time.Time {
- s.mutex.RLock()
- defer s.mutex.RUnlock()
- return s.idleTime
- }
- // SetIdle sets the user's idle state.
- func (s *Session) SetIdle(dur time.Duration) {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- s.idle = true
- // set the time the user became idle
- s.idleTime = s.nowFn().Add(-dur)
- }
- // UnsetIdle removes the user's idle state.
- func (s *Session) UnsetIdle() {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- s.idle = false
- }
- // SetAwayMessage sets the user's away message.
- func (s *Session) SetAwayMessage(awayMessage string) {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- s.awayMessage = awayMessage
- }
- // AwayMessage returns the user's away message.
- func (s *Session) AwayMessage() string {
- s.mutex.RLock()
- defer s.mutex.RUnlock()
- return s.awayMessage
- }
- // SetChatRoomCookie sets the chatRoomCookie for the chat room the user is currently in.
- func (s *Session) SetChatRoomCookie(cookie string) {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- s.chatRoomCookie = cookie
- }
- // ChatRoomCookie gets the chatRoomCookie for the chat room the user is currently in.
- func (s *Session) ChatRoomCookie() string {
- s.mutex.RLock()
- defer s.mutex.RUnlock()
- return s.chatRoomCookie
- }
- // SignonComplete indicates whether the client has completed the sign-on sequence.
- func (s *Session) SignonComplete() bool {
- s.mutex.RLock()
- defer s.mutex.RUnlock()
- return s.signonComplete
- }
- // SetSignonComplete indicates that the client has completed the sign-on sequence.
- func (s *Session) SetSignonComplete() {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- s.signonComplete = true
- }
- // UIN returns the user's ICQ number.
- func (s *Session) UIN() uint32 {
- s.mutex.RLock()
- defer s.mutex.RUnlock()
- return s.uin
- }
- // SetUIN sets the user's ICQ number.
- func (s *Session) SetUIN(uin uint32) {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- s.uin = uin
- }
- // TLVUserInfo returns a TLV list containing session information required by
- // multiple SNAC message types that convey user information.
- func (s *Session) TLVUserInfo() wire.TLVUserInfo {
- s.mutex.RLock()
- defer s.mutex.RUnlock()
- return wire.TLVUserInfo{
- ScreenName: string(s.displayScreenName),
- WarningLevel: s.warning,
- TLVBlock: wire.TLVBlock{
- TLVList: s.userInfo(),
- },
- }
- }
- func (s *Session) userInfo() wire.TLVList {
- tlvs := wire.TLVList{}
- // sign-in timestamp
- tlvs.Append(wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(s.signonTime.Unix())))
- // user info flags
- uFlags := s.userInfoBitmask
- if s.awayMessage != "" {
- uFlags |= wire.OServiceUserFlagUnavailable
- }
- tlvs.Append(wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uFlags))
- // user status flags
- tlvs.Append(wire.NewTLVBE(wire.OServiceUserInfoStatus, s.userStatusBitmask))
- // idle status
- if s.idle {
- tlvs.Append(wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(s.nowFn().Sub(s.idleTime).Minutes())))
- }
- // ICQ direct-connect info. The TLV is required for buddy arrival events to
- // work in ICQ, even if the values are set to default.
- if s.userInfoBitmask&wire.OServiceUserFlagICQ == wire.OServiceUserFlagICQ {
- tlvs.Append(wire.NewTLVBE(wire.OServiceUserInfoICQDC, wire.ICQDCInfo{}))
- }
- // capabilities (buddy icon, chat, etc...)
- if len(s.caps) > 0 {
- tlvs.Append(wire.NewTLVBE(wire.OServiceUserInfoOscarCaps, s.caps))
- }
- return tlvs
- }
- // SetCaps sets capability UUIDs that represent the features the client
- // supports. If set, capability metadata appears in the user info TLV list.
- func (s *Session) SetCaps(caps [][16]byte) {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- s.caps = caps
- }
- // Caps retrieves user capabilities.
- func (s *Session) Caps() [][16]byte {
- s.mutex.RLock()
- defer s.mutex.RUnlock()
- return s.caps
- }
- func (s *Session) Warning() uint16 {
- s.mutex.RLock()
- defer s.mutex.RUnlock()
- var w uint16
- w = s.warning
- return w
- }
- // ReceiveMessage returns a channel of messages relayed via this session. It
- // may only be read by one consumer. The channel never closes; call this method
- // in a select block along with Closed in order to detect session closure.
- func (s *Session) ReceiveMessage() chan wire.SNACMessage {
- return s.msgCh
- }
- // RelayMessage receives a SNAC message from a user and passes it on
- // asynchronously to the consumer of this session's messages. It returns
- // SessSendStatus to indicate whether the message was successfully sent or
- // not. This method is non-blocking.
- func (s *Session) RelayMessage(msg wire.SNACMessage) SessSendStatus {
- s.mutex.RLock()
- defer s.mutex.RUnlock()
- if s.closed {
- return SessSendClosed
- }
- select {
- case s.msgCh <- msg:
- return SessSendOK
- case <-s.stopCh:
- return SessSendClosed
- default:
- return SessQueueFull
- }
- }
- // Close shuts down the session's ability to relay messages. Once invoked,
- // RelayMessage returns SessQueueFull and Closed returns a closed channel.
- // It is not possible to re-open message relaying once closed. It is safe to
- // call from multiple go routines.
- func (s *Session) Close() {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- if s.closed {
- return
- }
- close(s.stopCh)
- s.closed = true
- }
- // Closed blocks until the session is closed.
- func (s *Session) Closed() <-chan struct{} {
- return s.stopCh
- }
- // SetClientID sets the client ID.
- func (s *Session) SetClientID(clientID string) {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- s.clientID = clientID
- }
- // ClientID retrieves the client ID.
- func (s *Session) ClientID() string {
- s.mutex.RLock()
- defer s.mutex.RUnlock()
- return s.clientID
- }
|