session_test.go 78 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678
  1. package state
  2. import (
  3. "context"
  4. "fmt"
  5. "math"
  6. "net/netip"
  7. "sync"
  8. "testing"
  9. "testing/synctest"
  10. "time"
  11. "github.com/google/uuid"
  12. "github.com/mk6i/open-oscar-server/wire"
  13. "github.com/stretchr/testify/assert"
  14. "github.com/stretchr/testify/require"
  15. )
  16. func TestSession_UsesFeedbag(t *testing.T) {
  17. s := NewSession()
  18. if s.UsesFeedbag() {
  19. t.Fatalf("UsesFeedbag() = true; want false")
  20. }
  21. s.SetUsesFeedbag()
  22. if !s.UsesFeedbag() {
  23. t.Fatalf("UsesFeedbag() = false; want true")
  24. }
  25. // idempotent
  26. s.SetUsesFeedbag()
  27. if !s.UsesFeedbag() {
  28. t.Fatalf("UsesFeedbag() = false after second SetUsesFeedbag; want true")
  29. }
  30. }
  31. func TestSessionInstance_NotifyTxn(t *testing.T) {
  32. alice := NewIdentScreenName("Alice")
  33. bob := NewIdentScreenName("Bob")
  34. t.Run("lifecycle", func(t *testing.T) {
  35. inst := NewSession().AddInstance()
  36. inst.BeginNotifyTxn()
  37. assert.True(t, inst.InNotifyTxn())
  38. require.NoError(t, inst.NotifyTxn(alice, bob))
  39. shouldNotify, screenNames := inst.EndNotifyTxn()
  40. assert.True(t, shouldNotify)
  41. assert.ElementsMatch(t, []IdentScreenName{alice, bob}, screenNames)
  42. assert.False(t, inst.InNotifyTxn())
  43. shouldNotify, screenNames = inst.EndNotifyTxn()
  44. assert.False(t, shouldNotify)
  45. assert.Nil(t, screenNames)
  46. })
  47. t.Run("clear on begin", func(t *testing.T) {
  48. inst := NewSession().AddInstance()
  49. inst.BeginNotifyTxn()
  50. require.NoError(t, inst.NotifyTxn(alice))
  51. inst.BeginNotifyTxn()
  52. shouldNotify, screenNames := inst.EndNotifyTxn()
  53. assert.False(t, shouldNotify)
  54. assert.Empty(t, screenNames)
  55. })
  56. t.Run("notify without names", func(t *testing.T) {
  57. inst := NewSession().AddInstance()
  58. inst.BeginNotifyTxn()
  59. require.NoError(t, inst.NotifyTxn())
  60. shouldNotify, screenNames := inst.EndNotifyTxn()
  61. assert.True(t, shouldNotify)
  62. assert.Empty(t, screenNames)
  63. })
  64. t.Run("inactive notify", func(t *testing.T) {
  65. inst := NewSession().AddInstance()
  66. assert.ErrorIs(t, inst.NotifyTxn(alice), errNotifyTxnNotActive)
  67. inst.BeginNotifyTxn()
  68. shouldNotify, screenNames := inst.EndNotifyTxn()
  69. assert.False(t, shouldNotify)
  70. assert.Empty(t, screenNames)
  71. })
  72. t.Run("dedup", func(t *testing.T) {
  73. inst := NewSession().AddInstance()
  74. inst.BeginNotifyTxn()
  75. require.NoError(t, inst.NotifyTxn(alice, alice))
  76. shouldNotify, screenNames := inst.EndNotifyTxn()
  77. assert.True(t, shouldNotify)
  78. assert.Equal(t, []IdentScreenName{alice}, screenNames)
  79. })
  80. t.Run("exceeds max names", func(t *testing.T) {
  81. inst := NewSession().AddInstance()
  82. inst.BeginNotifyTxn()
  83. names := make([]IdentScreenName, maxNotifyTxnNames)
  84. for i := range names {
  85. names[i] = NewIdentScreenName(fmt.Sprintf("user%d", i))
  86. }
  87. require.NoError(t, inst.NotifyTxn(names...))
  88. shouldNotify, screenNames := inst.EndNotifyTxn()
  89. assert.True(t, shouldNotify)
  90. assert.Len(t, screenNames, maxNotifyTxnNames)
  91. inst.BeginNotifyTxn()
  92. require.NoError(t, inst.NotifyTxn(names...))
  93. assert.ErrorIs(t, inst.NotifyTxn(NewIdentScreenName("overflow")), errNotifyTxnTooManyNames)
  94. })
  95. }
  96. func TestSession_IncrementAndGetWarning(t *testing.T) {
  97. s := NewSession().AddInstance()
  98. var wg sync.WaitGroup
  99. wg.Add(1)
  100. go func() {
  101. defer wg.Done()
  102. s.Session().ScaleWarningAndRateLimit(1, 1)
  103. s.Session().ScaleWarningAndRateLimit(2, 1)
  104. s.Session().ScaleWarningAndRateLimit(3, 1)
  105. }()
  106. assert.Equal(t, uint16(1), <-s.WarningCh())
  107. assert.Equal(t, uint16(3), <-s.WarningCh())
  108. assert.Equal(t, uint16(6), <-s.WarningCh())
  109. wg.Wait()
  110. }
  111. func TestSession_SetAndGetInvisible(t *testing.T) {
  112. s := NewSession().AddInstance()
  113. assert.False(t, s.Invisible())
  114. s.SetUserStatusBitmask(wire.OServiceUserStatusInvisible)
  115. assert.True(t, s.Invisible())
  116. }
  117. func TestSession_SetAndGetScreenName(t *testing.T) {
  118. s := NewSession().AddInstance()
  119. assert.Empty(t, s.IdentScreenName())
  120. sn := NewIdentScreenName("user-screen-name")
  121. s.Session().SetIdentScreenName(sn)
  122. assert.Equal(t, sn, s.IdentScreenName())
  123. }
  124. func TestSession_SetAndGetChatRoomCookie(t *testing.T) {
  125. s := NewSession().AddInstance()
  126. assert.Empty(t, s.ChatRoomCookie())
  127. sn := "the-chat-cookie"
  128. s.Session().SetChatRoomCookie(sn)
  129. assert.Equal(t, sn, s.ChatRoomCookie())
  130. }
  131. func TestSession_SetAndGetUIN(t *testing.T) {
  132. s := NewSession().AddInstance()
  133. assert.Empty(t, s.UIN())
  134. uin := uint32(100003)
  135. s.Session().SetUIN(uin)
  136. assert.Equal(t, uin, s.UIN())
  137. }
  138. func TestSession_SetAndGetClientID(t *testing.T) {
  139. s := NewSession().AddInstance()
  140. assert.Empty(t, s.ClientID())
  141. clientID := "AIM Client ID"
  142. s.SetClientID(clientID)
  143. assert.Equal(t, clientID, s.ClientID())
  144. }
  145. func TestSession_SetAndGetKerberosAuth(t *testing.T) {
  146. s := NewSession().AddInstance()
  147. assert.False(t, s.KerberosAuth())
  148. s.SetKerberosAuth(true)
  149. assert.True(t, s.KerberosAuth())
  150. s.SetKerberosAuth(false)
  151. assert.False(t, s.KerberosAuth())
  152. }
  153. func TestSession_SetAndGetRemoteAddr(t *testing.T) {
  154. s := NewSession().AddInstance()
  155. assert.Empty(t, s.RemoteAddr())
  156. remoteAddr, _ := netip.ParseAddrPort("1.2.3.4:1234")
  157. s.SetRemoteAddr(&remoteAddr)
  158. assert.Equal(t, &remoteAddr, s.RemoteAddr())
  159. }
  160. var testBuddyIcon = wire.BARTID{
  161. Type: wire.BARTTypesBuddyIcon,
  162. BARTInfo: wire.BARTInfo{
  163. Flags: wire.BARTFlagsData,
  164. Hash: []byte{0xAA, 0xBB, 0xCC},
  165. },
  166. }
  167. var testStatusBART = statusBARTID("brb")
  168. // testClearedStatusBART carries an empty status message, which is how a cleared
  169. // status reaches buddy lists.
  170. var testClearedStatusBART = statusBARTID("")
  171. // statusBARTID builds the BART item that advertises statusMsg as a user's status
  172. // message. It panics only on a message too long to fit the item.
  173. func statusBARTID(statusMsg string) wire.BARTID {
  174. var id wire.BARTID
  175. if err := id.SetStatusText(statusMsg); err != nil {
  176. panic(err)
  177. }
  178. return id
  179. }
  180. func TestSession_Status(t *testing.T) {
  181. s := NewSession()
  182. _, hasStatus := s.Status()
  183. assert.False(t, hasStatus)
  184. s.SetStatus(testStatusBART)
  185. status, hasStatus := s.Status()
  186. assert.True(t, hasStatus)
  187. assert.Equal(t, testStatusBART, status)
  188. // Only the status message BART type reports as a status message.
  189. s.SetStatus(testBuddyIcon)
  190. _, hasStatus = s.Status()
  191. assert.False(t, hasStatus)
  192. }
  193. func TestSession_TLVUserInfo(t *testing.T) {
  194. tests := []struct {
  195. name string
  196. givenSessionFn func() *SessionInstance
  197. want wire.TLVUserInfo
  198. }{
  199. {
  200. name: "user is active and visible",
  201. givenSessionFn: func() *SessionInstance {
  202. s := NewSession().AddInstance()
  203. s.Session().SetSignonTime(time.Unix(1, 0))
  204. s.Session().SetIdentScreenName(NewIdentScreenName("xXAIMUSERXx"))
  205. s.Session().SetDisplayScreenName("xXAIMUSERXx")
  206. s.Session().ScaleWarningAndRateLimit(10, 1)
  207. s.SetUserInfoFlag(wire.OServiceUserFlagOSCARFree)
  208. return s
  209. },
  210. want: wire.TLVUserInfo{
  211. ScreenName: "xXAIMUSERXx",
  212. WarningLevel: 10,
  213. TLVBlock: wire.TLVBlock{
  214. TLVList: wire.TLVList{
  215. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  216. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x0010)),
  217. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  218. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  219. },
  220. },
  221. },
  222. },
  223. {
  224. name: "user is on ICQ",
  225. givenSessionFn: func() *SessionInstance {
  226. s := NewSession().AddInstance()
  227. s.Session().SetSignonTime(time.Unix(1, 0))
  228. s.Session().SetIdentScreenName(NewIdentScreenName("1000003"))
  229. s.Session().SetDisplayScreenName("1000003")
  230. s.SetUserInfoFlag(wire.OServiceUserFlagICQ)
  231. return s
  232. },
  233. want: wire.TLVUserInfo{
  234. ScreenName: "1000003",
  235. TLVBlock: wire.TLVBlock{
  236. TLVList: wire.TLVList{
  237. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  238. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree|wire.OServiceUserFlagICQ),
  239. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  240. wire.NewTLVBE(wire.OServiceUserInfoICQDC, wire.ICQDCInfo{}),
  241. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  242. },
  243. },
  244. },
  245. },
  246. {
  247. name: "user is on ICQ with direct connect info from client",
  248. givenSessionFn: func() *SessionInstance {
  249. s := NewSession().AddInstance()
  250. s.Session().SetSignonTime(time.Unix(1, 0))
  251. s.Session().SetIdentScreenName(NewIdentScreenName("1000003"))
  252. s.Session().SetDisplayScreenName("1000003")
  253. s.SetUserInfoFlag(wire.OServiceUserFlagICQ)
  254. s.SetICQDCInfo(wire.ICQDCInfo{
  255. DCType: 4,
  256. ProtoVersion: 10,
  257. })
  258. return s
  259. },
  260. want: wire.TLVUserInfo{
  261. ScreenName: "1000003",
  262. TLVBlock: wire.TLVBlock{
  263. TLVList: wire.TLVList{
  264. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  265. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree|wire.OServiceUserFlagICQ),
  266. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  267. wire.NewTLVBE(wire.OServiceUserInfoICQDC, wire.ICQDCInfo{
  268. DCType: 4,
  269. ProtoVersion: 10,
  270. }),
  271. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  272. },
  273. },
  274. },
  275. },
  276. {
  277. name: "user has away message set - all instances away",
  278. givenSessionFn: func() *SessionInstance {
  279. sg := NewSession()
  280. s := sg.AddInstance()
  281. s.Session().SetSignonTime(time.Unix(1, 0))
  282. s.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  283. // Add a second instance that is also away
  284. s2 := sg.AddInstance()
  285. s2.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  286. return s
  287. },
  288. want: wire.TLVUserInfo{
  289. TLVBlock: wire.TLVBlock{
  290. TLVList: wire.TLVList{
  291. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  292. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x30)),
  293. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  294. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  295. },
  296. },
  297. },
  298. },
  299. {
  300. name: "user has one instance away, one not away - away flag not set",
  301. givenSessionFn: func() *SessionInstance {
  302. sg := NewSession()
  303. // Create the NOT away instance first so it's used as the base
  304. s2 := sg.AddInstance()
  305. s2.Session().SetSignonTime(time.Unix(1, 0))
  306. // s2 is NOT away - it has default flags only (OServiceUserFlagOSCARFree)
  307. // Now create the away instance
  308. s := sg.AddInstance()
  309. s.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  310. // Since s2 is the first instance and is not away, and allAway() returns false,
  311. // the unavailable flag should not be set
  312. return s
  313. },
  314. want: wire.TLVUserInfo{
  315. TLVBlock: wire.TLVBlock{
  316. TLVList: wire.TLVList{
  317. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  318. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x10)),
  319. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  320. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  321. },
  322. },
  323. },
  324. },
  325. {
  326. name: "user has two instances away, second goes off away - away flag not set",
  327. givenSessionFn: func() *SessionInstance {
  328. sg := NewSession()
  329. sg.SetSignonTime(time.Unix(1, 0))
  330. // Set the first instance as away
  331. s1 := sg.AddInstance()
  332. s1.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  333. // Set the second instance as away
  334. s2 := sg.AddInstance()
  335. s2.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  336. // Make the second instance as not away
  337. s2.ClearUserInfoFlag(wire.OServiceUserFlagUnavailable)
  338. return s1
  339. },
  340. want: wire.TLVUserInfo{
  341. TLVBlock: wire.TLVBlock{
  342. TLVList: wire.TLVList{
  343. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  344. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x10)),
  345. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  346. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  347. },
  348. },
  349. },
  350. },
  351. {
  352. name: "user is invisible",
  353. givenSessionFn: func() *SessionInstance {
  354. s := NewSession().AddInstance()
  355. s.Session().SetSignonTime(time.Unix(1, 0))
  356. s.SetUserStatusBitmask(wire.OServiceUserStatusInvisible)
  357. return s
  358. },
  359. want: wire.TLVUserInfo{
  360. TLVBlock: wire.TLVBlock{
  361. TLVList: wire.TLVList{
  362. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  363. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x0010)),
  364. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0100)),
  365. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  366. },
  367. },
  368. },
  369. },
  370. {
  371. name: "user is idle",
  372. givenSessionFn: func() *SessionInstance {
  373. s := NewSession().AddInstance()
  374. // sign on at t=0m
  375. timeBegin := time.Unix(0, 0)
  376. s.Session().SetSignonTime(timeBegin)
  377. // set idle for 1m at t=+5m (ergo user idled @ t=+4m)
  378. timeIdle := timeBegin.Add(5 * time.Minute)
  379. s.Session().SetNowFn(func() time.Time { return timeIdle })
  380. s.SetIdle(1 * time.Minute)
  381. // now it's t=+10m, ergo idle time should be t10-t4=6m
  382. timeNow := timeBegin.Add(10 * time.Minute)
  383. s.Session().SetNowFn(func() time.Time { return timeNow })
  384. return s
  385. },
  386. want: wire.TLVUserInfo{
  387. TLVBlock: wire.TLVBlock{
  388. TLVList: wire.TLVList{
  389. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(0)),
  390. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x0010)),
  391. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  392. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(6)),
  393. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  394. },
  395. },
  396. },
  397. },
  398. {
  399. name: "user goes idle then returns",
  400. givenSessionFn: func() *SessionInstance {
  401. s := NewSession().AddInstance()
  402. s.Session().SetSignonTime(time.Unix(1, 0))
  403. s.SetIdle(1 * time.Second)
  404. s.UnsetIdle()
  405. return s
  406. },
  407. want: wire.TLVUserInfo{
  408. TLVBlock: wire.TLVBlock{
  409. TLVList: wire.TLVList{
  410. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  411. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x0010)),
  412. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  413. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  414. },
  415. },
  416. },
  417. },
  418. {
  419. name: "user has capabilities",
  420. givenSessionFn: func() *SessionInstance {
  421. s := NewSession().AddInstance()
  422. s.Session().SetSignonTime(time.Unix(1, 0))
  423. s.SetCaps([][16]byte{
  424. {
  425. // chat: "748F2420-6287-11D1-8222-444553540000"
  426. 0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1,
  427. 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00,
  428. },
  429. {
  430. // chat2: "748F2420-6287-11D1-8222-444553540000"
  431. 0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1,
  432. 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01,
  433. },
  434. })
  435. return s
  436. },
  437. want: wire.TLVUserInfo{
  438. TLVBlock: wire.TLVBlock{
  439. TLVList: wire.TLVList{
  440. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  441. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x0010)),
  442. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  443. wire.NewTLVBE(wire.OServiceUserInfoOscarCaps, []byte{
  444. // chat: "748F2420-6287-11D1-8222-444553540000"
  445. 0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1,
  446. 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00,
  447. // chat: "748F2420-6287-11D1-8222-444553540000"
  448. 0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1,
  449. 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01,
  450. }),
  451. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  452. },
  453. },
  454. },
  455. },
  456. {
  457. name: "user has buddy icon",
  458. givenSessionFn: func() *SessionInstance {
  459. s := NewSession().AddInstance()
  460. s.Session().SetSignonTime(time.Unix(1, 0))
  461. s.Session().SetBuddyIcon(testBuddyIcon)
  462. return s
  463. },
  464. want: wire.TLVUserInfo{
  465. WarningLevel: 0,
  466. TLVBlock: wire.TLVBlock{
  467. TLVList: wire.TLVList{
  468. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  469. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x0010)),
  470. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  471. wire.NewTLVBE(wire.OServiceUserInfoBARTInfo, []wire.BARTID{testBuddyIcon}),
  472. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  473. },
  474. },
  475. },
  476. },
  477. {
  478. name: "user has status message",
  479. givenSessionFn: func() *SessionInstance {
  480. s := NewSession().AddInstance()
  481. s.Session().SetSignonTime(time.Unix(1, 0))
  482. s.Session().SetStatus(testStatusBART)
  483. return s
  484. },
  485. want: wire.TLVUserInfo{
  486. WarningLevel: 0,
  487. TLVBlock: wire.TLVBlock{
  488. TLVList: wire.TLVList{
  489. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  490. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x0010)),
  491. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  492. wire.NewTLVBE(wire.OServiceUserInfoBARTInfo, []wire.BARTID{testStatusBART}),
  493. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  494. },
  495. },
  496. },
  497. },
  498. {
  499. // A cleared status is an empty status message, not a missing one: a
  500. // client drops the status it shows only when it is sent an empty one.
  501. name: "user cleared their status message",
  502. givenSessionFn: func() *SessionInstance {
  503. s := NewSession().AddInstance()
  504. s.Session().SetSignonTime(time.Unix(1, 0))
  505. s.Session().SetStatus(testClearedStatusBART)
  506. return s
  507. },
  508. want: wire.TLVUserInfo{
  509. WarningLevel: 0,
  510. TLVBlock: wire.TLVBlock{
  511. TLVList: wire.TLVList{
  512. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  513. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x0010)),
  514. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  515. wire.NewTLVBE(wire.OServiceUserInfoBARTInfo, []wire.BARTID{testClearedStatusBART}),
  516. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  517. },
  518. },
  519. },
  520. },
  521. {
  522. // Both ride in one TLV, so a client reading the icon reads the status.
  523. name: "user has buddy icon and status message",
  524. givenSessionFn: func() *SessionInstance {
  525. s := NewSession().AddInstance()
  526. s.Session().SetSignonTime(time.Unix(1, 0))
  527. s.Session().SetBuddyIcon(testBuddyIcon)
  528. s.Session().SetStatus(testStatusBART)
  529. return s
  530. },
  531. want: wire.TLVUserInfo{
  532. WarningLevel: 0,
  533. TLVBlock: wire.TLVBlock{
  534. TLVList: wire.TLVList{
  535. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  536. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x0010)),
  537. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  538. wire.NewTLVBE(wire.OServiceUserInfoBARTInfo, []wire.BARTID{testBuddyIcon, testStatusBART}),
  539. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  540. },
  541. },
  542. },
  543. },
  544. }
  545. for _, tt := range tests {
  546. t.Run(tt.name, func(t *testing.T) {
  547. s := tt.givenSessionFn()
  548. assert.Equal(t, tt.want, s.Session().TLVUserInfo())
  549. })
  550. }
  551. }
  552. func TestSession_SendAndRecvMessage_ExpectSessSendOK(t *testing.T) {
  553. s := NewSession().AddInstance()
  554. s.SetSignonComplete()
  555. msg := wire.SNACMessage{
  556. Frame: wire.SNACFrame{
  557. FoodGroup: wire.ICBM,
  558. },
  559. }
  560. var wg sync.WaitGroup
  561. wg.Add(1)
  562. go func() {
  563. defer wg.Done()
  564. defer s.CloseInstance()
  565. status := s.RelayMessageToInstance(msg)
  566. assert.Equal(t, SessSendOK, status)
  567. }()
  568. loop:
  569. for {
  570. select {
  571. case m := <-s.ReceiveMessage():
  572. assert.Equal(t, msg, m)
  573. case <-s.Closed():
  574. break loop
  575. }
  576. }
  577. wg.Wait()
  578. }
  579. func TestSession_SendMessage_SessSendClosed(t *testing.T) {
  580. s := NewSession().AddInstance()
  581. s.CloseInstance()
  582. if res := s.RelayMessageToInstance(wire.SNACMessage{}); res != SessSendClosed {
  583. t.Fatalf("expected SessSendClosed, got %+v", res)
  584. }
  585. }
  586. func TestSession_SendMessage_SessQueueFull(t *testing.T) {
  587. s := NewSession().AddInstance()
  588. s.SetSignonComplete()
  589. // Fill up the message channel (default buffer size is 1000)
  590. for i := 0; i < 1000; i++ {
  591. assert.Equal(t, SessSendOK, s.RelayMessageToInstance(wire.SNACMessage{}))
  592. }
  593. assert.Equal(t, SessQueueFull, s.RelayMessageToInstance(wire.SNACMessage{}))
  594. }
  595. func TestSession_Close_Twice(t *testing.T) {
  596. s := NewSession().AddInstance()
  597. s.CloseInstance()
  598. s.CloseInstance() // make sure close is idempotent
  599. // Check that the session is closed by trying to relay a message
  600. if res := s.RelayMessageToInstance(wire.SNACMessage{}); res != SessSendClosed {
  601. t.Fatalf("expected SessSendClosed, got %+v", res)
  602. }
  603. select {
  604. case <-s.Closed():
  605. case <-time.After(1 * time.Second):
  606. t.Fatalf("channel is not closed")
  607. }
  608. }
  609. func TestSession_Closed(t *testing.T) {
  610. s := NewSession().AddInstance()
  611. select {
  612. case <-s.Closed():
  613. assert.Fail(t, "channel is closed")
  614. default:
  615. // channel is open by default
  616. }
  617. s.Session().CloseSession()
  618. <-s.Closed()
  619. }
  620. func TestSession_EvaluateRateLimit_ObserveRateChanges(t *testing.T) {
  621. classParams := [5]wire.RateClass{
  622. {
  623. ID: 1,
  624. WindowSize: 80,
  625. ClearLevel: 2500,
  626. AlertLevel: 2000,
  627. LimitLevel: 1500,
  628. DisconnectLevel: 800,
  629. MaxLevel: 6000,
  630. },
  631. {
  632. ID: 2,
  633. WindowSize: 80,
  634. ClearLevel: 3000,
  635. AlertLevel: 2000,
  636. LimitLevel: 1500,
  637. DisconnectLevel: 1000,
  638. MaxLevel: 6000,
  639. },
  640. {
  641. ID: 3,
  642. WindowSize: 20,
  643. ClearLevel: 5100,
  644. AlertLevel: 5000,
  645. LimitLevel: 4000,
  646. DisconnectLevel: 3000,
  647. MaxLevel: 6000,
  648. },
  649. {
  650. ID: 4,
  651. WindowSize: 20,
  652. ClearLevel: 5500,
  653. AlertLevel: 5300,
  654. LimitLevel: 4200,
  655. DisconnectLevel: 3000,
  656. MaxLevel: 8000,
  657. },
  658. {
  659. ID: 5,
  660. WindowSize: 10,
  661. ClearLevel: 5500,
  662. AlertLevel: 5300,
  663. LimitLevel: 4200,
  664. DisconnectLevel: 3000,
  665. MaxLevel: 8000,
  666. },
  667. }
  668. rateClasses := wire.NewRateLimitClasses(classParams)
  669. t.Run("we can action every 5 seconds indefinitely without getting rate limited", func(t *testing.T) {
  670. now := time.Now()
  671. instance := NewSession().AddInstance()
  672. instance.Session().SetRateClasses(now, rateClasses)
  673. rateClass := rateClasses.Get(3)
  674. instance.Session().SubscribeRateLimits([]wire.RateLimitClassID{rateClass.ID})
  675. for i := 0; i < 100; i++ {
  676. now = now.Add(5 * time.Second)
  677. have := instance.Session().EvaluateRateLimit(now, rateClass.ID)
  678. assert.Equal(t, wire.RateLimitStatusClear, have)
  679. }
  680. })
  681. t.Run("reach disconnect threshold", func(t *testing.T) {
  682. now := time.Now()
  683. sess := NewSession()
  684. sess.SetRateClasses(now, rateClasses)
  685. sess.AddInstance()
  686. sess.AddInstance()
  687. sess.AddInstance()
  688. rateClass := rateClasses.Get(3)
  689. sess.SubscribeRateLimits([]wire.RateLimitClassID{rateClass.ID})
  690. // record some event in the rate limiter
  691. want := []wire.RateLimitStatus{
  692. wire.RateLimitStatusClear,
  693. wire.RateLimitStatusClear,
  694. wire.RateLimitStatusClear,
  695. wire.RateLimitStatusClear,
  696. wire.RateLimitStatusAlert,
  697. wire.RateLimitStatusAlert,
  698. wire.RateLimitStatusAlert,
  699. wire.RateLimitStatusAlert,
  700. wire.RateLimitStatusAlert,
  701. wire.RateLimitStatusLimited,
  702. wire.RateLimitStatusLimited,
  703. wire.RateLimitStatusLimited,
  704. wire.RateLimitStatusLimited,
  705. wire.RateLimitStatusLimited,
  706. wire.RateLimitStatusLimited,
  707. wire.RateLimitStatusLimited,
  708. wire.RateLimitStatusLimited,
  709. wire.RateLimitStatusDisconnect,
  710. }
  711. for i := 0; i < len(want); i++ {
  712. now = now.Add(1 * time.Second)
  713. have := sess.EvaluateRateLimit(now, rateClass.ID)
  714. assert.Equal(t, want[i], have)
  715. }
  716. for _, instance := range sess.Instances() {
  717. select {
  718. case <-instance.Closed():
  719. default:
  720. t.Error("expected session to be closed")
  721. }
  722. }
  723. })
  724. t.Run("reach rate limit threshold, wait for clear threshold", func(t *testing.T) {
  725. now := time.Now()
  726. instance := NewSession().AddInstance()
  727. instance.Session().SetRateClasses(now, rateClasses)
  728. rateClass := rateClasses.Get(3)
  729. instance.Session().SubscribeRateLimits([]wire.RateLimitClassID{rateClass.ID})
  730. // first reach the rate limit threshold
  731. want := []wire.RateLimitStatus{
  732. wire.RateLimitStatusClear,
  733. wire.RateLimitStatusClear,
  734. wire.RateLimitStatusClear,
  735. wire.RateLimitStatusClear,
  736. wire.RateLimitStatusAlert,
  737. wire.RateLimitStatusAlert,
  738. wire.RateLimitStatusAlert,
  739. wire.RateLimitStatusAlert,
  740. wire.RateLimitStatusAlert,
  741. wire.RateLimitStatusLimited,
  742. }
  743. for i := 0; i < len(want); i++ {
  744. now = now.Add(1 * time.Second)
  745. have := instance.Session().EvaluateRateLimit(now, rateClass.ID)
  746. assert.Equal(t, want[i], have)
  747. if i > 0 && want[i-1] != want[i] {
  748. classChanges, rateChanges := instance.Session().ObserveRateChanges(now)
  749. assert.Empty(t, classChanges)
  750. if assert.NotEmpty(t, rateChanges) {
  751. rateDelta := rateChanges[0]
  752. assert.Equal(t, rateClass, rateDelta.RateClass)
  753. assert.Equal(t, want[i], rateDelta.CurrentStatus)
  754. assert.True(t, rateDelta.Subscribed)
  755. if want[i] == wire.RateLimitStatusLimited {
  756. assert.True(t, rateDelta.LimitedNow)
  757. }
  758. }
  759. }
  760. }
  761. // this is a rearranged moving average formula that determines how many
  762. // milliseconds it will take to reach the clear threshold
  763. rateLimitStates := instance.RateLimitStates()
  764. timeToRecover := int(math.Ceil((time.Duration(rateClass.ClearLevel*rateClass.WindowSize-rateLimitStates[rateClass.ID-1].CurrentLevel*(rateClass.WindowSize-1)) * time.Millisecond).Seconds()))
  765. assert.True(t, timeToRecover > 0)
  766. // indicate the time rate limiting kicked in
  767. timeLimited := now
  768. for i := 0; i < timeToRecover; i++ {
  769. now = now.Add(1 * time.Second)
  770. classDelta, stateDelta := instance.Session().ObserveRateChanges(now)
  771. assert.Empty(t, classDelta)
  772. if i == timeToRecover-1 {
  773. // assert that the clear threshold has been met.
  774. assert.ElementsMatch(t, stateDelta, []RateClassState{
  775. {
  776. RateClass: rateClass,
  777. CurrentLevel: 5140,
  778. CurrentStatus: wire.RateLimitStatusClear,
  779. LastTime: timeLimited,
  780. Subscribed: true,
  781. LimitedNow: false,
  782. }})
  783. } else {
  784. // assert that no changed have been observed, it's still rate-limited
  785. assert.Nil(t, stateDelta)
  786. }
  787. }
  788. })
  789. t.Run("observe a rate class change", func(t *testing.T) {
  790. now := time.Now()
  791. instance := NewSession().AddInstance()
  792. instance.Session().SetRateClasses(now, rateClasses)
  793. rateClass := rateClasses.Get(3)
  794. instance.Session().SubscribeRateLimits([]wire.RateLimitClassID{rateClass.ID})
  795. now = now.Add(1 * time.Second)
  796. classDelta, stateDelta := instance.Session().ObserveRateChanges(now)
  797. assert.Empty(t, classDelta)
  798. assert.Empty(t, stateDelta)
  799. paramsCopy := classParams
  800. paramsCopy[rateClass.ID-1].LimitLevel++
  801. newRateClasses := wire.NewRateLimitClasses(paramsCopy)
  802. now = now.Add(1 * time.Second)
  803. instance.Session().SetRateClasses(now, newRateClasses)
  804. now = now.Add(1 * time.Second)
  805. classDelta, stateDelta = instance.Session().ObserveRateChanges(now)
  806. assert.Equal(t, classDelta[0].RateClass, newRateClasses.Get(rateClass.ID))
  807. assert.Empty(t, stateDelta)
  808. })
  809. t.Run("as a bot, I can action every second indefinitely without getting rate limited", func(t *testing.T) {
  810. now := time.Now()
  811. instance := NewSession().AddInstance()
  812. instance.SetUserInfoFlag(wire.OServiceUserFlagBot)
  813. instance.Session().SetRateClasses(now, rateClasses)
  814. for i := 0; i < 100; i++ {
  815. now = now.Add(1 * time.Second)
  816. have := instance.Session().EvaluateRateLimit(now, wire.RateLimitClassID(1))
  817. assert.Equal(t, wire.RateLimitStatusClear, have)
  818. }
  819. })
  820. }
  821. func TestSession_SetAndGetFoodGroupVersions(t *testing.T) {
  822. versions := [wire.MDir + 1]uint16{}
  823. versions[wire.Feedbag] = 1
  824. versions[wire.OService] = 2
  825. s := NewSession().AddInstance()
  826. s.SetFoodGroupVersions(versions)
  827. assert.Equal(t, versions, s.FoodGroupVersions())
  828. }
  829. func TestSession_SetAndGetTypingEventsEnabled(t *testing.T) {
  830. s := NewSession().AddInstance()
  831. assert.False(t, s.TypingEventsEnabled())
  832. s.Session().SetTypingEventsEnabled(true)
  833. assert.True(t, s.TypingEventsEnabled())
  834. s.Session().SetTypingEventsEnabled(false)
  835. assert.False(t, s.TypingEventsEnabled())
  836. }
  837. func TestSession_SetAndGetMultiConnFlag(t *testing.T) {
  838. s := NewSession().AddInstance()
  839. assert.Zero(t, s.MultiConnFlag())
  840. s.SetMultiConnFlag(wire.MultiConnFlagsOldClient)
  841. assert.Equal(t, wire.MultiConnFlagsOldClient, s.MultiConnFlag())
  842. s.SetMultiConnFlag(wire.MultiConnFlagsRecentClient)
  843. assert.Equal(t, wire.MultiConnFlagsRecentClient, s.MultiConnFlag())
  844. s.SetMultiConnFlag(wire.MultiConnFlagsSingleClient)
  845. assert.Equal(t, wire.MultiConnFlagsSingleClient, s.MultiConnFlag())
  846. }
  847. func TestSessionInstance_FeedbagActive(t *testing.T) {
  848. instance := NewSession().AddInstance()
  849. assert.False(t, instance.FeedbagActive())
  850. instance.SetFeedbagActive()
  851. assert.True(t, instance.FeedbagActive())
  852. }
  853. func TestSession_SetAndGetLastWarnLevel(t *testing.T) {
  854. s := NewSession().AddInstance()
  855. assert.Zero(t, s.Warning())
  856. level := uint16(500)
  857. s.Session().SetWarning(level)
  858. assert.Equal(t, level, s.Warning())
  859. }
  860. func TestInstance_Active(t *testing.T) {
  861. tests := []struct {
  862. name string
  863. setupInstance func() *SessionInstance
  864. expectedActive bool
  865. }{
  866. {
  867. name: "active instance - not closed, not idle, no away message",
  868. setupInstance: func() *SessionInstance {
  869. sg := NewSession()
  870. instance := &SessionInstance{
  871. session: sg,
  872. closed: false,
  873. idle: false,
  874. awayMsg: "",
  875. signonComplete: true,
  876. }
  877. return instance
  878. },
  879. expectedActive: true,
  880. },
  881. {
  882. name: "inactive instance - closed",
  883. setupInstance: func() *SessionInstance {
  884. sg := NewSession()
  885. instance := &SessionInstance{
  886. session: sg,
  887. closed: true,
  888. idle: false,
  889. awayMsg: "",
  890. }
  891. return instance
  892. },
  893. expectedActive: false,
  894. },
  895. {
  896. name: "inactive instance - idle",
  897. setupInstance: func() *SessionInstance {
  898. sg := NewSession()
  899. instance := &SessionInstance{
  900. session: sg,
  901. closed: false,
  902. idle: true,
  903. awayMsg: "",
  904. }
  905. return instance
  906. },
  907. expectedActive: false,
  908. },
  909. {
  910. name: "inactive instance - has away message",
  911. setupInstance: func() *SessionInstance {
  912. sg := NewSession()
  913. instance := &SessionInstance{
  914. session: sg,
  915. closed: false,
  916. idle: false,
  917. awayMsg: "I'm away",
  918. userInfoBitmask: wire.OServiceUserFlagUnavailable,
  919. }
  920. return instance
  921. },
  922. expectedActive: false,
  923. },
  924. {
  925. name: "inactive instance - closed and idle",
  926. setupInstance: func() *SessionInstance {
  927. sg := NewSession()
  928. instance := &SessionInstance{
  929. session: sg,
  930. closed: true,
  931. idle: true,
  932. awayMsg: "",
  933. }
  934. return instance
  935. },
  936. expectedActive: false,
  937. },
  938. {
  939. name: "inactive instance - closed and has away message",
  940. setupInstance: func() *SessionInstance {
  941. sg := NewSession()
  942. instance := &SessionInstance{
  943. session: sg,
  944. closed: true,
  945. idle: false,
  946. awayMsg: "I'm away",
  947. userInfoBitmask: wire.OServiceUserFlagUnavailable,
  948. }
  949. return instance
  950. },
  951. expectedActive: false,
  952. },
  953. {
  954. name: "inactive instance - idle and has away message",
  955. setupInstance: func() *SessionInstance {
  956. sg := NewSession()
  957. instance := &SessionInstance{
  958. session: sg,
  959. closed: false,
  960. idle: true,
  961. awayMsg: "I'm away",
  962. userInfoBitmask: wire.OServiceUserFlagUnavailable,
  963. }
  964. return instance
  965. },
  966. expectedActive: false,
  967. },
  968. {
  969. name: "inactive instance - closed, idle, and has away message",
  970. setupInstance: func() *SessionInstance {
  971. sg := NewSession()
  972. instance := &SessionInstance{
  973. session: sg,
  974. closed: true,
  975. idle: true,
  976. awayMsg: "I'm away",
  977. userInfoBitmask: wire.OServiceUserFlagUnavailable,
  978. }
  979. return instance
  980. },
  981. expectedActive: false,
  982. },
  983. {
  984. name: "inactive instance - signon not complete",
  985. setupInstance: func() *SessionInstance {
  986. sg := NewSession()
  987. instance := &SessionInstance{
  988. session: sg,
  989. closed: false,
  990. idle: false,
  991. awayMsg: "",
  992. signonComplete: false,
  993. }
  994. return instance
  995. },
  996. expectedActive: false,
  997. },
  998. {
  999. name: "inactive instance - signon not complete and idle",
  1000. setupInstance: func() *SessionInstance {
  1001. sg := NewSession()
  1002. instance := &SessionInstance{
  1003. session: sg,
  1004. closed: false,
  1005. idle: true,
  1006. awayMsg: "",
  1007. signonComplete: false,
  1008. }
  1009. return instance
  1010. },
  1011. expectedActive: false,
  1012. },
  1013. }
  1014. for _, tt := range tests {
  1015. t.Run(tt.name, func(t *testing.T) {
  1016. instance := tt.setupInstance()
  1017. assert.Equal(t, tt.expectedActive, instance.active())
  1018. })
  1019. }
  1020. }
  1021. func TestSessionGroup_AllInactive(t *testing.T) {
  1022. tests := []struct {
  1023. name string
  1024. setupSessionGroup func() *Session
  1025. expectedResult bool
  1026. }{
  1027. {
  1028. name: "no instances - should return true",
  1029. setupSessionGroup: func() *Session {
  1030. return NewSession()
  1031. },
  1032. expectedResult: true,
  1033. },
  1034. {
  1035. name: "one active instance - should return false",
  1036. setupSessionGroup: func() *Session {
  1037. sg := NewSession()
  1038. instance := sg.AddInstance()
  1039. instance.closed = false
  1040. instance.idle = false
  1041. instance.awayMsg = ""
  1042. instance.signonComplete = true
  1043. return sg
  1044. },
  1045. expectedResult: false,
  1046. },
  1047. {
  1048. name: "one closed instance - should return true",
  1049. setupSessionGroup: func() *Session {
  1050. sg := NewSession()
  1051. instance := sg.AddInstance()
  1052. instance.closed = true
  1053. instance.idle = false
  1054. instance.awayMsg = ""
  1055. return sg
  1056. },
  1057. expectedResult: true,
  1058. },
  1059. {
  1060. name: "one idle instance - should return true",
  1061. setupSessionGroup: func() *Session {
  1062. sg := NewSession()
  1063. instance := sg.AddInstance()
  1064. instance.closed = false
  1065. instance.idle = true
  1066. instance.awayMsg = ""
  1067. return sg
  1068. },
  1069. expectedResult: true,
  1070. },
  1071. {
  1072. name: "one instance with away message - should return true",
  1073. setupSessionGroup: func() *Session {
  1074. sg := NewSession()
  1075. instance := sg.AddInstance()
  1076. instance.closed = false
  1077. instance.idle = false
  1078. instance.awayMsg = "I'm away"
  1079. return sg
  1080. },
  1081. expectedResult: true,
  1082. },
  1083. {
  1084. name: "multiple instances - all inactive - should return true",
  1085. setupSessionGroup: func() *Session {
  1086. sg := NewSession()
  1087. // Add closed instance
  1088. instance1 := sg.AddInstance()
  1089. instance1.closed = true
  1090. instance1.idle = false
  1091. instance1.awayMsg = ""
  1092. // Add idle instance
  1093. instance2 := sg.AddInstance()
  1094. instance2.closed = false
  1095. instance2.idle = true
  1096. instance2.awayMsg = ""
  1097. // Add instance with away message
  1098. instance3 := sg.AddInstance()
  1099. instance3.closed = false
  1100. instance3.idle = false
  1101. instance3.awayMsg = "I'm away"
  1102. return sg
  1103. },
  1104. expectedResult: true,
  1105. },
  1106. {
  1107. name: "multiple instances - one active - should return false",
  1108. setupSessionGroup: func() *Session {
  1109. sg := NewSession()
  1110. // Add closed instance
  1111. instance1 := sg.AddInstance()
  1112. instance1.closed = true
  1113. instance1.idle = false
  1114. instance1.awayMsg = ""
  1115. // Add active instance
  1116. instance2 := sg.AddInstance()
  1117. instance2.closed = false
  1118. instance2.idle = false
  1119. instance2.awayMsg = ""
  1120. instance2.signonComplete = true
  1121. // Add idle instance
  1122. instance3 := sg.AddInstance()
  1123. instance3.closed = false
  1124. instance3.idle = true
  1125. instance3.awayMsg = ""
  1126. return sg
  1127. },
  1128. expectedResult: false,
  1129. },
  1130. {
  1131. name: "multiple instances - all active - should return false",
  1132. setupSessionGroup: func() *Session {
  1133. sg := NewSession()
  1134. // Add first active instance
  1135. instance1 := sg.AddInstance()
  1136. instance1.closed = false
  1137. instance1.idle = false
  1138. instance1.awayMsg = ""
  1139. instance1.signonComplete = true
  1140. // Add second active instance
  1141. instance2 := sg.AddInstance()
  1142. instance2.closed = false
  1143. instance2.idle = false
  1144. instance2.awayMsg = ""
  1145. instance2.signonComplete = true
  1146. return sg
  1147. },
  1148. expectedResult: false,
  1149. },
  1150. {
  1151. name: "mixed scenarios - some closed, some idle, some away, one active - should return false",
  1152. setupSessionGroup: func() *Session {
  1153. sg := NewSession()
  1154. // Add closed instance
  1155. instance1 := sg.AddInstance()
  1156. instance1.closed = true
  1157. instance1.idle = false
  1158. instance1.awayMsg = ""
  1159. // Add idle instance
  1160. instance2 := sg.AddInstance()
  1161. instance2.closed = false
  1162. instance2.idle = true
  1163. instance2.awayMsg = ""
  1164. // Add instance with away message
  1165. instance3 := sg.AddInstance()
  1166. instance3.closed = false
  1167. instance3.idle = false
  1168. instance3.awayMsg = "I'm away"
  1169. // Add active instance
  1170. instance4 := sg.AddInstance()
  1171. instance4.closed = false
  1172. instance4.idle = false
  1173. instance4.awayMsg = ""
  1174. instance4.signonComplete = true
  1175. return sg
  1176. },
  1177. expectedResult: false,
  1178. },
  1179. }
  1180. for _, tt := range tests {
  1181. t.Run(tt.name, func(t *testing.T) {
  1182. sg := tt.setupSessionGroup()
  1183. assert.Equal(t, tt.expectedResult, sg.Inactive())
  1184. })
  1185. }
  1186. }
  1187. func TestSessionGroup_InstanceCount(t *testing.T) {
  1188. tests := []struct {
  1189. name string
  1190. setupGroup func() *Session
  1191. expectedCount int
  1192. }{
  1193. {
  1194. name: "empty session group should return 0",
  1195. setupGroup: func() *Session {
  1196. return NewSession()
  1197. },
  1198. expectedCount: 0,
  1199. },
  1200. {
  1201. name: "one instance should return 1",
  1202. setupGroup: func() *Session {
  1203. sg := NewSession()
  1204. sg.AddInstance()
  1205. return sg
  1206. },
  1207. expectedCount: 1,
  1208. },
  1209. {
  1210. name: "multiple instances should return correct count",
  1211. setupGroup: func() *Session {
  1212. sg := NewSession()
  1213. for i := 0; i < 3; i++ {
  1214. sg.AddInstance()
  1215. }
  1216. return sg
  1217. },
  1218. expectedCount: 3,
  1219. },
  1220. {
  1221. name: "instance count decreases after removal",
  1222. setupGroup: func() *Session {
  1223. sg := NewSession()
  1224. sg.AddInstance()
  1225. instance2 := sg.AddInstance()
  1226. sg.AddInstance()
  1227. // Remove one instance
  1228. sg.RemoveInstance(instance2)
  1229. return sg
  1230. },
  1231. expectedCount: 2,
  1232. },
  1233. {
  1234. name: "instance count is correct after multiple add/remove operations",
  1235. setupGroup: func() *Session {
  1236. sg := NewSession()
  1237. instance1 := sg.AddInstance()
  1238. sg.AddInstance()
  1239. sg.RemoveInstance(instance1)
  1240. sg.AddInstance()
  1241. return sg
  1242. },
  1243. expectedCount: 2,
  1244. },
  1245. }
  1246. for _, tt := range tests {
  1247. t.Run(tt.name, func(t *testing.T) {
  1248. sg := tt.setupGroup()
  1249. assert.Equal(t, tt.expectedCount, sg.InstanceCount())
  1250. })
  1251. }
  1252. }
  1253. func TestSessionGroup_Instances(t *testing.T) {
  1254. tests := []struct {
  1255. name string
  1256. setupGroup func() *Session
  1257. expectedCount int
  1258. expectedAll bool // whether all instances should be returned (including non-signed-in)
  1259. }{
  1260. {
  1261. name: "empty session group should return empty slice",
  1262. setupGroup: func() *Session {
  1263. return NewSession()
  1264. },
  1265. expectedCount: 0,
  1266. expectedAll: true,
  1267. },
  1268. {
  1269. name: "returns all instances including non-signed-in",
  1270. setupGroup: func() *Session {
  1271. sg := NewSession()
  1272. instance1 := sg.AddInstance()
  1273. instance1.SetSignonComplete()
  1274. _ = sg.AddInstance()
  1275. // instance2 has not completed signon
  1276. return sg
  1277. },
  1278. expectedCount: 2,
  1279. expectedAll: true,
  1280. },
  1281. {
  1282. name: "returns all instances with mixed signon states",
  1283. setupGroup: func() *Session {
  1284. sg := NewSession()
  1285. instance1 := sg.AddInstance()
  1286. instance1.SetSignonComplete()
  1287. _ = sg.AddInstance()
  1288. // instance2 has not completed signon
  1289. instance3 := sg.AddInstance()
  1290. instance3.SetSignonComplete()
  1291. return sg
  1292. },
  1293. expectedCount: 3,
  1294. expectedAll: true,
  1295. },
  1296. }
  1297. for _, tt := range tests {
  1298. t.Run(tt.name, func(t *testing.T) {
  1299. sg := tt.setupGroup()
  1300. instances := sg.Instances()
  1301. assert.Equal(t, tt.expectedCount, len(instances), "should return all instances")
  1302. if tt.expectedAll {
  1303. // Verify that instances() returns all instances regardless of their state
  1304. // This is the key change: it should return all instances, not just live ones
  1305. assert.Equal(t, sg.InstanceCount(), len(instances), "Instances() should return all instances")
  1306. }
  1307. })
  1308. }
  1309. }
  1310. func TestSession_SetAndGetProfile(t *testing.T) {
  1311. s := NewSession().AddInstance()
  1312. profile := s.Session().Profile()
  1313. assert.Empty(t, profile.ProfileText)
  1314. assert.Empty(t, profile.MIMEType)
  1315. assert.True(t, profile.UpdateTime.IsZero())
  1316. profileTime := time.Unix(1234567890, 0)
  1317. newProfile := UserProfile{
  1318. ProfileText: "My profile text",
  1319. MIMEType: "text/plain",
  1320. UpdateTime: profileTime,
  1321. }
  1322. s.SetProfile(newProfile)
  1323. retrievedProfile := s.Session().Profile()
  1324. assert.Equal(t, newProfile, retrievedProfile)
  1325. assert.Equal(t, "My profile text", retrievedProfile.ProfileText)
  1326. assert.Equal(t, "text/plain", retrievedProfile.MIMEType)
  1327. assert.Equal(t, profileTime, retrievedProfile.UpdateTime)
  1328. }
  1329. func TestSession_Profile(t *testing.T) {
  1330. tests := []struct {
  1331. name string
  1332. setupSession func() *Session
  1333. expectedProfile UserProfile
  1334. }{
  1335. {
  1336. name: "no instances - returns empty profile",
  1337. setupSession: func() *Session {
  1338. return NewSession()
  1339. },
  1340. expectedProfile: UserProfile{},
  1341. },
  1342. {
  1343. name: "one instance with empty profile - returns empty profile",
  1344. setupSession: func() *Session {
  1345. s := NewSession()
  1346. s.AddInstance()
  1347. return s
  1348. },
  1349. expectedProfile: UserProfile{},
  1350. },
  1351. {
  1352. name: "one instance with non-empty profile - returns that profile",
  1353. setupSession: func() *Session {
  1354. s := NewSession()
  1355. instance := s.AddInstance()
  1356. profileTime := time.Unix(1234567890, 0)
  1357. instance.SetProfile(UserProfile{
  1358. ProfileText: "My profile",
  1359. MIMEType: "text/plain",
  1360. UpdateTime: profileTime,
  1361. })
  1362. return s
  1363. },
  1364. expectedProfile: UserProfile{
  1365. ProfileText: "My profile",
  1366. MIMEType: "text/plain",
  1367. UpdateTime: time.Unix(1234567890, 0),
  1368. },
  1369. },
  1370. {
  1371. name: "multiple instances, all empty - returns empty profile",
  1372. setupSession: func() *Session {
  1373. s := NewSession()
  1374. s.AddInstance()
  1375. s.AddInstance()
  1376. s.AddInstance()
  1377. return s
  1378. },
  1379. expectedProfile: UserProfile{},
  1380. },
  1381. {
  1382. name: "multiple instances, one non-empty - returns that one",
  1383. setupSession: func() *Session {
  1384. s := NewSession()
  1385. s.AddInstance() // empty instance
  1386. instance2 := s.AddInstance()
  1387. instance2.SetProfile(UserProfile{
  1388. ProfileText: "Profile 2",
  1389. MIMEType: "text/plain",
  1390. UpdateTime: time.Unix(1234567890, 0),
  1391. })
  1392. s.AddInstance() // empty instance
  1393. return s
  1394. },
  1395. expectedProfile: UserProfile{
  1396. ProfileText: "Profile 2",
  1397. MIMEType: "text/plain",
  1398. UpdateTime: time.Unix(1234567890, 0),
  1399. },
  1400. },
  1401. {
  1402. name: "multiple instances, multiple non-empty - returns most recent UpdateTime",
  1403. setupSession: func() *Session {
  1404. s := NewSession()
  1405. instance1 := s.AddInstance()
  1406. instance1.SetProfile(UserProfile{
  1407. ProfileText: "Profile 1",
  1408. MIMEType: "text/plain",
  1409. UpdateTime: time.Unix(1234567900, 0), // later time - should be returned
  1410. })
  1411. instance2 := s.AddInstance()
  1412. instance2.SetProfile(UserProfile{
  1413. ProfileText: "Profile 2",
  1414. MIMEType: "text/plain",
  1415. UpdateTime: time.Unix(1234567890, 0), // earlier time
  1416. })
  1417. instance3 := s.AddInstance()
  1418. instance3.SetProfile(UserProfile{
  1419. ProfileText: "Profile 3",
  1420. MIMEType: "text/plain",
  1421. UpdateTime: time.Unix(1234567895, 0), // middle time
  1422. })
  1423. return s
  1424. },
  1425. expectedProfile: UserProfile{
  1426. ProfileText: "Profile 1",
  1427. MIMEType: "text/plain",
  1428. UpdateTime: time.Unix(1234567900, 0),
  1429. },
  1430. },
  1431. {
  1432. name: "first instance empty, later instances have profiles - returns most recent non-empty",
  1433. setupSession: func() *Session {
  1434. s := NewSession()
  1435. s.AddInstance() // empty instance
  1436. instance2 := s.AddInstance()
  1437. instance2.SetProfile(UserProfile{
  1438. ProfileText: "Profile 2",
  1439. MIMEType: "text/plain",
  1440. UpdateTime: time.Unix(1234567890, 0), // earlier
  1441. })
  1442. instance3 := s.AddInstance()
  1443. instance3.SetProfile(UserProfile{
  1444. ProfileText: "Profile 3",
  1445. MIMEType: "text/plain",
  1446. UpdateTime: time.Unix(1234567900, 0), // later time - should be returned
  1447. })
  1448. return s
  1449. },
  1450. expectedProfile: UserProfile{
  1451. ProfileText: "Profile 3",
  1452. MIMEType: "text/plain",
  1453. UpdateTime: time.Unix(1234567900, 0),
  1454. },
  1455. },
  1456. {
  1457. name: "profile with empty ProfileText is considered empty",
  1458. setupSession: func() *Session {
  1459. s := NewSession()
  1460. instance := s.AddInstance()
  1461. instance.SetProfile(UserProfile{
  1462. ProfileText: "",
  1463. MIMEType: "text/plain",
  1464. UpdateTime: time.Unix(1234567890, 0),
  1465. })
  1466. return s
  1467. },
  1468. expectedProfile: UserProfile{},
  1469. },
  1470. {
  1471. name: "profile with null byte ProfileText is considered empty",
  1472. setupSession: func() *Session {
  1473. s := NewSession()
  1474. instance := s.AddInstance()
  1475. instance.SetProfile(UserProfile{
  1476. ProfileText: "\x00",
  1477. MIMEType: "text/plain",
  1478. UpdateTime: time.Unix(1234567890, 0),
  1479. })
  1480. return s
  1481. },
  1482. expectedProfile: UserProfile{},
  1483. },
  1484. }
  1485. for _, tt := range tests {
  1486. t.Run(tt.name, func(t *testing.T) {
  1487. s := tt.setupSession()
  1488. profile := s.Profile()
  1489. assert.Equal(t, tt.expectedProfile, profile)
  1490. })
  1491. }
  1492. }
  1493. func TestSession_SetAndGetMemberSince(t *testing.T) {
  1494. s := NewSession().AddInstance()
  1495. assert.True(t, s.Session().MemberSince().IsZero())
  1496. memberTime := time.Unix(1234567890, 0)
  1497. s.Session().SetMemberSince(memberTime)
  1498. assert.Equal(t, memberTime, s.Session().MemberSince())
  1499. }
  1500. func TestSession_SetAndGetOfflineMsgCount(t *testing.T) {
  1501. s := NewSession().AddInstance()
  1502. assert.Zero(t, s.OfflineMsgCount())
  1503. count := 5
  1504. s.Session().SetOfflineMsgCount(count)
  1505. assert.Equal(t, count, s.OfflineMsgCount())
  1506. count = 10
  1507. s.Session().SetOfflineMsgCount(count)
  1508. assert.Equal(t, count, s.OfflineMsgCount())
  1509. }
  1510. func TestSession_ScaleWarningAndRateLimit(t *testing.T) {
  1511. t.Run("scale up", func(t *testing.T) {
  1512. classParams := [5]wire.RateClass{
  1513. {},
  1514. {},
  1515. {
  1516. ID: 3,
  1517. WindowSize: 20,
  1518. ClearLevel: 5100,
  1519. AlertLevel: 5000,
  1520. LimitLevel: 4000,
  1521. DisconnectLevel: 3000,
  1522. MaxLevel: 6000,
  1523. },
  1524. {},
  1525. {},
  1526. }
  1527. rateClasses := wire.NewRateLimitClasses(classParams)
  1528. now := time.Now()
  1529. instance := NewSession().AddInstance()
  1530. instance.Session().SetRateClasses(now, rateClasses)
  1531. var wg sync.WaitGroup
  1532. wg.Add(1)
  1533. ctx, cancel := context.WithCancel(t.Context())
  1534. go func() {
  1535. defer wg.Done()
  1536. for {
  1537. select {
  1538. case <-ctx.Done():
  1539. return
  1540. case <-instance.WarningCh():
  1541. }
  1542. }
  1543. }()
  1544. rateLimitStates := instance.RateLimitStates()
  1545. assert.Equal(t, int32(5000), rateLimitStates[2].AlertLevel)
  1546. assert.Equal(t, int32(5100), rateLimitStates[2].ClearLevel)
  1547. assert.Equal(t, int32(4000), rateLimitStates[2].LimitLevel)
  1548. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1549. rateLimitStates = instance.RateLimitStates()
  1550. assert.Equal(t, int32(5085), rateLimitStates[2].AlertLevel)
  1551. assert.Equal(t, int32(5175), rateLimitStates[2].ClearLevel)
  1552. assert.Equal(t, int32(4185), rateLimitStates[2].LimitLevel)
  1553. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1554. rateLimitStates = instance.RateLimitStates()
  1555. assert.Equal(t, int32(5170), rateLimitStates[2].AlertLevel)
  1556. assert.Equal(t, int32(5250), rateLimitStates[2].ClearLevel)
  1557. assert.Equal(t, int32(4370), rateLimitStates[2].LimitLevel)
  1558. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1559. rateLimitStates = instance.RateLimitStates()
  1560. assert.Equal(t, int32(5255), rateLimitStates[2].AlertLevel)
  1561. assert.Equal(t, int32(5325), rateLimitStates[2].ClearLevel)
  1562. assert.Equal(t, int32(4555), rateLimitStates[2].LimitLevel)
  1563. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1564. rateLimitStates = instance.RateLimitStates()
  1565. assert.Equal(t, int32(5340), rateLimitStates[2].AlertLevel)
  1566. assert.Equal(t, int32(5400), rateLimitStates[2].ClearLevel)
  1567. assert.Equal(t, int32(4740), rateLimitStates[2].LimitLevel)
  1568. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1569. rateLimitStates = instance.RateLimitStates()
  1570. assert.Equal(t, int32(5425), rateLimitStates[2].AlertLevel)
  1571. assert.Equal(t, int32(5475), rateLimitStates[2].ClearLevel)
  1572. assert.Equal(t, int32(4925), rateLimitStates[2].LimitLevel)
  1573. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1574. rateLimitStates = instance.RateLimitStates()
  1575. assert.Equal(t, int32(5510), rateLimitStates[2].AlertLevel)
  1576. assert.Equal(t, int32(5550), rateLimitStates[2].ClearLevel)
  1577. assert.Equal(t, int32(5110), rateLimitStates[2].LimitLevel)
  1578. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1579. rateLimitStates = instance.RateLimitStates()
  1580. assert.Equal(t, int32(5595), rateLimitStates[2].AlertLevel)
  1581. assert.Equal(t, int32(5625), rateLimitStates[2].ClearLevel)
  1582. assert.Equal(t, int32(5295), rateLimitStates[2].LimitLevel)
  1583. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1584. rateLimitStates = instance.RateLimitStates()
  1585. assert.Equal(t, int32(5680), rateLimitStates[2].AlertLevel)
  1586. assert.Equal(t, int32(5700), rateLimitStates[2].ClearLevel)
  1587. assert.Equal(t, int32(5480), rateLimitStates[2].LimitLevel)
  1588. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1589. rateLimitStates = instance.RateLimitStates()
  1590. assert.Equal(t, int32(5765), rateLimitStates[2].AlertLevel)
  1591. assert.Equal(t, int32(5775), rateLimitStates[2].ClearLevel)
  1592. assert.Equal(t, int32(5665), rateLimitStates[2].LimitLevel)
  1593. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1594. rateLimitStates = instance.RateLimitStates()
  1595. assert.Equal(t, int32(5850), rateLimitStates[2].AlertLevel)
  1596. assert.Equal(t, int32(5850), rateLimitStates[2].ClearLevel)
  1597. assert.Equal(t, int32(5850), rateLimitStates[2].LimitLevel)
  1598. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1599. rateLimitStates = instance.RateLimitStates()
  1600. assert.Equal(t, int32(5850), rateLimitStates[2].AlertLevel)
  1601. assert.Equal(t, int32(5850), rateLimitStates[2].ClearLevel)
  1602. assert.Equal(t, int32(5850), rateLimitStates[2].LimitLevel)
  1603. cancel()
  1604. wg.Wait()
  1605. })
  1606. t.Run("scale down", func(t *testing.T) {
  1607. currentClassParams := [5]wire.RateClass{
  1608. {},
  1609. {},
  1610. {
  1611. ID: 3,
  1612. WindowSize: 20,
  1613. ClearLevel: 5100,
  1614. AlertLevel: 5000,
  1615. LimitLevel: 4000,
  1616. DisconnectLevel: 3000,
  1617. MaxLevel: 6000,
  1618. },
  1619. {},
  1620. {},
  1621. }
  1622. rateClasses := wire.NewRateLimitClasses(currentClassParams)
  1623. now := time.Now()
  1624. instance := NewSession().AddInstance()
  1625. instance.Session().SetRateClasses(now, rateClasses)
  1626. var wg sync.WaitGroup
  1627. wg.Add(1)
  1628. ctx, cancel := context.WithCancel(t.Context())
  1629. go func() {
  1630. defer wg.Done()
  1631. for {
  1632. select {
  1633. case <-ctx.Done():
  1634. return
  1635. case <-instance.WarningCh():
  1636. }
  1637. }
  1638. }()
  1639. for i := 0; i < 10; i++ {
  1640. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1641. }
  1642. rateLimitStates := instance.RateLimitStates()
  1643. assert.Equal(t, int32(5850), rateLimitStates[2].AlertLevel)
  1644. assert.Equal(t, int32(5850), rateLimitStates[2].ClearLevel)
  1645. assert.Equal(t, int32(5850), rateLimitStates[2].LimitLevel)
  1646. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1647. rateLimitStates = instance.RateLimitStates()
  1648. assert.Equal(t, int32(5765), rateLimitStates[2].AlertLevel)
  1649. assert.Equal(t, int32(5775), rateLimitStates[2].ClearLevel)
  1650. assert.Equal(t, int32(5665), rateLimitStates[2].LimitLevel)
  1651. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1652. rateLimitStates = instance.RateLimitStates()
  1653. assert.Equal(t, int32(5680), rateLimitStates[2].AlertLevel)
  1654. assert.Equal(t, int32(5700), rateLimitStates[2].ClearLevel)
  1655. assert.Equal(t, int32(5480), rateLimitStates[2].LimitLevel)
  1656. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1657. rateLimitStates = instance.RateLimitStates()
  1658. assert.Equal(t, int32(5595), rateLimitStates[2].AlertLevel)
  1659. assert.Equal(t, int32(5625), rateLimitStates[2].ClearLevel)
  1660. assert.Equal(t, int32(5295), rateLimitStates[2].LimitLevel)
  1661. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1662. rateLimitStates = instance.RateLimitStates()
  1663. assert.Equal(t, int32(5510), rateLimitStates[2].AlertLevel)
  1664. assert.Equal(t, int32(5550), rateLimitStates[2].ClearLevel)
  1665. assert.Equal(t, int32(5110), rateLimitStates[2].LimitLevel)
  1666. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1667. rateLimitStates = instance.RateLimitStates()
  1668. assert.Equal(t, int32(5425), rateLimitStates[2].AlertLevel)
  1669. assert.Equal(t, int32(5475), rateLimitStates[2].ClearLevel)
  1670. assert.Equal(t, int32(4925), rateLimitStates[2].LimitLevel)
  1671. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1672. rateLimitStates = instance.RateLimitStates()
  1673. assert.Equal(t, int32(5340), rateLimitStates[2].AlertLevel)
  1674. assert.Equal(t, int32(5400), rateLimitStates[2].ClearLevel)
  1675. assert.Equal(t, int32(4740), rateLimitStates[2].LimitLevel)
  1676. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1677. rateLimitStates = instance.RateLimitStates()
  1678. assert.Equal(t, int32(5255), rateLimitStates[2].AlertLevel)
  1679. assert.Equal(t, int32(5325), rateLimitStates[2].ClearLevel)
  1680. assert.Equal(t, int32(4555), rateLimitStates[2].LimitLevel)
  1681. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1682. rateLimitStates = instance.RateLimitStates()
  1683. assert.Equal(t, int32(5170), rateLimitStates[2].AlertLevel)
  1684. assert.Equal(t, int32(5250), rateLimitStates[2].ClearLevel)
  1685. assert.Equal(t, int32(4370), rateLimitStates[2].LimitLevel)
  1686. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1687. rateLimitStates = instance.RateLimitStates()
  1688. assert.Equal(t, int32(5085), rateLimitStates[2].AlertLevel)
  1689. assert.Equal(t, int32(5175), rateLimitStates[2].ClearLevel)
  1690. assert.Equal(t, int32(4185), rateLimitStates[2].LimitLevel)
  1691. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1692. rateLimitStates = instance.RateLimitStates()
  1693. assert.Equal(t, int32(5000), rateLimitStates[2].AlertLevel)
  1694. assert.Equal(t, int32(5100), rateLimitStates[2].ClearLevel)
  1695. assert.Equal(t, int32(4000), rateLimitStates[2].LimitLevel)
  1696. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1697. rateLimitStates = instance.RateLimitStates()
  1698. assert.Equal(t, int32(5000), rateLimitStates[2].AlertLevel)
  1699. assert.Equal(t, int32(5100), rateLimitStates[2].ClearLevel)
  1700. assert.Equal(t, int32(4000), rateLimitStates[2].LimitLevel)
  1701. cancel()
  1702. wg.Wait()
  1703. })
  1704. t.Run("increment 100%", func(t *testing.T) {
  1705. classParams := [5]wire.RateClass{
  1706. {},
  1707. {},
  1708. {
  1709. ID: 3,
  1710. WindowSize: 20,
  1711. ClearLevel: 5100,
  1712. AlertLevel: 5000,
  1713. LimitLevel: 4000,
  1714. DisconnectLevel: 3000,
  1715. MaxLevel: 6000,
  1716. },
  1717. {},
  1718. {},
  1719. }
  1720. rateClasses := wire.NewRateLimitClasses(classParams)
  1721. now := time.Now()
  1722. instance := NewSession().AddInstance()
  1723. instance.Session().SetRateClasses(now, rateClasses)
  1724. var wg sync.WaitGroup
  1725. wg.Add(1)
  1726. ctx, cancel := context.WithCancel(t.Context())
  1727. go func() {
  1728. defer wg.Done()
  1729. for {
  1730. select {
  1731. case <-ctx.Done():
  1732. return
  1733. case <-instance.WarningCh():
  1734. }
  1735. }
  1736. }()
  1737. rateLimitStates := instance.RateLimitStates()
  1738. assert.Equal(t, int32(5000), rateLimitStates[2].AlertLevel)
  1739. assert.Equal(t, int32(5100), rateLimitStates[2].ClearLevel)
  1740. assert.Equal(t, int32(4000), rateLimitStates[2].LimitLevel)
  1741. instance.Session().ScaleWarningAndRateLimit(1000, 3)
  1742. rateLimitStates = instance.RateLimitStates()
  1743. assert.Equal(t, int32(5850), rateLimitStates[2].AlertLevel)
  1744. assert.Equal(t, int32(5850), rateLimitStates[2].ClearLevel)
  1745. assert.Equal(t, int32(5850), rateLimitStates[2].LimitLevel)
  1746. cancel()
  1747. wg.Wait()
  1748. })
  1749. }
  1750. func TestSession_RunOnce(t *testing.T) {
  1751. t.Run("runs function on first call", func(t *testing.T) {
  1752. s := NewSession()
  1753. callCount := 0
  1754. err := s.RunOnce(func() error {
  1755. callCount++
  1756. return nil
  1757. })
  1758. assert.NoError(t, err)
  1759. assert.Equal(t, 1, callCount)
  1760. })
  1761. t.Run("does not run function on subsequent calls", func(t *testing.T) {
  1762. s := NewSession()
  1763. callCount := 0
  1764. // First call
  1765. err1 := s.RunOnce(func() error {
  1766. callCount++
  1767. return nil
  1768. })
  1769. // Second call
  1770. err2 := s.RunOnce(func() error {
  1771. callCount++
  1772. return nil
  1773. })
  1774. // Third call
  1775. err3 := s.RunOnce(func() error {
  1776. callCount++
  1777. return nil
  1778. })
  1779. assert.NoError(t, err1)
  1780. assert.NoError(t, err2)
  1781. assert.NoError(t, err3)
  1782. assert.Equal(t, 1, callCount, "function should only be called once")
  1783. })
  1784. t.Run("returns error from function", func(t *testing.T) {
  1785. s := NewSession()
  1786. expectedErr := assert.AnError
  1787. err := s.RunOnce(func() error {
  1788. return expectedErr
  1789. })
  1790. assert.Error(t, err)
  1791. assert.Equal(t, expectedErr, err)
  1792. })
  1793. // Init is one-shot, so a session that failed to initialize must not linger for
  1794. // later instances to attach to.
  1795. t.Run("closes the session when the function fails", func(t *testing.T) {
  1796. s := NewSession()
  1797. instance := s.AddInstance()
  1798. err := s.RunOnce(func() error {
  1799. return assert.AnError
  1800. })
  1801. assert.Error(t, err)
  1802. assert.True(t, s.IsClosed(), "session should be closed after a failed init")
  1803. assert.True(t, instance.IsClosed(), "instances should be torn down with the session")
  1804. })
  1805. t.Run("leaves the session open when the function succeeds", func(t *testing.T) {
  1806. s := NewSession()
  1807. s.AddInstance()
  1808. assert.NoError(t, s.RunOnce(func() error { return nil }))
  1809. assert.False(t, s.IsClosed())
  1810. })
  1811. // A failed init tears the session down, so a caller that did not run fn must
  1812. // still learn it failed rather than sign a client on to a session whose
  1813. // instances have just been closed underneath it.
  1814. t.Run("reports the init error to every caller", func(t *testing.T) {
  1815. s := NewSession()
  1816. s.AddInstance()
  1817. first := s.RunOnce(func() error { return assert.AnError })
  1818. second := s.RunOnce(func() error { return nil })
  1819. assert.ErrorIs(t, first, assert.AnError)
  1820. assert.ErrorIs(t, second, assert.AnError, "the caller that did not run fn must not read failure as success")
  1821. })
  1822. // Two instances can be added to a fresh session and reach RunOnce
  1823. // concurrently. Whichever one loses the race still has a client to sign on,
  1824. // and must be told the session is not usable.
  1825. t.Run("reports the init error to a concurrent caller", func(t *testing.T) {
  1826. synctest.Test(t, func(t *testing.T) {
  1827. s := NewSession()
  1828. s.AddInstance()
  1829. s.AddInstance()
  1830. var wg sync.WaitGroup
  1831. errs := make([]error, 2)
  1832. for i := range errs {
  1833. wg.Add(1)
  1834. go func() {
  1835. defer wg.Done()
  1836. errs[i] = s.RunOnce(func() error { return assert.AnError })
  1837. }()
  1838. }
  1839. wg.Wait()
  1840. for i, err := range errs {
  1841. assert.ErrorIs(t, err, assert.AnError, "caller %d", i)
  1842. }
  1843. })
  1844. })
  1845. }
  1846. // An instance added to a closed session is live but orphaned: Closed() has
  1847. // already fired, so the per-account goroutines that select on it — the rate limit
  1848. // monitor above all — have exited, and RunOnce is spent so nothing restarts them.
  1849. // AddInstance must refuse, so AddSession can fall through to the tombstone wait it
  1850. // already implements.
  1851. func TestSession_AddInstance_RefusesClosedSession(t *testing.T) {
  1852. s := NewSession()
  1853. first := s.AddInstance()
  1854. // Stand in for OServiceService.MonitorRateLimits: a per-account goroutine
  1855. // started from RunOnce that runs until the session closes.
  1856. require.NoError(t, s.RunOnce(func() error { return nil }))
  1857. monitorDone := make(chan struct{})
  1858. go func() {
  1859. defer close(monitorDone)
  1860. <-s.Closed()
  1861. }()
  1862. // The account's last instance departs, closing the session and stopping the
  1863. // monitor. This is the moment AddSession's IsClosed check races.
  1864. first.CloseInstance()
  1865. select {
  1866. case <-monitorDone:
  1867. case <-time.After(2 * time.Second):
  1868. t.Fatal("monitor did not exit when the session closed")
  1869. }
  1870. assert.Nil(t, s.AddInstance(), "AddInstance must refuse a closed session")
  1871. // RunOnce is spent, so an instance that did attach could never start a
  1872. // replacement monitor.
  1873. ran := false
  1874. assert.NoError(t, s.RunOnce(func() error { ran = true; return nil }))
  1875. assert.False(t, ran)
  1876. }
  1877. func TestSession_CloseInstance(t *testing.T) {
  1878. s := NewSession()
  1879. sessionCloseCount := 0
  1880. s.OnSessionClose(func() {
  1881. sessionCloseCount++
  1882. })
  1883. instance1CloseCount := 0
  1884. instance2CloseCount := 0
  1885. instance3CloseCount := 0
  1886. instance1 := s.AddInstance()
  1887. instance2 := s.AddInstance()
  1888. instance3 := s.AddInstance()
  1889. instance1.OnClose(func() {
  1890. // ensure instance is removed from the session before calling this func
  1891. assert.Equal(t, 2, s.InstanceCount())
  1892. instance1CloseCount++
  1893. })
  1894. instance2.OnClose(func() {
  1895. assert.Equal(t, 1, s.InstanceCount())
  1896. instance2CloseCount++
  1897. })
  1898. instance3.OnClose(func() {
  1899. instance3CloseCount++
  1900. })
  1901. // Close instance1 (instances 2 and 3 remain)
  1902. instance1.CloseInstance()
  1903. instance2.CloseInstance()
  1904. instance3.CloseInstance()
  1905. assert.Equal(t, 1, instance1CloseCount, "instance1 onInstanceCloseFn should only be called once")
  1906. assert.Equal(t, 1, instance2CloseCount, "instance2 onInstanceCloseFn should only be called once")
  1907. assert.Equal(t, 0, instance3CloseCount, "instance3 onInstanceCloseFn should not be called because it's the last instance")
  1908. assert.Equal(t, 1, sessionCloseCount, "session onSessCloseFn should not be called")
  1909. }
  1910. func TestSession_CloseSession(t *testing.T) {
  1911. s := NewSession()
  1912. sessionCloseCount := 0
  1913. s.OnSessionClose(func() {
  1914. sessionCloseCount++
  1915. })
  1916. instance1CloseCount := 0
  1917. instance2CloseCount := 0
  1918. instance3CloseCount := 0
  1919. instance1 := s.AddInstance()
  1920. instance2 := s.AddInstance()
  1921. instance3 := s.AddInstance()
  1922. instance1.OnClose(func() {
  1923. instance1CloseCount++
  1924. })
  1925. instance2.OnClose(func() {
  1926. instance2CloseCount++
  1927. })
  1928. instance3.OnClose(func() {
  1929. instance3CloseCount++
  1930. })
  1931. s.CloseSession()
  1932. assert.Equal(t, 0, instance1CloseCount, "instance1 onInstanceCloseFn should not be called")
  1933. assert.Equal(t, 0, instance2CloseCount, "instance2 onInstanceCloseFn should not be called")
  1934. assert.Equal(t, 0, instance3CloseCount, "instance3 onInstanceCloseFn should not be called")
  1935. assert.Equal(t, 1, sessionCloseCount, "session onSessCloseFn should only be called once")
  1936. }
  1937. func TestSession_AwayMessage(t *testing.T) {
  1938. tests := []struct {
  1939. name string
  1940. setupSession func() *Session
  1941. expectedResult string
  1942. }{
  1943. {
  1944. name: "no instances - should return empty string",
  1945. setupSession: func() *Session {
  1946. return NewSession()
  1947. },
  1948. expectedResult: "",
  1949. },
  1950. {
  1951. name: "one instance not away - should return empty string",
  1952. setupSession: func() *Session {
  1953. sg := NewSession()
  1954. _ = sg.AddInstance()
  1955. // instance has no away message and is not set as away
  1956. return sg
  1957. },
  1958. expectedResult: "",
  1959. },
  1960. {
  1961. name: "one instance away via SetUserInfoFlag - should return away message",
  1962. setupSession: func() *Session {
  1963. sg := NewSession()
  1964. instance := sg.AddInstance()
  1965. instance.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  1966. instance.SetAwayMessage("I'm away")
  1967. return sg
  1968. },
  1969. expectedResult: "I'm away",
  1970. },
  1971. {
  1972. name: "one instance away via SetUserStatusBitmask - should return away message",
  1973. setupSession: func() *Session {
  1974. sg := NewSession()
  1975. instance := sg.AddInstance()
  1976. instance.SetUserStatusBitmask(wire.OServiceUserStatusAway)
  1977. instance.SetAwayMessage("I'm away")
  1978. return sg
  1979. },
  1980. expectedResult: "I'm away",
  1981. },
  1982. {
  1983. name: "multiple instances - not all away - should return away message from away instance",
  1984. setupSession: func() *Session {
  1985. sg := NewSession()
  1986. instance1 := sg.AddInstance()
  1987. instance1.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  1988. instance1.SetAwayMessage("I'm away")
  1989. _ = sg.AddInstance()
  1990. // instance2 has no away message and is not set as away
  1991. return sg
  1992. },
  1993. expectedResult: "I'm away",
  1994. },
  1995. {
  1996. name: "multiple instances - all away - should return latest away message",
  1997. setupSession: func() *Session {
  1998. sg := NewSession()
  1999. baseTime := time.Now()
  2000. callCount := 0
  2001. sg.nowFn = func() time.Time {
  2002. callCount++
  2003. return baseTime.Add(time.Duration(callCount) * time.Second)
  2004. }
  2005. instance1 := sg.AddInstance()
  2006. instance1.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  2007. instance1.SetAwayMessage("First away message")
  2008. instance2 := sg.AddInstance()
  2009. instance2.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  2010. instance2.SetAwayMessage("Second away message")
  2011. return sg
  2012. },
  2013. expectedResult: "Second away message",
  2014. },
  2015. {
  2016. name: "multiple instances - all away after multiple updates - should return latest away message",
  2017. setupSession: func() *Session {
  2018. sg := NewSession()
  2019. baseTime := time.Now()
  2020. callCount := 0
  2021. sg.nowFn = func() time.Time {
  2022. callCount++
  2023. return baseTime.Add(time.Duration(callCount) * time.Second)
  2024. }
  2025. instance1 := sg.AddInstance()
  2026. instance1.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  2027. instance1.SetAwayMessage("First away message")
  2028. instance2 := sg.AddInstance()
  2029. instance2.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  2030. instance2.SetAwayMessage("Second away message")
  2031. // Update instance1's away status again (this will update awayTime)
  2032. instance1.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  2033. instance1.SetAwayMessage("Third away message")
  2034. return sg
  2035. },
  2036. expectedResult: "Third away message",
  2037. },
  2038. {
  2039. name: "multiple instances - different away methods - should return latest away message",
  2040. setupSession: func() *Session {
  2041. sg := NewSession()
  2042. baseTime := time.Now()
  2043. callCount := 0
  2044. sg.nowFn = func() time.Time {
  2045. callCount++
  2046. return baseTime.Add(time.Duration(callCount) * time.Second)
  2047. }
  2048. instance1 := sg.AddInstance()
  2049. instance1.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  2050. instance1.SetAwayMessage("First away message")
  2051. instance2 := sg.AddInstance()
  2052. instance2.SetUserStatusBitmask(wire.OServiceUserStatusAway)
  2053. instance2.SetAwayMessage("Second away message")
  2054. return sg
  2055. },
  2056. expectedResult: "Second away message",
  2057. },
  2058. {
  2059. name: "instance sets away message then clears message - should return empty string",
  2060. setupSession: func() *Session {
  2061. sg := NewSession()
  2062. instance := sg.AddInstance()
  2063. instance.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  2064. instance.SetAwayMessage("I'm away")
  2065. instance.SetAwayMessage("") // clear away message (but still away)
  2066. return sg
  2067. },
  2068. expectedResult: "",
  2069. },
  2070. {
  2071. name: "instance sets away message then clears away status - should return empty string",
  2072. setupSession: func() *Session {
  2073. sg := NewSession()
  2074. instance := sg.AddInstance()
  2075. instance.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  2076. instance.SetAwayMessage("I'm away")
  2077. instance.ClearUserInfoFlag(wire.OServiceUserFlagUnavailable) // clear away status
  2078. return sg
  2079. },
  2080. expectedResult: "",
  2081. },
  2082. {
  2083. name: "multiple instances - one away with message, one away without message - should return message from most recent",
  2084. setupSession: func() *Session {
  2085. sg := NewSession()
  2086. baseTime := time.Now()
  2087. callCount := 0
  2088. sg.nowFn = func() time.Time {
  2089. callCount++
  2090. return baseTime.Add(time.Duration(callCount) * time.Second)
  2091. }
  2092. instance1 := sg.AddInstance()
  2093. instance1.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  2094. instance1.SetAwayMessage("I'm away")
  2095. instance2 := sg.AddInstance()
  2096. instance2.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  2097. // instance2 is away but has no message, and was set away after instance1
  2098. return sg
  2099. },
  2100. expectedResult: "", // instance2 has more recent awayTime but no message
  2101. },
  2102. {
  2103. name: "multiple instances - one away with message set later - should return that message",
  2104. setupSession: func() *Session {
  2105. sg := NewSession()
  2106. baseTime := time.Now()
  2107. callCount := 0
  2108. sg.nowFn = func() time.Time {
  2109. callCount++
  2110. return baseTime.Add(time.Duration(callCount) * time.Second)
  2111. }
  2112. instance1 := sg.AddInstance()
  2113. instance1.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  2114. instance1.SetAwayMessage("I'm away")
  2115. instance2 := sg.AddInstance()
  2116. instance2.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  2117. // instance2 is away but has no message
  2118. // Now update instance1's away status to make it more recent
  2119. instance1.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  2120. return sg
  2121. },
  2122. expectedResult: "I'm away", // instance1 has more recent awayTime and has a message
  2123. },
  2124. }
  2125. for _, tt := range tests {
  2126. t.Run(tt.name, func(t *testing.T) {
  2127. sg := tt.setupSession()
  2128. result := sg.AwayMessage()
  2129. assert.Equal(t, tt.expectedResult, result)
  2130. })
  2131. }
  2132. }
  2133. func TestSession_Caps(t *testing.T) {
  2134. // Helper function to compare capability slices (order-independent)
  2135. capsEqual := func(a, b [][16]byte) bool {
  2136. if len(a) != len(b) {
  2137. return false
  2138. }
  2139. capMap := make(map[[16]byte]bool)
  2140. for _, cap := range a {
  2141. capMap[cap] = true
  2142. }
  2143. for _, cap := range b {
  2144. if !capMap[cap] {
  2145. return false
  2146. }
  2147. }
  2148. return true
  2149. }
  2150. tests := []struct {
  2151. name string
  2152. setupSession func() *Session
  2153. expectedCaps [][16]byte
  2154. expectedCount int
  2155. }{
  2156. {
  2157. name: "empty session with no instances - should return empty slice",
  2158. setupSession: func() *Session {
  2159. return NewSession()
  2160. },
  2161. expectedCaps: [][16]byte{},
  2162. expectedCount: 0,
  2163. },
  2164. {
  2165. name: "single instance with no capabilities - should return empty slice",
  2166. setupSession: func() *Session {
  2167. sg := NewSession()
  2168. _ = sg.AddInstance()
  2169. return sg
  2170. },
  2171. expectedCaps: [][16]byte{},
  2172. expectedCount: 0,
  2173. },
  2174. {
  2175. name: "single instance with one cap - should return that cap",
  2176. setupSession: func() *Session {
  2177. sg := NewSession()
  2178. instance := sg.AddInstance()
  2179. cap1 := [16]byte{0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}
  2180. instance.SetCaps([][16]byte{cap1})
  2181. return sg
  2182. },
  2183. expectedCaps: [][16]byte{
  2184. {0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00},
  2185. },
  2186. expectedCount: 1,
  2187. },
  2188. {
  2189. name: "single instance with multiple capabilities - should return all capabilities",
  2190. setupSession: func() *Session {
  2191. sg := NewSession()
  2192. instance := sg.AddInstance()
  2193. cap1 := [16]byte{0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}
  2194. cap2 := [16]byte{0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01}
  2195. cap3 := [16]byte{0x76, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x02}
  2196. instance.SetCaps([][16]byte{cap1, cap2, cap3})
  2197. return sg
  2198. },
  2199. expectedCaps: [][16]byte{
  2200. {0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00},
  2201. {0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01},
  2202. {0x76, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x02},
  2203. },
  2204. expectedCount: 3,
  2205. },
  2206. {
  2207. name: "multiple instances with no overlapping capabilities - should return union of all capabilities",
  2208. setupSession: func() *Session {
  2209. sg := NewSession()
  2210. instance1 := sg.AddInstance()
  2211. cap1 := [16]byte{0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}
  2212. instance1.SetCaps([][16]byte{cap1})
  2213. instance2 := sg.AddInstance()
  2214. cap2 := [16]byte{0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01}
  2215. instance2.SetCaps([][16]byte{cap2})
  2216. instance3 := sg.AddInstance()
  2217. cap3 := [16]byte{0x76, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x02}
  2218. instance3.SetCaps([][16]byte{cap3})
  2219. return sg
  2220. },
  2221. expectedCaps: [][16]byte{
  2222. {0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00},
  2223. {0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01},
  2224. {0x76, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x02},
  2225. },
  2226. expectedCount: 3,
  2227. },
  2228. {
  2229. name: "multiple instances with overlapping capabilities - should deduplicate",
  2230. setupSession: func() *Session {
  2231. sg := NewSession()
  2232. cap1 := [16]byte{0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}
  2233. cap2 := [16]byte{0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01}
  2234. cap3 := [16]byte{0x76, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x02}
  2235. instance1 := sg.AddInstance()
  2236. instance1.SetCaps([][16]byte{cap1, cap2})
  2237. instance2 := sg.AddInstance()
  2238. instance2.SetCaps([][16]byte{cap2, cap3}) // cap2 overlaps
  2239. return sg
  2240. },
  2241. expectedCaps: [][16]byte{
  2242. {0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00},
  2243. {0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01},
  2244. {0x76, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x02},
  2245. },
  2246. expectedCount: 3,
  2247. },
  2248. {
  2249. name: "multiple instances with all same capabilities - should return unique capabilities",
  2250. setupSession: func() *Session {
  2251. sg := NewSession()
  2252. cap1 := [16]byte{0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}
  2253. cap2 := [16]byte{0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01}
  2254. instance1 := sg.AddInstance()
  2255. instance1.SetCaps([][16]byte{cap1, cap2})
  2256. instance2 := sg.AddInstance()
  2257. instance2.SetCaps([][16]byte{cap1, cap2}) // same caps
  2258. instance3 := sg.AddInstance()
  2259. instance3.SetCaps([][16]byte{cap1, cap2}) // same caps
  2260. return sg
  2261. },
  2262. expectedCaps: [][16]byte{
  2263. {0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00},
  2264. {0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01},
  2265. },
  2266. expectedCount: 2,
  2267. },
  2268. {
  2269. name: "multiple instances with some having capabilities and some not - should return union",
  2270. setupSession: func() *Session {
  2271. sg := NewSession()
  2272. _ = sg.AddInstance()
  2273. // instance1 has no caps
  2274. instance2 := sg.AddInstance()
  2275. cap1 := [16]byte{0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}
  2276. instance2.SetCaps([][16]byte{cap1})
  2277. _ = sg.AddInstance()
  2278. // instance3 has no caps
  2279. instance4 := sg.AddInstance()
  2280. cap2 := [16]byte{0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01}
  2281. instance4.SetCaps([][16]byte{cap2})
  2282. return sg
  2283. },
  2284. expectedCaps: [][16]byte{
  2285. {0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00},
  2286. {0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01},
  2287. },
  2288. expectedCount: 2,
  2289. },
  2290. }
  2291. for _, tt := range tests {
  2292. t.Run(tt.name, func(t *testing.T) {
  2293. sg := tt.setupSession()
  2294. result := sg.Caps()
  2295. assert.Equal(t, tt.expectedCount, len(result), "cap count should match")
  2296. assert.True(t, capsEqual(tt.expectedCaps, result), "capabilities should match (order-independent)")
  2297. })
  2298. }
  2299. }
  2300. func TestSession_InstanceNumberAssignment(t *testing.T) {
  2301. t.Run("first instance gets number 1", func(t *testing.T) {
  2302. s := NewSession()
  2303. instance := s.AddInstance()
  2304. assert.Equal(t, uint8(1), instance.Num())
  2305. })
  2306. t.Run("multiple instances get sequential numbers", func(t *testing.T) {
  2307. s := NewSession()
  2308. instance1 := s.AddInstance()
  2309. instance2 := s.AddInstance()
  2310. instance3 := s.AddInstance()
  2311. assert.Equal(t, uint8(1), instance1.Num())
  2312. assert.Equal(t, uint8(2), instance2.Num())
  2313. assert.Equal(t, uint8(3), instance3.Num())
  2314. })
  2315. t.Run("removed instance numbers are reused", func(t *testing.T) {
  2316. s := NewSession()
  2317. instance1 := s.AddInstance()
  2318. instance2 := s.AddInstance()
  2319. instance3 := s.AddInstance()
  2320. assert.Equal(t, uint8(1), instance1.Num())
  2321. assert.Equal(t, uint8(2), instance2.Num())
  2322. assert.Equal(t, uint8(3), instance3.Num())
  2323. // Remove instance 2
  2324. s.RemoveInstance(instance2)
  2325. // New instance should reuse number 2
  2326. instance4 := s.AddInstance()
  2327. assert.Equal(t, uint8(2), instance4.Num())
  2328. // Verify all instance numbers are unique
  2329. instances := s.Instances()
  2330. instanceNums := make(map[uint8]bool)
  2331. for _, inst := range instances {
  2332. assert.False(t, instanceNums[inst.Num()], "instance number %d should be unique", inst.Num())
  2333. instanceNums[inst.Num()] = true
  2334. }
  2335. })
  2336. t.Run("finds lowest available number", func(t *testing.T) {
  2337. s := NewSession()
  2338. // Create instances 1, 2, 3
  2339. instance1 := s.AddInstance()
  2340. instance2 := s.AddInstance()
  2341. instance3 := s.AddInstance()
  2342. assert.Equal(t, uint8(1), instance1.Num())
  2343. assert.Equal(t, uint8(2), instance2.Num())
  2344. assert.Equal(t, uint8(3), instance3.Num())
  2345. // Remove instance 1
  2346. s.RemoveInstance(instance1)
  2347. // New instance should get number 1 (lowest available)
  2348. instance4 := s.AddInstance()
  2349. assert.Equal(t, uint8(1), instance4.Num())
  2350. // Remove instance 2
  2351. s.RemoveInstance(instance2)
  2352. // New instance should get number 2 (lowest available)
  2353. instance5 := s.AddInstance()
  2354. assert.Equal(t, uint8(2), instance5.Num())
  2355. // Verify instance 3 still has its number
  2356. assert.Equal(t, uint8(3), instance3.Num())
  2357. })
  2358. t.Run("panics when all instance numbers are taken", func(t *testing.T) {
  2359. s := NewSession()
  2360. // Fill up all 255 instance numbers
  2361. instances := make([]*SessionInstance, 255)
  2362. for i := 0; i < 255; i++ {
  2363. instances[i] = s.AddInstance()
  2364. }
  2365. // Verify we have 255 instances
  2366. assert.Equal(t, 255, s.InstanceCount())
  2367. // Try to create one more - should panic
  2368. assert.PanicsWithValue(t, "all instance numbers are taken (max 255 instances per session)", func() {
  2369. s.AddInstance()
  2370. })
  2371. })
  2372. }
  2373. func TestSessionInstance_Caps(t *testing.T) {
  2374. t.Run("returns the instance's own capabilities, not the session's union", func(t *testing.T) {
  2375. sess := NewSession()
  2376. first := sess.AddInstance()
  2377. second := sess.AddInstance()
  2378. first.SetCaps([][16]byte{wire.CapChat})
  2379. second.SetCaps([][16]byte{wire.CapFileTransfer})
  2380. assert.Equal(t, [][16]byte{wire.CapChat}, first.Caps())
  2381. assert.Len(t, sess.Caps(), 2)
  2382. })
  2383. t.Run("returns a copy the caller cannot write through", func(t *testing.T) {
  2384. instance := NewSession().AddInstance()
  2385. instance.SetCaps([][16]byte{wire.CapChat})
  2386. caps := instance.Caps()
  2387. caps[0] = wire.CapFileTransfer
  2388. assert.Equal(t, [][16]byte{wire.CapChat}, instance.Caps())
  2389. })
  2390. t.Run("an instance with no capabilities returns an empty list", func(t *testing.T) {
  2391. assert.Empty(t, NewSession().AddInstance().Caps())
  2392. })
  2393. }
  2394. func TestSessionInstance_Mood(t *testing.T) {
  2395. t.Run("returns the mood among the advertised capabilities", func(t *testing.T) {
  2396. instance := NewSession().AddInstance()
  2397. instance.SetCaps([][16]byte{wire.CapChat, wire.CapXStatusBeer})
  2398. assert.Equal(t, uuid.UUID(wire.CapXStatusBeer), instance.Mood())
  2399. })
  2400. t.Run("returns the zero UUID when no capability is a mood", func(t *testing.T) {
  2401. instance := NewSession().AddInstance()
  2402. instance.SetCaps([][16]byte{wire.CapChat})
  2403. assert.Equal(t, uuid.UUID{}, instance.Mood())
  2404. })
  2405. t.Run("returns the zero UUID when no capabilities are advertised", func(t *testing.T) {
  2406. assert.Equal(t, uuid.UUID{}, NewSession().AddInstance().Mood())
  2407. })
  2408. }