session.go 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274
  1. package state
  2. import (
  3. "net/netip"
  4. "sync"
  5. "time"
  6. "github.com/mk6i/open-oscar-server/wire"
  7. )
  8. // SessSendStatus is the result of sending a message to a user.
  9. type SessSendStatus int
  10. // RateClassState tracks the rate limiting state for a specific rate class
  11. // within a user's session.
  12. //
  13. // It embeds the static wire.RateClass configuration and maintains dynamic,
  14. // per-session state used to evaluate rate limits in real time.
  15. type RateClassState struct {
  16. // static rate limit configuration for this class
  17. wire.RateClass
  18. // CurrentLevel is the current exponential moving average for this rate
  19. // class.
  20. CurrentLevel int32
  21. // LastTime represents the last time a SNAC message was sent for this rate
  22. // class.
  23. LastTime time.Time
  24. // CurrentStatus is the last recorded rate limit status for this rate class.
  25. CurrentStatus wire.RateLimitStatus
  26. // Subscribed indicates whether the user wants to receive rate limit
  27. // parameter updates for this rate class.
  28. Subscribed bool
  29. // LimitedNow indicates whether the user is currently rate limited for this
  30. // rate class; the user is blocked from sending SNACs in this rate class
  31. // until the clear threshold is met.
  32. LimitedNow bool
  33. }
  34. const (
  35. // SessSendOK indicates message was sent to recipient
  36. SessSendOK SessSendStatus = iota
  37. // SessSendClosed indicates send did not complete because session is closed
  38. SessSendClosed
  39. // SessQueueFull indicates send failed due to full queue -- client is likely
  40. // dead
  41. SessQueueFull
  42. )
  43. // Session represents shared user-level state that persists across all concurrent
  44. // connections for a single user account.
  45. //
  46. // Session maintains client identity information, preferences, rate limiting state,
  47. // and other shared data that should be consistent across all of a user's active
  48. // connections. Individual connection-specific state (like remote address, sign-on
  49. // status, or per-connection capabilities) is stored in SessionInstance instead.
  50. //
  51. // All methods on Session are safe for concurrent use.
  52. type Session struct {
  53. mutex sync.RWMutex
  54. // User identity (shared across all sessions)
  55. displayScreenName DisplayScreenName
  56. identScreenName IdentScreenName
  57. uin uint32
  58. memberSince time.Time
  59. signonTime time.Time
  60. // User-level settings and profile (shared)
  61. warning uint16
  62. warningCh chan uint16
  63. offlineMsgCount int
  64. chatRoomCookie string
  65. buddyIcon wire.BARTID
  66. typingEventsEnabled bool
  67. // Rate limiting (shared across all sessions per user)
  68. rateLimitStates [5]RateClassState
  69. rateLimitStatesOriginal [5]RateClassState
  70. lastObservedStates [5]RateClassState
  71. instances map[uint8]*SessionInstance
  72. instancesOrdered []*SessionInstance
  73. initOnce sync.Once
  74. onSessCloseFn func()
  75. nowFn func() time.Time
  76. }
  77. // NewSession creates a new Session for a user.
  78. func NewSession() *Session {
  79. return &Session{
  80. warningCh: make(chan uint16, 1),
  81. instances: make(map[uint8]*SessionInstance),
  82. instancesOrdered: make([]*SessionInstance, 0),
  83. onSessCloseFn: func() {},
  84. nowFn: time.Now,
  85. }
  86. }
  87. //
  88. // Instance Management
  89. //
  90. // AddInstance creates and adds a new connection instance to the session.
  91. // Returns the newly created SessionInstance with a unique instance number.
  92. func (s *Session) AddInstance() *SessionInstance {
  93. s.mutex.Lock()
  94. defer s.mutex.Unlock()
  95. instance := &SessionInstance{
  96. session: s,
  97. instanceNum: s.generateInstanceNum(),
  98. msgCh: make(chan wire.SNACMessage, 1000),
  99. stopCh: make(chan struct{}),
  100. capabilities: make([][16]byte, 0),
  101. foodGroupVersions: defaultFoodGroupVersions(),
  102. userInfoBitmask: wire.OServiceUserFlagOSCARFree,
  103. userStatusBitmask: wire.OServiceUserStatusAvailable,
  104. onInstanceCloseFn: func() {},
  105. }
  106. s.instances[instance.instanceNum] = instance
  107. s.instancesOrdered = append(s.instancesOrdered, instance)
  108. return instance
  109. }
  110. // HasLiveInstances returns true if the session has at least one live instance.
  111. // A live instance is one that is not closed and has completed the sign-on sequence.
  112. func (s *Session) HasLiveInstances() bool {
  113. s.mutex.RLock()
  114. defer s.mutex.RUnlock()
  115. for _, instance := range s.instances {
  116. if instance.live() {
  117. return true
  118. }
  119. }
  120. return false
  121. }
  122. // InstanceCount returns the number of total instances in the session group.
  123. func (s *Session) InstanceCount() int {
  124. s.mutex.RLock()
  125. defer s.mutex.RUnlock()
  126. return len(s.instances)
  127. }
  128. // Instances returns all instances in the order they were added.
  129. func (s *Session) Instances() []*SessionInstance {
  130. s.mutex.RLock()
  131. defer s.mutex.RUnlock()
  132. instances := make([]*SessionInstance, len(s.instancesOrdered))
  133. copy(instances, s.instancesOrdered)
  134. return instances
  135. }
  136. // RemoveInstance removes an instance from the session group.
  137. func (s *Session) RemoveInstance(instance *SessionInstance) {
  138. s.mutex.Lock()
  139. defer s.mutex.Unlock()
  140. delete(s.instances, instance.instanceNum)
  141. for i, inst := range s.instancesOrdered {
  142. if inst == instance {
  143. s.instancesOrdered = append(s.instancesOrdered[:i], s.instancesOrdered[i+1:]...)
  144. break
  145. }
  146. }
  147. }
  148. // generateInstanceNum generates the next available instance number for this session group.
  149. // It finds the next number that is not currently in use by iterating over the possible key range.
  150. func (s *Session) generateInstanceNum() uint8 {
  151. // if num reaches 0, all number have been taken
  152. for num := uint8(1); num != 0; num++ {
  153. if _, exists := s.instances[num]; !exists {
  154. return num
  155. }
  156. }
  157. // the caller should ensure there are no more than 255 instances per session
  158. panic("all instance numbers are taken (max 255 instances per session)")
  159. }
  160. // defaultFoodGroupVersions returns default version numbers for all food groups.
  161. func defaultFoodGroupVersions() [wire.MDir + 1]uint16 {
  162. vals := [wire.MDir + 1]uint16{}
  163. vals[wire.OService] = 1
  164. vals[wire.Locate] = 1
  165. vals[wire.Buddy] = 1
  166. vals[wire.ICBM] = 1
  167. vals[wire.Advert] = 1
  168. vals[wire.Invite] = 1
  169. vals[wire.Admin] = 1
  170. vals[wire.Popup] = 1
  171. vals[wire.PermitDeny] = 1
  172. vals[wire.UserLookup] = 1
  173. vals[wire.Stats] = 1
  174. vals[wire.Translate] = 1
  175. vals[wire.ChatNav] = 1
  176. vals[wire.Chat] = 1
  177. vals[wire.ODir] = 1
  178. vals[wire.BART] = 1
  179. vals[wire.Feedbag] = 1
  180. vals[wire.ICQ] = 1
  181. vals[wire.BUCP] = 1
  182. vals[wire.Alert] = 1
  183. vals[wire.Plugin] = 1
  184. vals[wire.UnnamedFG24] = 1
  185. vals[wire.MDir] = 1
  186. return vals
  187. }
  188. //
  189. // Identity
  190. //
  191. // ChatRoomCookie returns the chat room cookie.
  192. func (s *Session) ChatRoomCookie() string {
  193. s.mutex.RLock()
  194. defer s.mutex.RUnlock()
  195. return s.chatRoomCookie
  196. }
  197. // DisplayScreenName returns the user's display screen name.
  198. func (s *Session) DisplayScreenName() DisplayScreenName {
  199. s.mutex.RLock()
  200. defer s.mutex.RUnlock()
  201. return s.displayScreenName
  202. }
  203. // IdentScreenName returns the user's identity screen name.
  204. func (s *Session) IdentScreenName() IdentScreenName {
  205. s.mutex.RLock()
  206. defer s.mutex.RUnlock()
  207. return s.identScreenName
  208. }
  209. // SetDisplayScreenName sets the user's display screen name (shared across all sessions).
  210. func (s *Session) SetDisplayScreenName(displayScreenName DisplayScreenName) {
  211. s.mutex.Lock()
  212. defer s.mutex.Unlock()
  213. s.displayScreenName = displayScreenName
  214. }
  215. // SetIdentScreenName sets the user's identity screen name (shared across all sessions).
  216. func (s *Session) SetIdentScreenName(screenName IdentScreenName) {
  217. s.mutex.Lock()
  218. defer s.mutex.Unlock()
  219. s.identScreenName = screenName
  220. }
  221. // SetUIN sets the user's ICQ number (shared across all sessions).
  222. func (s *Session) SetUIN(uin uint32) {
  223. s.mutex.Lock()
  224. defer s.mutex.Unlock()
  225. s.uin = uin
  226. }
  227. // UIN returns the user's ICQ number.
  228. func (s *Session) UIN() uint32 {
  229. s.mutex.RLock()
  230. defer s.mutex.RUnlock()
  231. return s.uin
  232. }
  233. //
  234. // Status / Availability
  235. //
  236. // Away returns true if all instances are away.
  237. func (s *Session) Away() bool {
  238. instances := s.Instances()
  239. if len(instances) == 0 {
  240. return false
  241. }
  242. for _, instance := range instances {
  243. if instance.UserInfoBitmask()&wire.OServiceUserFlagUnavailable == 0 &&
  244. instance.UserStatusBitmask()&wire.OServiceUserStatusAway == 0 {
  245. return false
  246. }
  247. }
  248. return true
  249. }
  250. // AwayMessage returns the away message from the last instance to set an away message.
  251. func (s *Session) AwayMessage() string {
  252. s.mutex.RLock()
  253. defer s.mutex.RUnlock()
  254. var latest *SessionInstance
  255. var latestTime time.Time
  256. for _, instance := range s.instances {
  257. // Only consider instances that are away
  258. if !instance.Away() {
  259. continue
  260. }
  261. _, awayTime := instance.AwayMessage()
  262. if latest == nil || awayTime.After(latestTime) {
  263. latest = instance
  264. latestTime = awayTime
  265. }
  266. }
  267. if latest == nil {
  268. return ""
  269. }
  270. awayMsg, _ := latest.AwayMessage()
  271. return awayMsg
  272. }
  273. // Idle returns true if all instances are idle.
  274. func (s *Session) Idle() bool {
  275. instances := s.Instances()
  276. if len(instances) == 0 {
  277. return false
  278. }
  279. for _, instance := range instances {
  280. if !instance.Idle() {
  281. return false
  282. }
  283. }
  284. return true
  285. }
  286. // IdleTime returns the latest idle time if all instances are idle. If not all
  287. // instances are idle, it returns a zero time.
  288. func (s *Session) IdleTime() time.Time {
  289. if !s.Idle() {
  290. return time.Time{}
  291. }
  292. return s.mostRecentIdleTime()
  293. }
  294. // Inactive returns true if all instances are not active.
  295. func (s *Session) Inactive() bool {
  296. for _, instance := range s.Instances() {
  297. if instance.active() {
  298. return false
  299. }
  300. }
  301. return true
  302. }
  303. // Instance returns the SessionInstance with the given instance number, or nil if not found.
  304. func (s *Session) Instance(num uint8) *SessionInstance {
  305. s.mutex.RLock()
  306. defer s.mutex.RUnlock()
  307. return s.instances[num]
  308. }
  309. // Invisible returns true if all instances are invisible.
  310. func (s *Session) Invisible() bool {
  311. s.mutex.RLock()
  312. defer s.mutex.RUnlock()
  313. for _, instance := range s.instances {
  314. if !instance.Invisible() {
  315. return false
  316. }
  317. }
  318. return true
  319. }
  320. // mostRecentIdleTime returns the most recent idle time from all instances.
  321. func (s *Session) mostRecentIdleTime() time.Time {
  322. var mostRecent time.Time
  323. for _, instance := range s.Instances() {
  324. if mostRecent.IsZero() || (instance.Idle() && instance.IdleTime().After(mostRecent)) {
  325. mostRecent = instance.IdleTime()
  326. }
  327. }
  328. return mostRecent
  329. }
  330. //
  331. // Rate Limiting / Warning
  332. //
  333. // EvaluateRateLimit checks and updates the rate limit state.
  334. func (s *Session) EvaluateRateLimit(now time.Time, rateClassID wire.RateLimitClassID) wire.RateLimitStatus {
  335. if s.AllUserInfoBitmask(wire.OServiceUserFlagBot) {
  336. return wire.RateLimitStatusClear // don't rate limit bots
  337. }
  338. s.mutex.Lock()
  339. rateClass := &s.rateLimitStates[rateClassID-1]
  340. status, newLevel := wire.CheckRateLimit(rateClass.LastTime, now, rateClass.RateClass, rateClass.CurrentLevel, rateClass.LimitedNow)
  341. rateClass.CurrentLevel = newLevel
  342. rateClass.CurrentStatus = status
  343. rateClass.LastTime = now
  344. rateClass.LimitedNow = status == wire.RateLimitStatusLimited
  345. s.mutex.Unlock()
  346. if status == wire.RateLimitStatusDisconnect {
  347. s.CloseSession()
  348. }
  349. return status
  350. }
  351. // ObserveRateChanges updates rate limit states and returns changes.
  352. func (s *Session) ObserveRateChanges(now time.Time) (classDelta []RateClassState, stateDelta []RateClassState) {
  353. s.mutex.Lock()
  354. defer s.mutex.Unlock()
  355. for i, params := range s.rateLimitStates {
  356. if !params.Subscribed {
  357. continue
  358. }
  359. state, level := wire.CheckRateLimit(params.LastTime, now, params.RateClass, params.CurrentLevel, params.LimitedNow)
  360. s.rateLimitStates[i].CurrentStatus = state
  361. // clear limited now flag if passing from limited state to clear state
  362. if s.rateLimitStates[i].LimitedNow && state == wire.RateLimitStatusClear {
  363. s.rateLimitStates[i].LimitedNow = false
  364. s.rateLimitStates[i].CurrentLevel = level
  365. }
  366. // did rate class change?
  367. if params.RateClass != s.lastObservedStates[i].RateClass {
  368. classDelta = append(classDelta, s.rateLimitStates[i])
  369. }
  370. // did rate limit status change?
  371. if s.lastObservedStates[i].CurrentStatus != s.rateLimitStates[i].CurrentStatus {
  372. stateDelta = append(stateDelta, s.rateLimitStates[i])
  373. }
  374. // save it for next time
  375. s.lastObservedStates[i] = s.rateLimitStates[i]
  376. }
  377. return classDelta, stateDelta
  378. }
  379. // RateLimitStates returns the current rate limit states (shared across all sessions).
  380. func (s *Session) RateLimitStates() [5]RateClassState {
  381. s.mutex.RLock()
  382. defer s.mutex.RUnlock()
  383. return s.rateLimitStates
  384. }
  385. // SetRateClasses sets the rate limit classes (shared across all sessions).
  386. func (s *Session) SetRateClasses(now time.Time, classes wire.RateLimitClasses) {
  387. s.mutex.Lock()
  388. defer s.mutex.Unlock()
  389. var newStates [5]RateClassState
  390. for i, class := range classes.All() {
  391. newStates[i] = RateClassState{
  392. CurrentLevel: class.MaxLevel,
  393. CurrentStatus: wire.RateLimitStatusClear,
  394. LastTime: now,
  395. RateClass: class,
  396. Subscribed: s.lastObservedStates[i].Subscribed,
  397. }
  398. }
  399. if s.lastObservedStates[0].ID == 0 {
  400. s.lastObservedStates = newStates
  401. } else {
  402. s.lastObservedStates = s.rateLimitStates
  403. }
  404. s.rateLimitStates = newStates
  405. s.rateLimitStatesOriginal = newStates
  406. }
  407. // SetWarning sets the user's warning level (shared across all sessions).
  408. func (s *Session) SetWarning(warning uint16) {
  409. s.mutex.Lock()
  410. defer s.mutex.Unlock()
  411. s.warning = warning
  412. }
  413. // ScaleWarningAndRateLimit increments the user's warning level and scales rate limits.
  414. func (s *Session) ScaleWarningAndRateLimit(incr int16, classID wire.RateLimitClassID) (bool, uint16) {
  415. s.mutex.Lock()
  416. defer s.mutex.Unlock()
  417. // Handle warning level increment
  418. newWarning := int32(s.warning) + int32(incr)
  419. if newWarning > 1000 {
  420. return false, 0
  421. }
  422. if newWarning < 0 {
  423. s.warning = 0 // clamp min at 0
  424. } else {
  425. s.warning = uint16(newWarning)
  426. }
  427. pct := float32(incr) / 1000.0
  428. // create reference variables for better readability
  429. rateClass := &s.rateLimitStates[classID-1]
  430. originalRateClass := &s.rateLimitStatesOriginal[classID-1]
  431. // clamp function to constrain values between min and max
  432. clamp := func(value, min, max int32) int32 {
  433. if value < min {
  434. return min
  435. }
  436. if value > max {
  437. return max
  438. }
  439. return value
  440. }
  441. // Apply a buffer to limit/clear/alert levels so that they never approach
  442. // too close to the maximum level. Otherwise, AIM 4.8 exhibits instability
  443. // (client crashes, IM window glitches) when the warning level reaches 90-100%.
  444. maxLevel := originalRateClass.MaxLevel - 150
  445. // scale the rate limit parameters
  446. newLimitLevel := rateClass.LimitLevel + int32(float32(maxLevel-originalRateClass.LimitLevel)*pct)
  447. rateClass.LimitLevel = clamp(newLimitLevel, originalRateClass.LimitLevel, originalRateClass.MaxLevel)
  448. newLimitLevel = rateClass.ClearLevel + int32(float32(maxLevel-originalRateClass.ClearLevel)*pct)
  449. rateClass.ClearLevel = clamp(newLimitLevel, originalRateClass.ClearLevel, originalRateClass.MaxLevel)
  450. newLimitLevel = rateClass.AlertLevel + int32(float32(maxLevel-originalRateClass.AlertLevel)*pct)
  451. rateClass.AlertLevel = clamp(newLimitLevel, originalRateClass.AlertLevel, originalRateClass.MaxLevel)
  452. s.warningCh <- s.warning
  453. return true, s.warning
  454. }
  455. // SubscribeRateLimits subscribes to rate limit updates.
  456. func (s *Session) SubscribeRateLimits(classes []wire.RateLimitClassID) {
  457. s.mutex.Lock()
  458. defer s.mutex.Unlock()
  459. for _, classID := range classes {
  460. s.rateLimitStates[classID-1].Subscribed = true
  461. }
  462. }
  463. // Warning returns the user's current warning level.
  464. func (s *Session) Warning() uint16 {
  465. s.mutex.RLock()
  466. defer s.mutex.RUnlock()
  467. return s.warning
  468. }
  469. // WarningCh returns the warning notification channel.
  470. func (s *Session) WarningCh() chan uint16 {
  471. return s.warningCh
  472. }
  473. //
  474. // Lifecycle
  475. //
  476. // CloseSession closes all instances in the session.
  477. func (s *Session) CloseSession() {
  478. s.mutex.RLock()
  479. instances := make([]*SessionInstance, 0, len(s.instances))
  480. for _, instance := range s.instances {
  481. instances = append(instances, instance)
  482. }
  483. s.mutex.RUnlock()
  484. for _, instance := range instances {
  485. instance.closeOnly()
  486. }
  487. }
  488. // OnSessionClose registers a function to be called once all instances have closed.
  489. func (s *Session) OnSessionClose(fn func()) {
  490. s.mutex.Lock()
  491. defer s.mutex.Unlock()
  492. s.onSessCloseFn = fn
  493. }
  494. // RunOnce executes the given function once across all invocations. Used to
  495. // run arbitrary code that must only run once when the first session instance
  496. // connects. The function must not block.
  497. func (s *Session) RunOnce(fn func() error) error {
  498. var err error
  499. s.initOnce.Do(func() {
  500. err = fn()
  501. })
  502. return err
  503. }
  504. // SetNowFn sets the function used to get the current time. This is useful for testing.
  505. func (s *Session) SetNowFn(fn func() time.Time) {
  506. s.mutex.Lock()
  507. defer s.mutex.Unlock()
  508. s.nowFn = fn
  509. }
  510. //
  511. // User Settings / Attributes
  512. //
  513. // AllUserInfoBitmask returns whether all instances have user info flag set.
  514. func (s *Session) AllUserInfoBitmask(flag uint16) bool {
  515. for _, instance := range s.Instances() {
  516. if instance.UserInfoBitmask()&flag != flag {
  517. return false
  518. }
  519. }
  520. return true
  521. }
  522. // AllUserStatusBitmask returns whether all instances have user status flag set.
  523. func (s *Session) AllUserStatusBitmask(flag uint32) bool {
  524. for _, instance := range s.Instances() {
  525. if instance.UserStatusBitmask()&flag != flag {
  526. return false
  527. }
  528. }
  529. return true
  530. }
  531. // BuddyIcon returns the session's buddy icon metadata and reports whether it
  532. // has been set.
  533. func (s *Session) BuddyIcon() (wire.BARTID, bool) {
  534. s.mutex.RLock()
  535. defer s.mutex.RUnlock()
  536. icon := s.buddyIcon
  537. return icon, icon.Type != 0
  538. }
  539. // Caps returns the union of all capability UUIDs from all instances in the session.
  540. func (s *Session) Caps() [][16]byte {
  541. s.mutex.RLock()
  542. defer s.mutex.RUnlock()
  543. caps := make(map[[16]byte]bool)
  544. for _, instance := range s.instances {
  545. for _, c := range instance.caps() {
  546. caps[c] = true
  547. }
  548. }
  549. ret := make([][16]byte, 0, len(caps))
  550. for c := range caps {
  551. ret = append(ret, c)
  552. }
  553. return ret
  554. }
  555. // MemberSince reports when the user became a member.
  556. func (s *Session) MemberSince() time.Time {
  557. s.mutex.RLock()
  558. defer s.mutex.RUnlock()
  559. return s.memberSince
  560. }
  561. // OfflineMsgCount returns the offline message count.
  562. func (s *Session) OfflineMsgCount() int {
  563. s.mutex.RLock()
  564. defer s.mutex.RUnlock()
  565. return s.offlineMsgCount
  566. }
  567. // Profile returns the most recently updated non-empty profile from all instances.
  568. func (s *Session) Profile() UserProfile {
  569. var latest UserProfile
  570. for _, instance := range s.Instances() {
  571. profile := instance.Profile()
  572. if profile.IsEmpty() {
  573. continue
  574. }
  575. if latest.IsEmpty() || profile.UpdateTime.After(latest.UpdateTime) {
  576. latest = profile
  577. }
  578. }
  579. return latest
  580. }
  581. // SetBuddyIcon stores the session's buddy icon metadata.
  582. func (s *Session) SetBuddyIcon(icon wire.BARTID) {
  583. s.mutex.Lock()
  584. defer s.mutex.Unlock()
  585. s.buddyIcon = icon
  586. }
  587. // SetChatRoomCookie sets the chat room cookie.
  588. func (s *Session) SetChatRoomCookie(cookie string) {
  589. s.mutex.Lock()
  590. defer s.mutex.Unlock()
  591. s.chatRoomCookie = cookie
  592. }
  593. // SetMemberSince sets the member since timestamp.
  594. func (s *Session) SetMemberSince(t time.Time) {
  595. s.mutex.Lock()
  596. defer s.mutex.Unlock()
  597. s.memberSince = t
  598. }
  599. // SetOfflineMsgCount sets the offline message count.
  600. func (s *Session) SetOfflineMsgCount(count int) {
  601. s.mutex.Lock()
  602. defer s.mutex.Unlock()
  603. s.offlineMsgCount = count
  604. }
  605. // SetSignonTime sets the session's sign-on time.
  606. func (s *Session) SetSignonTime(t time.Time) {
  607. s.mutex.Lock()
  608. defer s.mutex.Unlock()
  609. s.signonTime = t
  610. }
  611. // SetTypingEventsEnabled sets whether the session wants to send and receive typing events.
  612. func (s *Session) SetTypingEventsEnabled(enabled bool) {
  613. s.mutex.Lock()
  614. defer s.mutex.Unlock()
  615. s.typingEventsEnabled = enabled
  616. }
  617. // SignonTime returns the session's sign-on time.
  618. func (s *Session) SignonTime() time.Time {
  619. s.mutex.RLock()
  620. defer s.mutex.RUnlock()
  621. return s.signonTime
  622. }
  623. // TLVUserInfo returns a TLV list containing session information aggregated from all instances.
  624. func (s *Session) TLVUserInfo() wire.TLVUserInfo {
  625. return wire.TLVUserInfo{
  626. ScreenName: s.DisplayScreenName().String(),
  627. WarningLevel: s.Warning(),
  628. TLVBlock: wire.TLVBlock{
  629. TLVList: s.userInfo(),
  630. },
  631. }
  632. }
  633. // TypingEventsEnabled indicates whether the session wants to send and receive typing events.
  634. func (s *Session) TypingEventsEnabled() bool {
  635. s.mutex.RLock()
  636. defer s.mutex.RUnlock()
  637. return s.typingEventsEnabled
  638. }
  639. func (s *Session) userInfo() wire.TLVList {
  640. tlvs := wire.TLVList{}
  641. // sign-in timestamp
  642. tlvs.Append(wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(s.SignonTime().Unix())))
  643. instances := s.Instances()
  644. // Use the first instance as a template for user flags. Most flags are static
  645. // and should be consistent across all instances; only the "away" flag may vary.
  646. // If instances differ in protocol type (ICQ vs AIM), that indicates an error.
  647. var baseUserFlags uint16
  648. if len(instances) > 0 {
  649. baseUserFlags = instances[0].UserInfoBitmask()
  650. }
  651. if s.Away() {
  652. baseUserFlags |= wire.OServiceUserFlagUnavailable
  653. } else {
  654. baseUserFlags &^= wire.OServiceUserFlagUnavailable
  655. }
  656. tlvs.Append(wire.NewTLVBE(wire.OServiceUserInfoUserFlags, baseUserFlags))
  657. // user status flags - user-level (shared)
  658. var statusBitmask uint32
  659. if len(instances) > 0 {
  660. statusBitmask = instances[0].UserStatusBitmask()
  661. for _, instance := range instances {
  662. statusBitmask &= instance.UserStatusBitmask()
  663. }
  664. }
  665. tlvs.Append(wire.NewTLVBE(wire.OServiceUserInfoStatus, statusBitmask))
  666. // idle status - use most recent idle time if all instances are idle
  667. if s.Idle() {
  668. mostRecentIdleTime := s.mostRecentIdleTime()
  669. tlvs.Append(wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(s.nowFn().Sub(mostRecentIdleTime).Minutes())))
  670. }
  671. // set buddy icon metadata, if user has buddy icon
  672. if icon, hasIcon := s.BuddyIcon(); hasIcon {
  673. tlvs.Append(wire.NewTLVBE(wire.OServiceUserInfoBARTInfo, icon))
  674. }
  675. // ICQ direct-connect info. The TLV is required for buddy arrival events to
  676. // work in ICQ, even if the values are set to default.
  677. if baseUserFlags&wire.OServiceUserFlagICQ == wire.OServiceUserFlagICQ {
  678. tlvs.Append(wire.NewTLVBE(wire.OServiceUserInfoICQDC, wire.ICQDCInfo{}))
  679. }
  680. caps := s.Caps()
  681. if len(caps) > 0 {
  682. tlvs.Append(wire.NewTLVBE(wire.OServiceUserInfoOscarCaps, caps))
  683. }
  684. tlvs.Append(wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)))
  685. return tlvs
  686. }
  687. // SessionInstance represents a single client connection instance within a user's
  688. // session. Multiple SessionInstance objects can belong to the same Session,
  689. // allowing a user to maintain concurrent connections from different clients or
  690. // devices.
  691. //
  692. // SessionInstance stores connection-specific state such as the remote address,
  693. // sign-on completion status, client capabilities, idle state, and per-connection
  694. // profile data. It holds a reference to its parent Session to access shared
  695. // user-level data like identity, warning levels, and rate limiting state.
  696. //
  697. // All methods on SessionInstance are safe for concurrent use.
  698. type SessionInstance struct {
  699. session *Session
  700. mutex sync.RWMutex
  701. // Unique instance identifier
  702. instanceNum uint8
  703. // Per-session connection state
  704. remoteAddr *netip.AddrPort
  705. signonComplete bool
  706. closed bool
  707. stopCh chan struct{}
  708. msgCh chan wire.SNACMessage
  709. kerberosAuth bool
  710. // Per-session client information
  711. clientID string
  712. capabilities [][16]byte
  713. foodGroupVersions [wire.MDir + 1]uint16
  714. multiConnFlag wire.MultiConnFlag
  715. // Per-session state
  716. idle bool
  717. idleTime time.Time
  718. awayMsg string
  719. userInfoBitmask uint16
  720. userStatusBitmask uint32
  721. // Per-session profile
  722. profile UserProfile
  723. awayTime time.Time
  724. onInstanceCloseFn func()
  725. }
  726. // Session returns the parent Session for this instance.
  727. func (s *SessionInstance) Session() *Session {
  728. return s.session
  729. }
  730. //
  731. // Identity
  732. //
  733. // ChatRoomCookie returns the chat room cookie from the parent session.
  734. func (s *SessionInstance) ChatRoomCookie() string {
  735. return s.session.ChatRoomCookie()
  736. }
  737. // ClientID retrieves the instance's client ID.
  738. func (s *SessionInstance) ClientID() string {
  739. s.mutex.RLock()
  740. defer s.mutex.RUnlock()
  741. return s.clientID
  742. }
  743. // DisplayScreenName returns the user's display screen name.
  744. func (s *SessionInstance) DisplayScreenName() DisplayScreenName {
  745. return s.session.DisplayScreenName()
  746. }
  747. // IdentScreenName returns the user's identity screen name.
  748. func (s *SessionInstance) IdentScreenName() IdentScreenName {
  749. return s.session.IdentScreenName()
  750. }
  751. // Num returns the unique instance identifier.
  752. func (s *SessionInstance) Num() uint8 {
  753. return s.instanceNum
  754. }
  755. // UIN returns the user's ICQ number.
  756. func (s *SessionInstance) UIN() uint32 {
  757. return s.session.UIN()
  758. }
  759. // SetClientID sets the instance's client ID.
  760. func (s *SessionInstance) SetClientID(clientID string) {
  761. s.mutex.Lock()
  762. defer s.mutex.Unlock()
  763. s.clientID = clientID
  764. }
  765. //
  766. // Status / Availability
  767. //
  768. // Away returns true if the instance is away.
  769. func (s *SessionInstance) Away() bool {
  770. s.mutex.RLock()
  771. defer s.mutex.RUnlock()
  772. return s.away()
  773. }
  774. // AwayMessage returns the instance's away message and the time it was set.
  775. func (s *SessionInstance) AwayMessage() (string, time.Time) {
  776. s.mutex.RLock()
  777. defer s.mutex.RUnlock()
  778. return s.awayMsg, s.awayTime
  779. }
  780. // Idle reports the instance's idle state.
  781. func (s *SessionInstance) Idle() bool {
  782. s.mutex.RLock()
  783. defer s.mutex.RUnlock()
  784. return s.idle
  785. }
  786. // IdleTime reports when the instance went idle.
  787. func (s *SessionInstance) IdleTime() time.Time {
  788. s.mutex.RLock()
  789. defer s.mutex.RUnlock()
  790. return s.idleTime
  791. }
  792. // Invisible returns true if the user is invisible.
  793. func (s *SessionInstance) Invisible() bool {
  794. s.mutex.RLock()
  795. defer s.mutex.RUnlock()
  796. return s.userStatusBitmask&wire.OServiceUserStatusInvisible == wire.OServiceUserStatusInvisible
  797. }
  798. // SetIdle sets the instance's idle state.
  799. func (s *SessionInstance) SetIdle(dur time.Duration) {
  800. s.mutex.Lock()
  801. defer s.mutex.Unlock()
  802. s.idle = true
  803. // set the time the instance became idle
  804. s.idleTime = s.session.nowFn().Add(-dur)
  805. }
  806. // SetSignonComplete indicates that the instance has completed the sign-on sequence.
  807. func (s *SessionInstance) SetSignonComplete() {
  808. s.mutex.Lock()
  809. defer s.mutex.Unlock()
  810. s.signonComplete = true
  811. }
  812. // SignonComplete indicates whether the instance has completed the sign-on sequence.
  813. func (s *SessionInstance) SignonComplete() bool {
  814. s.mutex.RLock()
  815. defer s.mutex.RUnlock()
  816. return s.signonComplete
  817. }
  818. // UnsetIdle removes the instance's idle state.
  819. func (s *SessionInstance) UnsetIdle() {
  820. s.mutex.Lock()
  821. defer s.mutex.Unlock()
  822. s.idle = false
  823. }
  824. // active returns true if the instance is active. An instance is considered active if:
  825. // - it is not closed
  826. // - it has completed the sign-on sequence
  827. // - it is not idle
  828. // - it is not away
  829. func (s *SessionInstance) active() bool {
  830. s.mutex.RLock()
  831. defer s.mutex.RUnlock()
  832. return !s.closed && s.signonComplete && !s.idle && !s.away()
  833. }
  834. // away checks if the instance is away based on bitmask flags.
  835. // This method must be called while holding the mutex lock.
  836. func (s *SessionInstance) away() bool {
  837. return s.userInfoBitmask&wire.OServiceUserFlagUnavailable != 0 ||
  838. s.userStatusBitmask&wire.OServiceUserStatusAway != 0
  839. }
  840. // live returns whether the instance is ready to receive messages.
  841. func (s *SessionInstance) live() bool {
  842. s.mutex.RLock()
  843. defer s.mutex.RUnlock()
  844. return !s.closed && s.signonComplete
  845. }
  846. //
  847. // Rate Limiting / Warning
  848. //
  849. // RateLimitStates returns the current rate limit states.
  850. func (s *SessionInstance) RateLimitStates() [5]RateClassState {
  851. return s.session.RateLimitStates()
  852. }
  853. // Warning returns the user's current warning level.
  854. func (s *SessionInstance) Warning() uint16 {
  855. return s.session.Warning()
  856. }
  857. // WarningCh returns the warning notification channel.
  858. func (s *SessionInstance) WarningCh() chan uint16 {
  859. return s.session.WarningCh()
  860. }
  861. //
  862. // Lifecycle
  863. //
  864. // Closed blocks until the instance is closed.
  865. func (s *SessionInstance) Closed() <-chan struct{} {
  866. return s.stopCh
  867. }
  868. // CloseInstance shuts down the instance's ability to relay messages and removes it from the session.
  869. func (s *SessionInstance) CloseInstance() {
  870. s.mutex.Lock()
  871. if s.closed {
  872. s.mutex.Unlock()
  873. return
  874. }
  875. close(s.stopCh)
  876. s.closed = true
  877. onInstanceCloseFn := s.onInstanceCloseFn
  878. s.mutex.Unlock()
  879. s.session.RemoveInstance(s)
  880. count := s.session.InstanceCount()
  881. if count == 0 {
  882. s.session.mutex.RLock()
  883. onSessCloseFn := s.session.onSessCloseFn
  884. s.session.mutex.RUnlock()
  885. onSessCloseFn()
  886. } else {
  887. onInstanceCloseFn()
  888. }
  889. }
  890. // OnClose registers a function to be called when the instance closes,
  891. // but only if other instances remain in the session. If this is the last instance
  892. // to close, OnSessionClose will be called instead.
  893. func (s *SessionInstance) OnClose(fn func()) {
  894. s.mutex.Lock()
  895. defer s.mutex.Unlock()
  896. s.onInstanceCloseFn = fn
  897. }
  898. // CloseInstance shuts down the instance's ability to relay messages.
  899. func (s *SessionInstance) closeOnly() {
  900. s.mutex.Lock()
  901. if s.closed {
  902. s.mutex.Unlock()
  903. return
  904. }
  905. close(s.stopCh)
  906. s.closed = true
  907. s.mutex.Unlock()
  908. s.session.RemoveInstance(s)
  909. if s.session.InstanceCount() == 0 {
  910. s.session.mutex.RLock()
  911. onSessCloseFn := s.session.onSessCloseFn
  912. s.session.mutex.RUnlock()
  913. onSessCloseFn()
  914. }
  915. }
  916. //
  917. // User Settings / Attributes
  918. //
  919. // ClearUserInfoFlag clears a flag from the user info bitmask.
  920. func (s *SessionInstance) ClearUserInfoFlag(flag uint16) (flags uint16) {
  921. s.mutex.Lock()
  922. defer s.mutex.Unlock()
  923. s.userInfoBitmask &^= flag
  924. return s.userInfoBitmask
  925. }
  926. // FoodGroupVersions retrieves the instance's supported food group versions.
  927. func (s *SessionInstance) FoodGroupVersions() [wire.MDir + 1]uint16 {
  928. s.mutex.RLock()
  929. defer s.mutex.RUnlock()
  930. return s.foodGroupVersions
  931. }
  932. // KerberosAuth indicates whether Kerberos authentication was used for this instance.
  933. func (s *SessionInstance) KerberosAuth() bool {
  934. s.mutex.RLock()
  935. defer s.mutex.RUnlock()
  936. return s.kerberosAuth
  937. }
  938. // MultiConnFlag retrieves the multi-connection flag for this instance.
  939. func (s *SessionInstance) MultiConnFlag() wire.MultiConnFlag {
  940. s.mutex.RLock()
  941. defer s.mutex.RUnlock()
  942. return s.multiConnFlag
  943. }
  944. // OfflineMsgCount returns the offline message count.
  945. func (s *SessionInstance) OfflineMsgCount() int {
  946. return s.session.OfflineMsgCount()
  947. }
  948. // Profile returns the user's profile information.
  949. func (s *SessionInstance) Profile() UserProfile {
  950. s.mutex.RLock()
  951. defer s.mutex.RUnlock()
  952. return s.profile
  953. }
  954. // RemoteAddr returns the instance's remote IP address.
  955. func (s *SessionInstance) RemoteAddr() (remoteAddr *netip.AddrPort) {
  956. s.mutex.RLock()
  957. defer s.mutex.RUnlock()
  958. return s.remoteAddr
  959. }
  960. // SetAwayMessage sets the instance's away message.
  961. func (s *SessionInstance) SetAwayMessage(awayMessage string) {
  962. s.mutex.Lock()
  963. defer s.mutex.Unlock()
  964. s.awayMsg = awayMessage
  965. }
  966. // SetCaps sets capability UUIDs for the instance.
  967. func (s *SessionInstance) SetCaps(caps [][16]byte) {
  968. s.mutex.Lock()
  969. defer s.mutex.Unlock()
  970. s.capabilities = caps
  971. }
  972. // SetFoodGroupVersions sets the instance's supported food group versions.
  973. func (s *SessionInstance) SetFoodGroupVersions(versions [wire.MDir + 1]uint16) {
  974. s.mutex.Lock()
  975. defer s.mutex.Unlock()
  976. s.foodGroupVersions = versions
  977. }
  978. // SetKerberosAuth sets whether Kerberos authentication was used for this instance.
  979. func (s *SessionInstance) SetKerberosAuth(enabled bool) {
  980. s.mutex.Lock()
  981. defer s.mutex.Unlock()
  982. s.kerberosAuth = enabled
  983. }
  984. // SetMultiConnFlag sets the multi-connection flag for this instance.
  985. func (s *SessionInstance) SetMultiConnFlag(flag wire.MultiConnFlag) {
  986. s.mutex.Lock()
  987. defer s.mutex.Unlock()
  988. s.multiConnFlag = flag
  989. }
  990. // SetProfile sets the user's profile information.
  991. func (s *SessionInstance) SetProfile(profile UserProfile) {
  992. s.mutex.Lock()
  993. defer s.mutex.Unlock()
  994. s.profile = profile
  995. }
  996. // SetRemoteAddr sets the instance's remote IP address.
  997. func (s *SessionInstance) SetRemoteAddr(remoteAddr *netip.AddrPort) {
  998. s.mutex.Lock()
  999. defer s.mutex.Unlock()
  1000. s.remoteAddr = remoteAddr
  1001. }
  1002. // SetUserInfoFlag sets a flag on the user info bitmask.
  1003. func (s *SessionInstance) SetUserInfoFlag(flag uint16) {
  1004. s.mutex.Lock()
  1005. defer s.mutex.Unlock()
  1006. if flag == wire.OServiceUserFlagUnavailable {
  1007. s.awayTime = s.session.nowFn()
  1008. }
  1009. s.userInfoBitmask |= flag
  1010. }
  1011. // SetUserStatusBitmask sets the user status bitmask.
  1012. func (s *SessionInstance) SetUserStatusBitmask(bitmask uint32) {
  1013. s.mutex.Lock()
  1014. defer s.mutex.Unlock()
  1015. if bitmask&wire.OServiceUserStatusAway == wire.OServiceUserStatusAway {
  1016. if !s.away() {
  1017. s.awayTime = s.session.nowFn()
  1018. }
  1019. }
  1020. s.userStatusBitmask = bitmask
  1021. }
  1022. // SignonTime returns the session's sign-on time.
  1023. func (s *SessionInstance) SignonTime() time.Time {
  1024. return s.session.SignonTime()
  1025. }
  1026. // TypingEventsEnabled indicates whether the session wants to send and receive typing events.
  1027. func (s *SessionInstance) TypingEventsEnabled() bool {
  1028. return s.session.TypingEventsEnabled()
  1029. }
  1030. // UserInfoBitmask returns the user info bitmask.
  1031. func (s *SessionInstance) UserInfoBitmask() uint16 {
  1032. s.mutex.RLock()
  1033. defer s.mutex.RUnlock()
  1034. return s.userInfoBitmask
  1035. }
  1036. // UserStatusBitmask returns the user status bitmask.
  1037. func (s *SessionInstance) UserStatusBitmask() uint32 {
  1038. s.mutex.RLock()
  1039. defer s.mutex.RUnlock()
  1040. return s.userStatusBitmask
  1041. }
  1042. // caps retrieves instance capabilities.
  1043. func (s *SessionInstance) caps() [][16]byte {
  1044. s.mutex.RLock()
  1045. defer s.mutex.RUnlock()
  1046. return s.capabilities
  1047. }
  1048. //
  1049. // Message Sending
  1050. //
  1051. // ReceiveMessage returns a channel of messages relayed via this instance.
  1052. func (s *SessionInstance) ReceiveMessage() chan wire.SNACMessage {
  1053. return s.msgCh
  1054. }
  1055. // RelayMessageToInstance receives a SNAC message and passes it to the instance's message channel.
  1056. func (s *SessionInstance) RelayMessageToInstance(msg wire.SNACMessage) SessSendStatus {
  1057. s.mutex.RLock()
  1058. defer s.mutex.RUnlock()
  1059. if s.closed {
  1060. return SessSendClosed
  1061. }
  1062. select {
  1063. case s.msgCh <- msg:
  1064. return SessSendOK
  1065. case <-s.stopCh:
  1066. return SessSendClosed
  1067. default:
  1068. return SessQueueFull
  1069. }
  1070. }