session.go 35 KB

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