session_test.go 73 KB

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