oservice_test.go 112 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565
  1. package foodgroup
  2. import (
  3. "bytes"
  4. "context"
  5. "io"
  6. "log/slog"
  7. "testing"
  8. "time"
  9. "github.com/stretchr/testify/assert"
  10. "github.com/stretchr/testify/mock"
  11. "github.com/stretchr/testify/require"
  12. "github.com/mk6i/open-oscar-server/config"
  13. "github.com/mk6i/open-oscar-server/state"
  14. "github.com/mk6i/open-oscar-server/wire"
  15. )
  16. func TestOServiceService_ServiceRequest(t *testing.T) {
  17. chatRoom := state.NewChatRoom("the-chat-room", state.NewIdentScreenName(""), state.PrivateExchange)
  18. cases := []struct {
  19. // name is the unit test name
  20. name string
  21. // service is the OSCAR service type
  22. service uint16
  23. // listener is the connection listener
  24. listener config.Listener
  25. // instance is the session of the user requesting the chat service
  26. // info
  27. instance *state.SessionInstance
  28. // inputSNAC is the SNAC sent by the sender client
  29. inputSNAC wire.SNACMessage
  30. // expectSNACFrame is the SNAC frame sent from the server to the recipient
  31. // client
  32. expectOutput wire.SNACMessage
  33. // mockParams is the list of params sent to mocks that satisfy this
  34. // method's dependencies
  35. mockParams mockParams
  36. // expectErr is the expected error returned by the router
  37. expectErr error
  38. }{
  39. {
  40. name: "request info for connecting to admin svc, return admin svc connection metadata",
  41. service: wire.BOS,
  42. listener: config.Listener{BOSAdvertisedHostPlain: "127.0.0.1:1234"},
  43. instance: newTestInstance("me"),
  44. inputSNAC: wire.SNACMessage{
  45. Frame: wire.SNACFrame{
  46. RequestID: 1234,
  47. },
  48. Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
  49. FoodGroup: wire.Admin,
  50. },
  51. },
  52. expectOutput: wire.SNACMessage{
  53. Frame: wire.SNACFrame{
  54. FoodGroup: wire.OService,
  55. SubGroup: wire.OServiceServiceResponse,
  56. RequestID: 1234,
  57. },
  58. Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
  59. TLVRestBlock: wire.TLVRestBlock{
  60. TLVList: wire.TLVList{
  61. wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.Admin),
  62. wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1234"),
  63. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
  64. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  65. },
  66. },
  67. },
  68. },
  69. mockParams: mockParams{
  70. cookieBakerParams: cookieBakerParams{
  71. cookieIssueParams: cookieIssueParams{
  72. {
  73. dataIn: []byte{
  74. 0x00, 0x07, // admin service
  75. 0x02, 'm', 'e',
  76. 0x0, // no client ID
  77. 0x0, // no chat cookie
  78. 0x0, // multi conn flag
  79. 0x0, // kerberos flag
  80. 0x01, // session num
  81. },
  82. cookieOut: []byte("the-cookie"),
  83. },
  84. },
  85. },
  86. },
  87. },
  88. {
  89. name: "request info for connecting to alert svc, return alert svc connection metadata",
  90. service: wire.BOS,
  91. listener: config.Listener{BOSAdvertisedHostPlain: "127.0.0.1:1234"},
  92. instance: newTestInstance("me"),
  93. inputSNAC: wire.SNACMessage{
  94. Frame: wire.SNACFrame{
  95. RequestID: 1234,
  96. },
  97. Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
  98. FoodGroup: wire.Alert,
  99. },
  100. },
  101. expectOutput: wire.SNACMessage{
  102. Frame: wire.SNACFrame{
  103. FoodGroup: wire.OService,
  104. SubGroup: wire.OServiceServiceResponse,
  105. RequestID: 1234,
  106. },
  107. Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
  108. TLVRestBlock: wire.TLVRestBlock{
  109. TLVList: wire.TLVList{
  110. wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.Alert),
  111. wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1234"),
  112. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
  113. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  114. },
  115. },
  116. },
  117. },
  118. mockParams: mockParams{
  119. cookieBakerParams: cookieBakerParams{
  120. cookieIssueParams: cookieIssueParams{
  121. {
  122. dataIn: []byte{
  123. 0x00, 0x18, // alert service
  124. 0x02, 'm', 'e',
  125. 0x0, // no client ID
  126. 0x0, // no chat cookie
  127. 0x0, // multi conn flag
  128. 0x0, // kerberos flag
  129. 0x01, // session num
  130. },
  131. cookieOut: []byte("the-cookie"),
  132. },
  133. },
  134. },
  135. },
  136. },
  137. {
  138. name: "request info for connecting to BART service, return BART connection metadata",
  139. service: wire.BOS,
  140. listener: config.Listener{BOSAdvertisedHostPlain: "127.0.0.1:1234"},
  141. instance: newTestInstance("me"),
  142. inputSNAC: wire.SNACMessage{
  143. Frame: wire.SNACFrame{
  144. RequestID: 1234,
  145. },
  146. Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
  147. FoodGroup: wire.BART,
  148. },
  149. },
  150. expectOutput: wire.SNACMessage{
  151. Frame: wire.SNACFrame{
  152. FoodGroup: wire.OService,
  153. SubGroup: wire.OServiceServiceResponse,
  154. RequestID: 1234,
  155. },
  156. Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
  157. TLVRestBlock: wire.TLVRestBlock{
  158. TLVList: wire.TLVList{
  159. wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.BART),
  160. wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1234"),
  161. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
  162. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  163. },
  164. },
  165. },
  166. },
  167. mockParams: mockParams{
  168. cookieBakerParams: cookieBakerParams{
  169. cookieIssueParams: cookieIssueParams{
  170. {
  171. dataIn: []byte{
  172. 0x00, 0x10, // chatnav service
  173. 0x02, 'm', 'e',
  174. 0x0, // no client ID
  175. 0x0, // no chat cookie
  176. 0x0, // multi conn flag
  177. 0x0, // kerberos flag
  178. 0x01, // session num
  179. },
  180. cookieOut: []byte("the-cookie"),
  181. },
  182. },
  183. },
  184. },
  185. },
  186. {
  187. name: "request info for connecting to chat nav, return chat nav connection metadata",
  188. service: wire.BOS,
  189. listener: config.Listener{BOSAdvertisedHostPlain: "127.0.0.1:1234"},
  190. instance: newTestInstance("me"),
  191. inputSNAC: wire.SNACMessage{
  192. Frame: wire.SNACFrame{
  193. RequestID: 1234,
  194. },
  195. Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
  196. FoodGroup: wire.ChatNav,
  197. },
  198. },
  199. expectOutput: wire.SNACMessage{
  200. Frame: wire.SNACFrame{
  201. FoodGroup: wire.OService,
  202. SubGroup: wire.OServiceServiceResponse,
  203. RequestID: 1234,
  204. },
  205. Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
  206. TLVRestBlock: wire.TLVRestBlock{
  207. TLVList: wire.TLVList{
  208. wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.ChatNav),
  209. wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1234"),
  210. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
  211. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  212. },
  213. },
  214. },
  215. },
  216. mockParams: mockParams{
  217. cookieBakerParams: cookieBakerParams{
  218. cookieIssueParams: cookieIssueParams{
  219. {
  220. dataIn: []byte{
  221. 0x00, 0x0d, // chatnav service
  222. 0x02, 'm', 'e',
  223. 0x0, // no client ID
  224. 0x0, // no chat cookie
  225. 0x0, // multi conn flag
  226. 0x0, // kerberos flag
  227. 0x01, // session num
  228. },
  229. cookieOut: []byte("the-cookie"),
  230. },
  231. },
  232. },
  233. },
  234. },
  235. {
  236. name: "request info for connecting to chat room, return chat service and chat room metadata",
  237. service: wire.BOS,
  238. listener: config.Listener{BOSAdvertisedHostPlain: "127.0.0.1:1234"},
  239. instance: newTestInstance("me"),
  240. inputSNAC: wire.SNACMessage{
  241. Frame: wire.SNACFrame{
  242. RequestID: 1234,
  243. },
  244. Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
  245. FoodGroup: wire.Chat,
  246. TLVRestBlock: wire.TLVRestBlock{
  247. TLVList: wire.TLVList{
  248. wire.NewTLVBE(0x01, wire.SNAC_0x01_0x04_TLVRoomInfo{
  249. Exchange: chatRoom.Exchange(),
  250. Cookie: chatRoom.Cookie(),
  251. InstanceNumber: chatRoom.InstanceNumber(),
  252. }),
  253. },
  254. },
  255. },
  256. },
  257. expectOutput: wire.SNACMessage{
  258. Frame: wire.SNACFrame{
  259. FoodGroup: wire.OService,
  260. SubGroup: wire.OServiceServiceResponse,
  261. RequestID: 1234,
  262. },
  263. Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
  264. TLVRestBlock: wire.TLVRestBlock{
  265. TLVList: wire.TLVList{
  266. wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.Chat),
  267. wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1234"),
  268. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-auth-cookie")),
  269. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  270. },
  271. },
  272. },
  273. },
  274. mockParams: func() mockParams {
  275. return mockParams{
  276. chatRoomRegistryParams: chatRoomRegistryParams{
  277. chatRoomByCookieParams: chatRoomByCookieParams{
  278. {
  279. cookie: chatRoom.Cookie(),
  280. room: chatRoom,
  281. },
  282. },
  283. },
  284. cookieBakerParams: cookieBakerParams{
  285. cookieIssueParams: cookieIssueParams{
  286. {
  287. dataIn: []byte{
  288. 0x00, 0x0e, // chat service,
  289. 0x02, 'm', 'e', // screen name
  290. 0x00, // no client ID
  291. 0x11, '4', '-', '0', '-', 't', 'h', 'e', '-', 'c', 'h', 'a', 't', '-', 'r', 'o', 'o', 'm',
  292. 0x0, // multi conn flag
  293. 0x0, // kerberos flag
  294. 0x01, // session num
  295. },
  296. cookieOut: []byte("the-auth-cookie"),
  297. },
  298. },
  299. },
  300. }
  301. }(),
  302. },
  303. {
  304. name: "request info for connecting to BART service, return BART connection metadata",
  305. service: wire.BOS,
  306. listener: config.Listener{BOSAdvertisedHostPlain: "127.0.0.1:1234"},
  307. instance: newTestInstance("me"),
  308. inputSNAC: wire.SNACMessage{
  309. Frame: wire.SNACFrame{
  310. RequestID: 1234,
  311. },
  312. Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
  313. FoodGroup: wire.ODir,
  314. },
  315. },
  316. expectOutput: wire.SNACMessage{
  317. Frame: wire.SNACFrame{
  318. FoodGroup: wire.OService,
  319. SubGroup: wire.OServiceServiceResponse,
  320. RequestID: 1234,
  321. },
  322. Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
  323. TLVRestBlock: wire.TLVRestBlock{
  324. TLVList: wire.TLVList{
  325. wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.ODir),
  326. wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1234"),
  327. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
  328. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  329. },
  330. },
  331. },
  332. },
  333. mockParams: mockParams{
  334. cookieBakerParams: cookieBakerParams{
  335. cookieIssueParams: cookieIssueParams{
  336. {
  337. dataIn: []byte{
  338. 0x00, 0x0F, // chatnav service
  339. 0x02, 'm', 'e',
  340. 0x0, // no client ID
  341. 0x0, // no chat cookie
  342. 0x0, // multi conn flag
  343. 0x0, // kerberos flag
  344. 0x01, // session num
  345. },
  346. cookieOut: []byte("the-cookie"),
  347. },
  348. },
  349. },
  350. },
  351. },
  352. {
  353. name: "request info for connecting to non-existent chat room, return ErrChatRoomNotFound",
  354. service: wire.BOS,
  355. listener: config.Listener{BOSAdvertisedHostPlain: "127.0.0.1:1234"},
  356. instance: newTestInstance("me"),
  357. inputSNAC: wire.SNACMessage{
  358. Frame: wire.SNACFrame{
  359. RequestID: 1234,
  360. },
  361. Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
  362. FoodGroup: wire.Chat,
  363. TLVRestBlock: wire.TLVRestBlock{
  364. TLVList: wire.TLVList{
  365. wire.NewTLVBE(0x01, wire.SNAC_0x01_0x04_TLVRoomInfo{
  366. Exchange: 8,
  367. Cookie: "the-chat-cookie",
  368. InstanceNumber: 16,
  369. }),
  370. },
  371. },
  372. },
  373. },
  374. mockParams: mockParams{
  375. chatRoomRegistryParams: chatRoomRegistryParams{
  376. chatRoomByCookieParams: chatRoomByCookieParams{
  377. {
  378. cookie: "the-chat-cookie",
  379. err: state.ErrChatRoomNotFound,
  380. },
  381. },
  382. },
  383. },
  384. expectErr: state.ErrChatRoomNotFound,
  385. },
  386. {
  387. name: "request info from a non-BOS service",
  388. service: wire.Chat,
  389. instance: newTestInstance("me"),
  390. inputSNAC: wire.SNACMessage{
  391. Frame: wire.SNACFrame{
  392. RequestID: 1234,
  393. },
  394. Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
  395. FoodGroup: wire.ICBM,
  396. },
  397. },
  398. expectOutput: wire.SNACMessage{
  399. Frame: wire.SNACFrame{
  400. FoodGroup: wire.OService,
  401. SubGroup: wire.OServiceErr,
  402. RequestID: 1234,
  403. },
  404. Body: wire.SNACError{
  405. Code: wire.ErrorCodeNotSupportedByHost,
  406. },
  407. },
  408. },
  409. {
  410. name: "request info for ICBM service, return invalid SNAC err",
  411. service: wire.BOS,
  412. instance: newTestInstance("me"),
  413. inputSNAC: wire.SNACMessage{
  414. Frame: wire.SNACFrame{
  415. RequestID: 1234,
  416. },
  417. Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
  418. FoodGroup: wire.ICBM,
  419. },
  420. },
  421. expectOutput: wire.SNACMessage{
  422. Frame: wire.SNACFrame{
  423. FoodGroup: wire.OService,
  424. SubGroup: wire.OServiceErr,
  425. RequestID: 1234,
  426. },
  427. Body: wire.SNACError{
  428. Code: wire.ErrorCodeServiceUnavailable,
  429. },
  430. },
  431. },
  432. {
  433. name: "request info for connecting to admin svc with SSL, return admin svc SSL connection metadata",
  434. service: wire.BOS,
  435. listener: config.Listener{BOSAdvertisedHostPlain: "127.0.0.1:1234", BOSAdvertisedHostSSL: "127.0.0.1:1235", HasSSL: true},
  436. instance: newTestInstance("me"),
  437. inputSNAC: wire.SNACMessage{
  438. Frame: wire.SNACFrame{
  439. RequestID: 1234,
  440. },
  441. Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
  442. FoodGroup: wire.Admin,
  443. TLVRestBlock: wire.TLVRestBlock{
  444. TLVList: wire.TLVList{
  445. wire.NewTLVBE(wire.OserviceTLVTagsSSLUseSSL, []byte{}),
  446. },
  447. },
  448. },
  449. },
  450. expectOutput: wire.SNACMessage{
  451. Frame: wire.SNACFrame{
  452. FoodGroup: wire.OService,
  453. SubGroup: wire.OServiceServiceResponse,
  454. RequestID: 1234,
  455. },
  456. Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
  457. TLVRestBlock: wire.TLVRestBlock{
  458. TLVList: wire.TLVList{
  459. wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.Admin),
  460. wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1235"),
  461. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
  462. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x02)),
  463. },
  464. },
  465. },
  466. },
  467. mockParams: mockParams{
  468. cookieBakerParams: cookieBakerParams{
  469. cookieIssueParams: cookieIssueParams{
  470. {
  471. dataIn: []byte{
  472. 0x00, 0x07, // admin service
  473. 0x02, 'm', 'e',
  474. 0x0, // no client ID
  475. 0x0, // no chat cookie
  476. 0x0, // multi conn flag
  477. 0x0, // kerberos flag
  478. 0x01, // session num
  479. },
  480. cookieOut: []byte("the-cookie"),
  481. },
  482. },
  483. },
  484. },
  485. },
  486. {
  487. name: "request info for connecting to alert svc with SSL, return alert svc SSL connection metadata",
  488. service: wire.BOS,
  489. listener: config.Listener{BOSAdvertisedHostPlain: "127.0.0.1:1234", BOSAdvertisedHostSSL: "127.0.0.1:1235", HasSSL: true},
  490. instance: newTestInstance("me"),
  491. inputSNAC: wire.SNACMessage{
  492. Frame: wire.SNACFrame{
  493. RequestID: 1234,
  494. },
  495. Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
  496. FoodGroup: wire.Alert,
  497. TLVRestBlock: wire.TLVRestBlock{
  498. TLVList: wire.TLVList{
  499. wire.NewTLVBE(wire.OserviceTLVTagsSSLUseSSL, []byte{}),
  500. },
  501. },
  502. },
  503. },
  504. expectOutput: wire.SNACMessage{
  505. Frame: wire.SNACFrame{
  506. FoodGroup: wire.OService,
  507. SubGroup: wire.OServiceServiceResponse,
  508. RequestID: 1234,
  509. },
  510. Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
  511. TLVRestBlock: wire.TLVRestBlock{
  512. TLVList: wire.TLVList{
  513. wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.Alert),
  514. wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1235"),
  515. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
  516. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x02)),
  517. },
  518. },
  519. },
  520. },
  521. mockParams: mockParams{
  522. cookieBakerParams: cookieBakerParams{
  523. cookieIssueParams: cookieIssueParams{
  524. {
  525. dataIn: []byte{
  526. 0x00, 0x18, // alert service
  527. 0x02, 'm', 'e',
  528. 0x0, // no client ID
  529. 0x0, // no chat cookie
  530. 0x0, // multi conn flag
  531. 0x0, // kerberos flag
  532. 0x01, // session num
  533. },
  534. cookieOut: []byte("the-cookie"),
  535. },
  536. },
  537. },
  538. },
  539. },
  540. {
  541. name: "request info for connecting to BART service with SSL, return BART SSL connection metadata",
  542. service: wire.BOS,
  543. listener: config.Listener{BOSAdvertisedHostPlain: "127.0.0.1:1234", BOSAdvertisedHostSSL: "127.0.0.1:1235", HasSSL: true},
  544. instance: newTestInstance("me"),
  545. inputSNAC: wire.SNACMessage{
  546. Frame: wire.SNACFrame{
  547. RequestID: 1234,
  548. },
  549. Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
  550. FoodGroup: wire.BART,
  551. TLVRestBlock: wire.TLVRestBlock{
  552. TLVList: wire.TLVList{
  553. wire.NewTLVBE(wire.OserviceTLVTagsSSLUseSSL, []byte{}),
  554. },
  555. },
  556. },
  557. },
  558. expectOutput: wire.SNACMessage{
  559. Frame: wire.SNACFrame{
  560. FoodGroup: wire.OService,
  561. SubGroup: wire.OServiceServiceResponse,
  562. RequestID: 1234,
  563. },
  564. Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
  565. TLVRestBlock: wire.TLVRestBlock{
  566. TLVList: wire.TLVList{
  567. wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.BART),
  568. wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1235"),
  569. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
  570. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x02)),
  571. },
  572. },
  573. },
  574. },
  575. mockParams: mockParams{
  576. cookieBakerParams: cookieBakerParams{
  577. cookieIssueParams: cookieIssueParams{
  578. {
  579. dataIn: []byte{
  580. 0x00, 0x10, // BART service
  581. 0x02, 'm', 'e',
  582. 0x0, // no client ID
  583. 0x0, // no chat cookie
  584. 0x0, // multi conn flag
  585. 0x0, // kerberos flag
  586. 0x01, // session num
  587. },
  588. cookieOut: []byte("the-cookie"),
  589. },
  590. },
  591. },
  592. },
  593. },
  594. {
  595. name: "request info for connecting to chat nav with SSL, return chat nav SSL connection metadata",
  596. service: wire.BOS,
  597. listener: config.Listener{BOSAdvertisedHostPlain: "127.0.0.1:1234", BOSAdvertisedHostSSL: "127.0.0.1:1235", HasSSL: true},
  598. instance: newTestInstance("me"),
  599. inputSNAC: wire.SNACMessage{
  600. Frame: wire.SNACFrame{
  601. RequestID: 1234,
  602. },
  603. Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
  604. FoodGroup: wire.ChatNav,
  605. TLVRestBlock: wire.TLVRestBlock{
  606. TLVList: wire.TLVList{
  607. wire.NewTLVBE(wire.OserviceTLVTagsSSLUseSSL, []byte{}),
  608. },
  609. },
  610. },
  611. },
  612. expectOutput: wire.SNACMessage{
  613. Frame: wire.SNACFrame{
  614. FoodGroup: wire.OService,
  615. SubGroup: wire.OServiceServiceResponse,
  616. RequestID: 1234,
  617. },
  618. Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
  619. TLVRestBlock: wire.TLVRestBlock{
  620. TLVList: wire.TLVList{
  621. wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.ChatNav),
  622. wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1235"),
  623. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
  624. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x02)),
  625. },
  626. },
  627. },
  628. },
  629. mockParams: mockParams{
  630. cookieBakerParams: cookieBakerParams{
  631. cookieIssueParams: cookieIssueParams{
  632. {
  633. dataIn: []byte{
  634. 0x00, 0x0d, // chatnav service
  635. 0x02, 'm', 'e',
  636. 0x0, // no client ID
  637. 0x0, // no chat cookie
  638. 0x0, // multi conn flag
  639. 0x0, // kerberos flag
  640. 0x01, // session num
  641. },
  642. cookieOut: []byte("the-cookie"),
  643. },
  644. },
  645. },
  646. },
  647. },
  648. {
  649. name: "request info for connecting to chat room with SSL, return chat service SSL connection metadata",
  650. service: wire.BOS,
  651. listener: config.Listener{BOSAdvertisedHostPlain: "127.0.0.1:1234", BOSAdvertisedHostSSL: "127.0.0.1:1235", HasSSL: true},
  652. instance: newTestInstance("me"),
  653. inputSNAC: wire.SNACMessage{
  654. Frame: wire.SNACFrame{
  655. RequestID: 1234,
  656. },
  657. Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
  658. FoodGroup: wire.Chat,
  659. TLVRestBlock: wire.TLVRestBlock{
  660. TLVList: wire.TLVList{
  661. wire.NewTLVBE(0x01, wire.SNAC_0x01_0x04_TLVRoomInfo{
  662. Exchange: chatRoom.Exchange(),
  663. Cookie: chatRoom.Cookie(),
  664. InstanceNumber: chatRoom.InstanceNumber(),
  665. }),
  666. wire.NewTLVBE(wire.OserviceTLVTagsSSLUseSSL, []byte{}),
  667. },
  668. },
  669. },
  670. },
  671. expectOutput: wire.SNACMessage{
  672. Frame: wire.SNACFrame{
  673. FoodGroup: wire.OService,
  674. SubGroup: wire.OServiceServiceResponse,
  675. RequestID: 1234,
  676. },
  677. Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
  678. TLVRestBlock: wire.TLVRestBlock{
  679. TLVList: wire.TLVList{
  680. wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.Chat),
  681. wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1235"),
  682. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-auth-cookie")),
  683. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x02)),
  684. },
  685. },
  686. },
  687. },
  688. mockParams: func() mockParams {
  689. return mockParams{
  690. chatRoomRegistryParams: chatRoomRegistryParams{
  691. chatRoomByCookieParams: chatRoomByCookieParams{
  692. {
  693. cookie: chatRoom.Cookie(),
  694. room: chatRoom,
  695. },
  696. },
  697. },
  698. cookieBakerParams: cookieBakerParams{
  699. cookieIssueParams: cookieIssueParams{
  700. {
  701. dataIn: []byte{
  702. 0x00, 0x0e, // chat service,
  703. 0x02, 'm', 'e', // screen name
  704. 0x00, // no client ID
  705. 0x11, '4', '-', '0', '-', 't', 'h', 'e', '-', 'c', 'h', 'a', 't', '-', 'r', 'o', 'o', 'm',
  706. 0x0, // multi conn flag
  707. 0x0, // kerberos flag
  708. 0x01, // session num
  709. },
  710. cookieOut: []byte("the-auth-cookie"),
  711. },
  712. },
  713. },
  714. }
  715. }(),
  716. },
  717. {
  718. name: "request info for connecting to ODir service with SSL, return ODir SSL connection metadata",
  719. service: wire.BOS,
  720. listener: config.Listener{BOSAdvertisedHostPlain: "127.0.0.1:1234", BOSAdvertisedHostSSL: "127.0.0.1:1235", HasSSL: true},
  721. instance: newTestInstance("me"),
  722. inputSNAC: wire.SNACMessage{
  723. Frame: wire.SNACFrame{
  724. RequestID: 1234,
  725. },
  726. Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
  727. FoodGroup: wire.ODir,
  728. TLVRestBlock: wire.TLVRestBlock{
  729. TLVList: wire.TLVList{
  730. wire.NewTLVBE(wire.OserviceTLVTagsSSLUseSSL, []byte{}),
  731. },
  732. },
  733. },
  734. },
  735. expectOutput: wire.SNACMessage{
  736. Frame: wire.SNACFrame{
  737. FoodGroup: wire.OService,
  738. SubGroup: wire.OServiceServiceResponse,
  739. RequestID: 1234,
  740. },
  741. Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
  742. TLVRestBlock: wire.TLVRestBlock{
  743. TLVList: wire.TLVList{
  744. wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.ODir),
  745. wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1235"),
  746. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
  747. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x02)),
  748. },
  749. },
  750. },
  751. },
  752. mockParams: mockParams{
  753. cookieBakerParams: cookieBakerParams{
  754. cookieIssueParams: cookieIssueParams{
  755. {
  756. dataIn: []byte{
  757. 0x00, 0x0F, // ODir service
  758. 0x02, 'm', 'e',
  759. 0x0, // no client ID
  760. 0x0, // no chat cookie
  761. 0x0, // multi conn flag
  762. 0x0, // kerberos flag
  763. 0x01, // session num
  764. },
  765. cookieOut: []byte("the-cookie"),
  766. },
  767. },
  768. },
  769. },
  770. },
  771. {
  772. name: "request SSL service but listener doesn't support SSL, return error",
  773. service: wire.BOS,
  774. listener: config.Listener{BOSAdvertisedHostPlain: "127.0.0.1:1234", HasSSL: false},
  775. instance: newTestInstance("me"),
  776. inputSNAC: wire.SNACMessage{
  777. Frame: wire.SNACFrame{
  778. RequestID: 1234,
  779. },
  780. Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
  781. FoodGroup: wire.Admin,
  782. TLVRestBlock: wire.TLVRestBlock{
  783. TLVList: wire.TLVList{
  784. wire.NewTLVBE(wire.OserviceTLVTagsSSLUseSSL, []byte{}),
  785. },
  786. },
  787. },
  788. },
  789. expectOutput: wire.SNACMessage{
  790. Frame: wire.SNACFrame{
  791. FoodGroup: wire.OService,
  792. SubGroup: wire.OServiceErr,
  793. RequestID: 1234,
  794. },
  795. Body: wire.SNACError{
  796. Code: wire.ErrorCodeGeneralFailure,
  797. },
  798. },
  799. mockParams: mockParams{},
  800. },
  801. }
  802. for _, tc := range cases {
  803. t.Run(tc.name, func(t *testing.T) {
  804. //
  805. // initialize dependencies
  806. //
  807. chatRoomManager := newMockChatRoomRegistry(t)
  808. for _, params := range tc.mockParams.chatRoomByCookieParams {
  809. chatRoomManager.EXPECT().
  810. ChatRoomByCookie(context.Background(), params.cookie).
  811. Return(params.room, params.err)
  812. }
  813. cookieIssuer := newMockCookieBaker(t)
  814. for _, params := range tc.mockParams.cookieIssueParams {
  815. cookieIssuer.EXPECT().
  816. Issue(params.dataIn).
  817. Return(params.cookieOut, params.err)
  818. }
  819. chatMessageRelayer := newMockChatMessageRelayer(t)
  820. //
  821. // send input SNAC
  822. //
  823. svc := NewOServiceService(config.Config{}, nil, slog.Default(), cookieIssuer, chatRoomManager, nil, nil, nil, wire.DefaultSNACRateLimits(), chatMessageRelayer, nil, nil, nil)
  824. outputSNAC, err := svc.ServiceRequest(context.Background(), tc.service, tc.instance, tc.inputSNAC.Frame,
  825. tc.inputSNAC.Body.(wire.SNAC_0x01_0x04_OServiceServiceRequest), tc.listener)
  826. assert.ErrorIs(t, err, tc.expectErr)
  827. if tc.expectErr != nil {
  828. return
  829. }
  830. //
  831. // verify output
  832. //
  833. assert.Equal(t, tc.expectOutput, outputSNAC)
  834. })
  835. }
  836. }
  837. func TestOServiceService_ServiceRequest_LinkedAccountSignon(t *testing.T) {
  838. primaryUser := state.NewIdentScreenName("PrimaryUser")
  839. linkedUser := state.DisplayScreenName("LinkedUser")
  840. linkedUserIdent := state.NewIdentScreenName(string(linkedUser))
  841. // cookieDataFor returns the serialized ServerCookie bytes that fnIssueCookie
  842. // will pass to cookieIssuer.Issue, with the given MultiConnFlag.
  843. cookieDataFor := func(sn state.DisplayScreenName, flag wire.MultiConnFlag) []byte {
  844. buf := &bytes.Buffer{}
  845. assert.NoError(t, wire.MarshalBE(state.ServerCookie{
  846. Service: wire.BOS,
  847. ScreenName: sn,
  848. MultiConnFlag: uint8(flag),
  849. }, buf))
  850. return buf.Bytes()
  851. }
  852. makeBody := func(includeSUID bool, screenName string) wire.SNAC_0x01_0x04_OServiceServiceRequest {
  853. body := wire.SNAC_0x01_0x04_OServiceServiceRequest{
  854. FoodGroup: wire.OService,
  855. }
  856. if includeSUID {
  857. body.TLVList = append(body.TLVList,
  858. wire.NewTLVBE(uint16(0x0028), []byte("some-uuid-bytes")))
  859. }
  860. if screenName != "" {
  861. body.TLVList = append(body.TLVList,
  862. wire.NewTLVBE(uint16(0x01), []byte(screenName)))
  863. }
  864. return body
  865. }
  866. cases := []struct {
  867. name string
  868. instance *state.SessionInstance
  869. inputBody wire.SNAC_0x01_0x04_OServiceServiceRequest
  870. feedbagItems []wire.FeedbagItem
  871. feedbagErr error
  872. // setupCookie is whether to expect cookie issuance
  873. setupCookie bool
  874. expectOutput wire.SNACMessage
  875. wantErrContains string
  876. wantErr error
  877. }{
  878. {
  879. name: "linked account signon OK, returns BOS cookie with no MultiConnFlag",
  880. inputBody: makeBody(true, string(linkedUser)),
  881. feedbagItems: []wire.FeedbagItem{{ClassID: wire.FeedbagClassIdAlInfo, Name: linkedUserIdent.String()}},
  882. setupCookie: true,
  883. expectOutput: wire.SNACMessage{
  884. Frame: wire.SNACFrame{
  885. FoodGroup: wire.OService,
  886. SubGroup: wire.OServiceServiceResponse,
  887. RequestID: 1234,
  888. },
  889. Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
  890. TLVRestBlock: wire.TLVRestBlock{
  891. TLVList: wire.TLVList{
  892. wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.OService),
  893. wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:5190"),
  894. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
  895. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  896. },
  897. },
  898. },
  899. },
  900. },
  901. {
  902. name: "primary has MultiConnFlagsRecentClient, linked account cookie inherits flag",
  903. instance: newTestInstance(state.DisplayScreenName(primaryUser.String()), sessOptMultiConnFlag(wire.MultiConnFlagsRecentClient)),
  904. inputBody: makeBody(true, string(linkedUser)),
  905. feedbagItems: []wire.FeedbagItem{{ClassID: wire.FeedbagClassIdAlInfo, Name: linkedUserIdent.String()}},
  906. setupCookie: true,
  907. expectOutput: wire.SNACMessage{
  908. Frame: wire.SNACFrame{
  909. FoodGroup: wire.OService,
  910. SubGroup: wire.OServiceServiceResponse,
  911. RequestID: 1234,
  912. },
  913. Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
  914. TLVRestBlock: wire.TLVRestBlock{
  915. TLVList: wire.TLVList{
  916. wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.OService),
  917. wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:5190"),
  918. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
  919. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  920. },
  921. },
  922. },
  923. },
  924. },
  925. {
  926. name: "primary has MultiConnFlagsSingleClient, linked account cookie inherits flag",
  927. instance: newTestInstance(state.DisplayScreenName(primaryUser.String()), sessOptMultiConnFlag(wire.MultiConnFlagsSingleClient)),
  928. inputBody: makeBody(true, string(linkedUser)),
  929. feedbagItems: []wire.FeedbagItem{{ClassID: wire.FeedbagClassIdAlInfo, Name: linkedUserIdent.String()}},
  930. setupCookie: true,
  931. expectOutput: wire.SNACMessage{
  932. Frame: wire.SNACFrame{
  933. FoodGroup: wire.OService,
  934. SubGroup: wire.OServiceServiceResponse,
  935. RequestID: 1234,
  936. },
  937. Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
  938. TLVRestBlock: wire.TLVRestBlock{
  939. TLVList: wire.TLVList{
  940. wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.OService),
  941. wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:5190"),
  942. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
  943. wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
  944. },
  945. },
  946. },
  947. },
  948. },
  949. {
  950. name: "missing SUID TLV 0x0028, returns error",
  951. inputBody: makeBody(false, string(linkedUser)),
  952. wantErrContains: "unknown OService request",
  953. },
  954. {
  955. name: "missing screenname TLV 0x01, returns error",
  956. inputBody: makeBody(true, ""),
  957. wantErrContains: "new session request missing linked screenname TLV 0x01",
  958. },
  959. {
  960. name: "accounts not linked, returns error",
  961. inputBody: makeBody(true, string(linkedUser)),
  962. wantErrContains: "linked account session requested but accounts are not linked",
  963. },
  964. {
  965. name: "feedbag lookup error, error propagated",
  966. inputBody: makeBody(true, string(linkedUser)),
  967. feedbagErr: io.EOF,
  968. wantErr: io.EOF,
  969. },
  970. }
  971. for _, tc := range cases {
  972. t.Run(tc.name, func(t *testing.T) {
  973. instance := tc.instance
  974. if instance == nil {
  975. instance = newTestInstance(state.DisplayScreenName(primaryUser.String()))
  976. }
  977. cookieIssuer := newMockCookieBaker(t)
  978. if tc.setupCookie {
  979. cookieIssuer.EXPECT().
  980. Issue(cookieDataFor(linkedUser, instance.MultiConnFlag())).
  981. Return([]byte("the-cookie"), nil)
  982. }
  983. feedbagManager := newMockFeedbagManager(t)
  984. if tc.inputBody.HasTag(0x0028) {
  985. if snBytes, ok := tc.inputBody.Bytes(0x01); ok && len(snBytes) > 0 {
  986. feedbagManager.EXPECT().
  987. Feedbag(matchContext(), primaryUser).
  988. Return(tc.feedbagItems, tc.feedbagErr)
  989. }
  990. }
  991. svc := NewOServiceService(config.Config{}, nil, slog.Default(), cookieIssuer, nil, nil, nil, nil,
  992. wire.DefaultSNACRateLimits(), nil, nil, nil, feedbagManager)
  993. listener := config.Listener{BOSAdvertisedHostPlain: "127.0.0.1:5190"}
  994. outputSNAC, err := svc.ServiceRequest(context.Background(), wire.BOS, instance,
  995. wire.SNACFrame{RequestID: 1234}, tc.inputBody, listener)
  996. if tc.wantErrContains != "" {
  997. assert.ErrorContains(t, err, tc.wantErrContains)
  998. return
  999. }
  1000. if tc.wantErr != nil {
  1001. assert.ErrorIs(t, err, tc.wantErr)
  1002. return
  1003. }
  1004. assert.NoError(t, err)
  1005. assert.Equal(t, tc.expectOutput, outputSNAC)
  1006. })
  1007. }
  1008. }
  1009. func TestOServiceService_SetUserInfoFields(t *testing.T) {
  1010. cases := []struct {
  1011. // name is the unit test name
  1012. name string
  1013. // instance is the session of the user whose info is being set
  1014. instance *state.SessionInstance
  1015. // inputSNAC is the SNAC sent from the client to the server
  1016. inputSNAC wire.SNACMessage
  1017. // expectOutput is the SNAC reply sent from the server back to the
  1018. // client
  1019. expectOutput wire.SNACMessage
  1020. // broadcastMessage is the arrival/departure message sent to buddies
  1021. broadcastMessage []struct {
  1022. recipients []string
  1023. msg wire.SNACMessage
  1024. }
  1025. // interestedUserLookups contains all the users who have this user on
  1026. // their buddy list
  1027. interestedUserLookups map[string][]string
  1028. // expectErr is the expected error returned
  1029. expectErr error
  1030. // mockParams is the list of params sent to mocks that satisfy this
  1031. // method's dependencies
  1032. mockParams mockParams
  1033. // checkSession validates the state of the session
  1034. checkSession func(*testing.T, *state.Session)
  1035. }{
  1036. {
  1037. name: "set user status to visible aim < 6",
  1038. instance: newTestInstance("me", sessOptInvisible),
  1039. inputSNAC: wire.SNACMessage{
  1040. Frame: wire.SNACFrame{
  1041. RequestID: 1234,
  1042. },
  1043. Body: wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
  1044. TLVRestBlock: wire.TLVRestBlock{
  1045. TLVList: wire.TLVList{
  1046. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  1047. },
  1048. },
  1049. },
  1050. },
  1051. expectOutput: wire.SNACMessage{
  1052. Frame: wire.SNACFrame{
  1053. FoodGroup: wire.OService,
  1054. SubGroup: wire.OServiceUserInfoUpdate,
  1055. RequestID: 1234,
  1056. },
  1057. Body: func(val any) bool {
  1058. snac, ok := val.(wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate)
  1059. if !ok {
  1060. return false
  1061. }
  1062. if len(snac.UserInfo) == 0 {
  1063. return false
  1064. }
  1065. status, hasStatus := snac.UserInfo[0].Uint32BE(wire.OServiceUserInfoStatus)
  1066. return hasStatus && status == uint32(0x0000)
  1067. },
  1068. },
  1069. mockParams: mockParams{
  1070. buddyBroadcasterParams: buddyBroadcasterParams{
  1071. broadcastBuddyArrivedParams: broadcastBuddyArrivedParams{
  1072. {
  1073. screenName: state.DisplayScreenName("me"),
  1074. },
  1075. },
  1076. },
  1077. },
  1078. checkSession: func(t *testing.T, session *state.Session) {
  1079. assert.False(t, session.Invisible())
  1080. },
  1081. },
  1082. {
  1083. name: "set user status to invisible aim < 6",
  1084. instance: newTestInstance("me"),
  1085. inputSNAC: wire.SNACMessage{
  1086. Frame: wire.SNACFrame{
  1087. RequestID: 1234,
  1088. },
  1089. Body: wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
  1090. TLVRestBlock: wire.TLVRestBlock{
  1091. TLVList: wire.TLVList{
  1092. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0100)),
  1093. },
  1094. },
  1095. },
  1096. },
  1097. expectOutput: wire.SNACMessage{
  1098. Frame: wire.SNACFrame{
  1099. FoodGroup: wire.OService,
  1100. SubGroup: wire.OServiceUserInfoUpdate,
  1101. RequestID: 1234,
  1102. },
  1103. Body: func(val any) bool {
  1104. snac, ok := val.(wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate)
  1105. if !ok {
  1106. return false
  1107. }
  1108. if len(snac.UserInfo) == 0 {
  1109. return false
  1110. }
  1111. status, hasStatus := snac.UserInfo[0].Uint32BE(wire.OServiceUserInfoStatus)
  1112. return hasStatus && status == uint32(0x0100)
  1113. },
  1114. },
  1115. mockParams: mockParams{
  1116. buddyBroadcasterParams: buddyBroadcasterParams{
  1117. broadcastBuddyDepartedParams: broadcastBuddyDepartedParams{
  1118. {
  1119. screenName: state.NewIdentScreenName("me"),
  1120. },
  1121. },
  1122. },
  1123. },
  1124. checkSession: func(t *testing.T, session *state.Session) {
  1125. assert.True(t, session.Invisible())
  1126. },
  1127. },
  1128. {
  1129. name: "set user status to visible aim >= 6",
  1130. instance: newTestInstance("me", sessOptInvisible, sessOptSetFoodGroupVersion(wire.OService, 4)),
  1131. inputSNAC: wire.SNACMessage{
  1132. Frame: wire.SNACFrame{
  1133. RequestID: 1234,
  1134. },
  1135. Body: wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
  1136. TLVRestBlock: wire.TLVRestBlock{
  1137. TLVList: wire.TLVList{
  1138. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  1139. },
  1140. },
  1141. },
  1142. },
  1143. expectOutput: wire.SNACMessage{
  1144. Frame: wire.SNACFrame{
  1145. FoodGroup: wire.OService,
  1146. SubGroup: wire.OServiceUserInfoUpdate,
  1147. RequestID: 1234,
  1148. },
  1149. Body: func(val any) bool {
  1150. snac, ok := val.(wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate)
  1151. if !ok {
  1152. return false
  1153. }
  1154. if len(snac.UserInfo) == 0 {
  1155. return false
  1156. }
  1157. status, hasStatus := snac.UserInfo[0].Uint32BE(wire.OServiceUserInfoStatus)
  1158. return hasStatus && status == uint32(0x0000)
  1159. },
  1160. },
  1161. mockParams: mockParams{
  1162. buddyBroadcasterParams: buddyBroadcasterParams{
  1163. broadcastBuddyArrivedParams: broadcastBuddyArrivedParams{
  1164. {
  1165. screenName: state.DisplayScreenName("me"),
  1166. },
  1167. },
  1168. },
  1169. },
  1170. checkSession: func(t *testing.T, session *state.Session) {
  1171. assert.False(t, session.Invisible())
  1172. },
  1173. },
  1174. {
  1175. name: "set user status to invisible aim >= 6",
  1176. instance: newTestInstance("me", sessOptSetFoodGroupVersion(wire.OService, 4)),
  1177. inputSNAC: wire.SNACMessage{
  1178. Frame: wire.SNACFrame{
  1179. RequestID: 1234,
  1180. },
  1181. Body: wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
  1182. TLVRestBlock: wire.TLVRestBlock{
  1183. TLVList: wire.TLVList{
  1184. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0100)),
  1185. },
  1186. },
  1187. },
  1188. },
  1189. expectOutput: wire.SNACMessage{
  1190. Frame: wire.SNACFrame{
  1191. FoodGroup: wire.OService,
  1192. SubGroup: wire.OServiceUserInfoUpdate,
  1193. RequestID: 1234,
  1194. },
  1195. Body: func(val any) bool {
  1196. snac, ok := val.(wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate)
  1197. if !ok {
  1198. return false
  1199. }
  1200. if len(snac.UserInfo) == 0 {
  1201. return false
  1202. }
  1203. status, hasStatus := snac.UserInfo[0].Uint32BE(wire.OServiceUserInfoStatus)
  1204. return hasStatus && status == uint32(0x0100)
  1205. },
  1206. },
  1207. mockParams: mockParams{
  1208. buddyBroadcasterParams: buddyBroadcasterParams{
  1209. broadcastBuddyDepartedParams: broadcastBuddyDepartedParams{
  1210. {
  1211. screenName: state.NewIdentScreenName("me"),
  1212. },
  1213. },
  1214. },
  1215. },
  1216. checkSession: func(t *testing.T, session *state.Session) {
  1217. assert.True(t, session.Invisible())
  1218. },
  1219. },
  1220. {
  1221. name: "set ICQ direct connect info",
  1222. instance: newTestInstance("1000003", sessOptUserInfoFlag(wire.OServiceUserFlagICQ)),
  1223. inputSNAC: wire.SNACMessage{
  1224. Frame: wire.SNACFrame{
  1225. RequestID: 1234,
  1226. },
  1227. Body: wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
  1228. TLVRestBlock: wire.TLVRestBlock{
  1229. TLVList: wire.TLVList{
  1230. wire.NewTLVBE(wire.OServiceUserInfoICQDC, wire.ICQDCInfo{
  1231. DCType: 4,
  1232. ProtoVersion: 10,
  1233. }),
  1234. },
  1235. },
  1236. },
  1237. },
  1238. expectOutput: wire.SNACMessage{
  1239. Frame: wire.SNACFrame{
  1240. FoodGroup: wire.OService,
  1241. SubGroup: wire.OServiceUserInfoUpdate,
  1242. RequestID: 1234,
  1243. },
  1244. Body: func(val any) bool {
  1245. snac, ok := val.(wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate)
  1246. if !ok || len(snac.UserInfo) == 0 {
  1247. return false
  1248. }
  1249. dc, hasDC := snac.UserInfo[0].Bytes(wire.OServiceUserInfoICQDC)
  1250. if !hasDC {
  1251. return false
  1252. }
  1253. var got wire.ICQDCInfo
  1254. if err := wire.UnmarshalBE(&got, bytes.NewReader(dc)); err != nil {
  1255. return false
  1256. }
  1257. return got.DCType == 4 && got.ProtoVersion == 10
  1258. },
  1259. },
  1260. checkSession: func(t *testing.T, session *state.Session) {
  1261. info := session.Instances()[0].ICQDCInfo()
  1262. assert.Equal(t, uint8(4), info.DCType)
  1263. assert.Equal(t, uint16(10), info.ProtoVersion)
  1264. },
  1265. },
  1266. }
  1267. for _, tc := range cases {
  1268. t.Run(tc.name, func(t *testing.T) {
  1269. buddyUpdateBroadcaster := newMockbuddyBroadcaster(t)
  1270. for _, params := range tc.mockParams.broadcastBuddyArrivedParams {
  1271. buddyUpdateBroadcaster.EXPECT().
  1272. BroadcastBuddyArrived(mock.Anything, state.NewIdentScreenName(params.screenName.String()), mock.MatchedBy(func(userInfo wire.TLVUserInfo) bool {
  1273. return userInfo.ScreenName == params.screenName.String()
  1274. })).
  1275. Return(params.err)
  1276. }
  1277. for _, params := range tc.mockParams.broadcastBuddyDepartedParams {
  1278. buddyUpdateBroadcaster.EXPECT().
  1279. BroadcastBuddyDeparted(mock.Anything, params.screenName).
  1280. Return(params.err)
  1281. }
  1282. svc := OServiceService{
  1283. cfg: config.Config{},
  1284. logger: slog.Default(),
  1285. buddyBroadcaster: buddyUpdateBroadcaster,
  1286. }
  1287. outputSNAC, err := svc.SetUserInfoFields(context.TODO(), tc.instance, tc.inputSNAC.Frame,
  1288. tc.inputSNAC.Body.(wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields))
  1289. assert.ErrorIs(t, err, tc.expectErr)
  1290. if tc.expectErr != nil {
  1291. return
  1292. }
  1293. assert.Equal(t, tc.expectOutput.Frame, outputSNAC.Frame)
  1294. if matcherFn, ok := tc.expectOutput.Body.(func(val any) bool); ok {
  1295. assert.True(t, matcherFn(outputSNAC.Body), "Body matcher function failed")
  1296. } else {
  1297. assert.Equal(t, tc.expectOutput.Body, outputSNAC.Body)
  1298. }
  1299. tc.checkSession(t, tc.instance.Session())
  1300. })
  1301. }
  1302. }
  1303. func TestOServiceService_RateParamsQuery(t *testing.T) {
  1304. rateClasses := wire.NewRateLimitClasses([5]wire.RateClass{
  1305. {
  1306. ID: 1,
  1307. WindowSize: 80,
  1308. ClearLevel: 2500,
  1309. AlertLevel: 2000,
  1310. LimitLevel: 1500,
  1311. DisconnectLevel: 800,
  1312. MaxLevel: 6000,
  1313. },
  1314. {
  1315. ID: 2,
  1316. WindowSize: 80,
  1317. ClearLevel: 3000,
  1318. AlertLevel: 2000,
  1319. LimitLevel: 1500,
  1320. DisconnectLevel: 1000,
  1321. MaxLevel: 6000,
  1322. },
  1323. {
  1324. ID: 3,
  1325. WindowSize: 20,
  1326. ClearLevel: 5100,
  1327. AlertLevel: 5000,
  1328. LimitLevel: 4000,
  1329. DisconnectLevel: 3000,
  1330. MaxLevel: 6000,
  1331. },
  1332. {
  1333. ID: 4,
  1334. WindowSize: 20,
  1335. ClearLevel: 5500,
  1336. AlertLevel: 5300,
  1337. LimitLevel: 4200,
  1338. DisconnectLevel: 3000,
  1339. MaxLevel: 8000,
  1340. },
  1341. {
  1342. ID: 5,
  1343. WindowSize: 10,
  1344. ClearLevel: 5500,
  1345. AlertLevel: 5300,
  1346. LimitLevel: 4200,
  1347. DisconnectLevel: 3000,
  1348. MaxLevel: 8000,
  1349. },
  1350. })
  1351. expectRateGroups := []struct {
  1352. ID uint16
  1353. Pairs []struct {
  1354. FoodGroup uint16
  1355. SubGroup uint16
  1356. } `oscar:"count_prefix=uint16"`
  1357. }{
  1358. {
  1359. ID: 1,
  1360. Pairs: []struct {
  1361. FoodGroup uint16
  1362. SubGroup uint16
  1363. }{
  1364. {FoodGroup: wire.OService, SubGroup: wire.OServiceErr},
  1365. {FoodGroup: wire.OService, SubGroup: wire.OServiceClientOnline},
  1366. {FoodGroup: wire.OService, SubGroup: wire.OServiceHostOnline},
  1367. {FoodGroup: wire.OService, SubGroup: wire.OServiceServiceRequest},
  1368. {FoodGroup: wire.OService, SubGroup: wire.OServiceServiceResponse},
  1369. {FoodGroup: wire.OService, SubGroup: wire.OServiceRateParamsQuery},
  1370. {FoodGroup: wire.OService, SubGroup: wire.OServiceRateParamsReply},
  1371. {FoodGroup: wire.OService, SubGroup: wire.OServiceRateParamsSubAdd},
  1372. {FoodGroup: wire.OService, SubGroup: wire.OServiceRateDelParamSub},
  1373. {FoodGroup: wire.OService, SubGroup: wire.OServiceRateParamChange},
  1374. {FoodGroup: wire.OService, SubGroup: wire.OServicePauseReq},
  1375. {FoodGroup: wire.OService, SubGroup: wire.OServicePauseAck},
  1376. {FoodGroup: wire.OService, SubGroup: wire.OServiceResume},
  1377. {FoodGroup: wire.OService, SubGroup: wire.OServiceUserInfoQuery},
  1378. {FoodGroup: wire.OService, SubGroup: wire.OServiceUserInfoUpdate},
  1379. {FoodGroup: wire.OService, SubGroup: wire.OServiceEvilNotification},
  1380. {FoodGroup: wire.OService, SubGroup: wire.OServiceIdleNotification},
  1381. {FoodGroup: wire.OService, SubGroup: wire.OServiceMigrateGroups},
  1382. {FoodGroup: wire.OService, SubGroup: wire.OServiceMotd},
  1383. {FoodGroup: wire.OService, SubGroup: wire.OServiceSetPrivacyFlags},
  1384. {FoodGroup: wire.OService, SubGroup: wire.OServiceWellKnownUrls},
  1385. {FoodGroup: wire.OService, SubGroup: wire.OServiceNoop},
  1386. {FoodGroup: wire.OService, SubGroup: wire.OServiceClientVersions},
  1387. {FoodGroup: wire.OService, SubGroup: wire.OServiceHostVersions},
  1388. {FoodGroup: wire.OService, SubGroup: wire.OServiceMaxConfigQuery},
  1389. {FoodGroup: wire.OService, SubGroup: wire.OServiceMaxConfigReply},
  1390. {FoodGroup: wire.OService, SubGroup: wire.OServiceStoreConfig},
  1391. {FoodGroup: wire.OService, SubGroup: wire.OServiceConfigQuery},
  1392. {FoodGroup: wire.OService, SubGroup: wire.OServiceConfigReply},
  1393. {FoodGroup: wire.OService, SubGroup: wire.OServiceSetUserInfoFields},
  1394. {FoodGroup: wire.OService, SubGroup: wire.OServiceProbeReq},
  1395. {FoodGroup: wire.OService, SubGroup: wire.OServiceProbeAck},
  1396. {FoodGroup: wire.OService, SubGroup: wire.OServiceBartReply},
  1397. {FoodGroup: wire.OService, SubGroup: wire.OServiceBartQuery2},
  1398. {FoodGroup: wire.OService, SubGroup: wire.OServiceBartReply2},
  1399. {FoodGroup: wire.Locate, SubGroup: wire.LocateErr},
  1400. {FoodGroup: wire.Locate, SubGroup: wire.LocateRightsQuery},
  1401. {FoodGroup: wire.Locate, SubGroup: wire.LocateRightsReply},
  1402. {FoodGroup: wire.Locate, SubGroup: wire.LocateSetInfo},
  1403. {FoodGroup: wire.Locate, SubGroup: wire.LocateUserInfoReply},
  1404. {FoodGroup: wire.Locate, SubGroup: wire.LocateWatcherSubRequest},
  1405. {FoodGroup: wire.Locate, SubGroup: wire.LocateWatcherNotification},
  1406. {FoodGroup: wire.Locate, SubGroup: wire.LocateSetDirReply},
  1407. {FoodGroup: wire.Locate, SubGroup: wire.LocateGetDirReply},
  1408. {FoodGroup: wire.Locate, SubGroup: wire.LocateGroupCapabilityQuery},
  1409. {FoodGroup: wire.Locate, SubGroup: wire.LocateGroupCapabilityReply},
  1410. {FoodGroup: wire.Locate, SubGroup: wire.LocateSetKeywordInfo},
  1411. {FoodGroup: wire.Locate, SubGroup: wire.LocateSetKeywordReply},
  1412. {FoodGroup: wire.Locate, SubGroup: wire.LocateGetKeywordInfo},
  1413. {FoodGroup: wire.Locate, SubGroup: wire.LocateGetKeywordReply},
  1414. {FoodGroup: wire.Locate, SubGroup: wire.LocateFindListByEmail},
  1415. {FoodGroup: wire.Locate, SubGroup: wire.LocateFindListReply},
  1416. {FoodGroup: wire.Locate, SubGroup: wire.LocateUserInfoQuery2},
  1417. {FoodGroup: wire.Buddy, SubGroup: wire.BuddyErr},
  1418. {FoodGroup: wire.Buddy, SubGroup: wire.BuddyRightsQuery},
  1419. {FoodGroup: wire.Buddy, SubGroup: wire.BuddyRightsReply},
  1420. {FoodGroup: wire.Buddy, SubGroup: wire.BuddyWatcherListQuery},
  1421. {FoodGroup: wire.Buddy, SubGroup: wire.BuddyWatcherListResponse},
  1422. {FoodGroup: wire.Buddy, SubGroup: wire.BuddyWatcherSubRequest},
  1423. {FoodGroup: wire.Buddy, SubGroup: wire.BuddyWatcherNotification},
  1424. {FoodGroup: wire.Buddy, SubGroup: wire.BuddyRejectNotification},
  1425. {FoodGroup: wire.Buddy, SubGroup: wire.BuddyArrived},
  1426. {FoodGroup: wire.Buddy, SubGroup: wire.BuddyDeparted},
  1427. {FoodGroup: wire.Buddy, SubGroup: wire.BuddyAddTempBuddies},
  1428. {FoodGroup: wire.Buddy, SubGroup: wire.BuddyDelTempBuddies},
  1429. {FoodGroup: wire.ICBM, SubGroup: wire.ICBMErr},
  1430. {FoodGroup: wire.ICBM, SubGroup: wire.ICBMAddParameters},
  1431. {FoodGroup: wire.ICBM, SubGroup: wire.ICBMDelParameters},
  1432. {FoodGroup: wire.ICBM, SubGroup: wire.ICBMParameterQuery},
  1433. {FoodGroup: wire.ICBM, SubGroup: wire.ICBMParameterReply},
  1434. {FoodGroup: wire.ICBM, SubGroup: wire.ICBMChannelMsgToClient},
  1435. {FoodGroup: wire.ICBM, SubGroup: wire.ICBMEvilRequest},
  1436. {FoodGroup: wire.ICBM, SubGroup: wire.ICBMEvilReply},
  1437. {FoodGroup: wire.ICBM, SubGroup: wire.ICBMMissedCalls},
  1438. {FoodGroup: wire.ICBM, SubGroup: wire.ICBMClientErr},
  1439. {FoodGroup: wire.ICBM, SubGroup: wire.ICBMHostAck},
  1440. {FoodGroup: wire.ICBM, SubGroup: wire.ICBMSinStored},
  1441. {FoodGroup: wire.ICBM, SubGroup: wire.ICBMSinListQuery},
  1442. {FoodGroup: wire.ICBM, SubGroup: wire.ICBMSinListReply},
  1443. {FoodGroup: wire.ICBM, SubGroup: wire.ICBMOfflineRetrieve},
  1444. {FoodGroup: wire.ICBM, SubGroup: wire.ICBMSinDelete},
  1445. {FoodGroup: wire.ICBM, SubGroup: wire.ICBMNotifyRequest},
  1446. {FoodGroup: wire.ICBM, SubGroup: wire.ICBMNotifyReply},
  1447. {FoodGroup: wire.ICBM, SubGroup: wire.ICBMClientEvent},
  1448. {FoodGroup: wire.Advert, SubGroup: wire.AdvertErr},
  1449. {FoodGroup: wire.Advert, SubGroup: wire.AdvertAdsQuery},
  1450. {FoodGroup: wire.Advert, SubGroup: wire.AdvertAdsReply},
  1451. {FoodGroup: wire.Invite, SubGroup: wire.InviteErr},
  1452. {FoodGroup: wire.Invite, SubGroup: wire.InviteRequestQuery},
  1453. {FoodGroup: wire.Invite, SubGroup: wire.InviteRequestReply},
  1454. {FoodGroup: wire.Admin, SubGroup: wire.AdminErr},
  1455. {FoodGroup: wire.Admin, SubGroup: wire.AdminInfoQuery},
  1456. {FoodGroup: wire.Admin, SubGroup: wire.AdminInfoReply},
  1457. {FoodGroup: wire.Admin, SubGroup: wire.AdminInfoChangeRequest},
  1458. {FoodGroup: wire.Admin, SubGroup: wire.AdminInfoChangeReply},
  1459. {FoodGroup: wire.Admin, SubGroup: wire.AdminAcctConfirmRequest},
  1460. {FoodGroup: wire.Admin, SubGroup: wire.AdminAcctConfirmReply},
  1461. {FoodGroup: wire.Admin, SubGroup: wire.AdminAcctDeleteRequest},
  1462. {FoodGroup: wire.Admin, SubGroup: wire.AdminAcctDeleteReply},
  1463. {FoodGroup: wire.Popup, SubGroup: wire.PopupErr},
  1464. {FoodGroup: wire.Popup, SubGroup: wire.PopupDisplay},
  1465. {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenyErr},
  1466. {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenyRightsQuery},
  1467. {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenyRightsReply},
  1468. {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenySetGroupPermitMask},
  1469. {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenyBosErr},
  1470. {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenyAddTempPermitListEntries},
  1471. {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenyDelTempPermitListEntries},
  1472. {FoodGroup: wire.UserLookup, SubGroup: wire.UserLookupErr},
  1473. {FoodGroup: wire.UserLookup, SubGroup: wire.UserLookupFindByEmail},
  1474. {FoodGroup: wire.UserLookup, SubGroup: wire.UserLookupFindReply},
  1475. {FoodGroup: wire.Stats, SubGroup: wire.StatsErr},
  1476. {FoodGroup: wire.Stats, SubGroup: wire.StatsSetMinReportInterval},
  1477. {FoodGroup: wire.Stats, SubGroup: wire.StatsReportEvents},
  1478. {FoodGroup: wire.Stats, SubGroup: wire.StatsReportAck},
  1479. {FoodGroup: wire.Translate, SubGroup: wire.TranslateErr},
  1480. {FoodGroup: wire.Translate, SubGroup: wire.TranslateRequest},
  1481. {FoodGroup: wire.Translate, SubGroup: wire.TranslateReply},
  1482. {FoodGroup: wire.ChatNav, SubGroup: wire.ChatNavErr},
  1483. {FoodGroup: wire.ChatNav, SubGroup: wire.ChatNavRequestChatRights},
  1484. {FoodGroup: wire.ChatNav, SubGroup: wire.ChatNavRequestExchangeInfo},
  1485. {FoodGroup: wire.ChatNav, SubGroup: wire.ChatNavRequestRoomInfo},
  1486. {FoodGroup: wire.ChatNav, SubGroup: wire.ChatNavRequestMoreRoomInfo},
  1487. {FoodGroup: wire.ChatNav, SubGroup: wire.ChatNavRequestOccupantList},
  1488. {FoodGroup: wire.ChatNav, SubGroup: wire.ChatNavSearchForRoom},
  1489. {FoodGroup: wire.ChatNav, SubGroup: wire.ChatNavCreateRoom},
  1490. {FoodGroup: wire.ChatNav, SubGroup: wire.ChatNavNavInfo},
  1491. {FoodGroup: wire.Chat, SubGroup: wire.ChatErr},
  1492. {FoodGroup: wire.Chat, SubGroup: wire.ChatRoomInfoUpdate},
  1493. {FoodGroup: wire.Chat, SubGroup: wire.ChatUsersJoined},
  1494. {FoodGroup: wire.Chat, SubGroup: wire.ChatUsersLeft},
  1495. {FoodGroup: wire.Chat, SubGroup: wire.ChatChannelMsgToClient},
  1496. {FoodGroup: wire.Chat, SubGroup: wire.ChatEvilRequest},
  1497. {FoodGroup: wire.Chat, SubGroup: wire.ChatEvilReply},
  1498. {FoodGroup: wire.Chat, SubGroup: wire.ChatClientErr},
  1499. {FoodGroup: wire.ODir, SubGroup: wire.ODirErr},
  1500. {FoodGroup: wire.ODir, SubGroup: wire.ODirInfoQuery},
  1501. {FoodGroup: wire.ODir, SubGroup: wire.ODirInfoReply},
  1502. {FoodGroup: wire.ODir, SubGroup: wire.ODirKeywordListQuery},
  1503. {FoodGroup: wire.BART, SubGroup: wire.BARTErr},
  1504. {FoodGroup: wire.BART, SubGroup: wire.BARTUploadQuery},
  1505. {FoodGroup: wire.BART, SubGroup: wire.BARTDownloadQuery},
  1506. {FoodGroup: wire.BART, SubGroup: wire.BARTDownload2Query},
  1507. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagErr},
  1508. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRightsQuery},
  1509. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRightsReply},
  1510. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagQuery},
  1511. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagQueryIfModified},
  1512. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagReply},
  1513. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUse},
  1514. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  1515. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
  1516. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem},
  1517. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertClass},
  1518. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateClass},
  1519. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteClass},
  1520. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagStatus},
  1521. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagReplyNotModified},
  1522. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteUser},
  1523. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagStartCluster},
  1524. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagEndCluster},
  1525. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagAuthorizeBuddy},
  1526. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagPreAuthorizeBuddy},
  1527. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagPreAuthorizedBuddy},
  1528. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRemoveMe},
  1529. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRemoveMe2},
  1530. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRequestAuthorizeToHost},
  1531. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRequestAuthorizeToClient},
  1532. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRespondAuthorizeToHost},
  1533. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRespondAuthorizeToClient},
  1534. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagBuddyAdded},
  1535. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRequestAuthorizeToBadog},
  1536. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRespondAuthorizeToBadog},
  1537. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagBuddyAddedToBadog},
  1538. {FoodGroup: wire.Feedbag, SubGroup: 0x0020},
  1539. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagTestSnac},
  1540. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagForwardMsg},
  1541. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagIsAuthRequiredQuery},
  1542. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagIsAuthRequiredReply},
  1543. {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRecentBuddyUpdate},
  1544. {FoodGroup: wire.Feedbag, SubGroup: 0x0026},
  1545. {FoodGroup: wire.Feedbag, SubGroup: 0x0027},
  1546. {FoodGroup: wire.Feedbag, SubGroup: 0x0028},
  1547. {FoodGroup: wire.ICQ, SubGroup: wire.ICQErr},
  1548. {FoodGroup: wire.ICQ, SubGroup: wire.ICQDBQuery},
  1549. {FoodGroup: wire.ICQ, SubGroup: wire.ICQDBReply},
  1550. {FoodGroup: wire.BUCP, SubGroup: wire.BUCPErr},
  1551. {FoodGroup: wire.BUCP, SubGroup: wire.BUCPLoginRequest},
  1552. {FoodGroup: wire.BUCP, SubGroup: wire.BUCPRegisterRequest},
  1553. {FoodGroup: wire.BUCP, SubGroup: wire.BUCPChallengeRequest},
  1554. {FoodGroup: wire.BUCP, SubGroup: wire.BUCPAsasnRequest},
  1555. {FoodGroup: wire.BUCP, SubGroup: wire.BUCPSecuridRequest},
  1556. {FoodGroup: wire.BUCP, SubGroup: wire.BUCPRegistrationImageRequest},
  1557. {FoodGroup: wire.Alert, SubGroup: wire.AlertErr},
  1558. {FoodGroup: wire.Alert, SubGroup: wire.AlertSetAlertRequest},
  1559. {FoodGroup: wire.Alert, SubGroup: wire.AlertGetSubsRequest},
  1560. {FoodGroup: wire.Alert, SubGroup: wire.AlertNotifyCapabilities},
  1561. {FoodGroup: wire.Alert, SubGroup: wire.AlertNotify},
  1562. {FoodGroup: wire.Alert, SubGroup: wire.AlertGetRuleRequest},
  1563. {FoodGroup: wire.Alert, SubGroup: wire.AlertGetFeedRequest},
  1564. {FoodGroup: wire.Alert, SubGroup: wire.AlertRefreshFeed},
  1565. {FoodGroup: wire.Alert, SubGroup: wire.AlertEvent},
  1566. {FoodGroup: wire.Alert, SubGroup: wire.AlertQogSnac},
  1567. {FoodGroup: wire.Alert, SubGroup: wire.AlertRefreshFeedStock},
  1568. {FoodGroup: wire.Alert, SubGroup: wire.AlertNotifyTransport},
  1569. {FoodGroup: wire.Alert, SubGroup: wire.AlertSetAlertRequestV2},
  1570. {FoodGroup: wire.Alert, SubGroup: wire.AlertNotifyAck},
  1571. {FoodGroup: wire.Alert, SubGroup: wire.AlertNotifyDisplayCapabilities},
  1572. {FoodGroup: wire.Alert, SubGroup: wire.AlertUserOnline},
  1573. },
  1574. },
  1575. {
  1576. ID: 2,
  1577. Pairs: []struct {
  1578. FoodGroup uint16
  1579. SubGroup uint16
  1580. }{
  1581. {FoodGroup: wire.Buddy, SubGroup: wire.BuddyAddBuddies},
  1582. {FoodGroup: wire.Buddy, SubGroup: wire.BuddyDelBuddies},
  1583. {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenyAddPermListEntries},
  1584. {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenyDelPermListEntries},
  1585. {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenyAddDenyListEntries},
  1586. {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenyDelDenyListEntries},
  1587. {FoodGroup: wire.Chat, SubGroup: wire.ChatChannelMsgToHost},
  1588. },
  1589. },
  1590. {
  1591. ID: 3,
  1592. Pairs: []struct {
  1593. FoodGroup uint16
  1594. SubGroup uint16
  1595. }{
  1596. {FoodGroup: wire.Locate, SubGroup: wire.LocateUserInfoQuery},
  1597. {FoodGroup: wire.ICBM, SubGroup: wire.ICBMChannelMsgToHost},
  1598. },
  1599. },
  1600. {
  1601. ID: 4,
  1602. Pairs: []struct {
  1603. FoodGroup uint16
  1604. SubGroup uint16
  1605. }{
  1606. {FoodGroup: wire.Locate, SubGroup: wire.LocateSetDirInfo},
  1607. {FoodGroup: wire.Locate, SubGroup: wire.LocateGetDirInfo},
  1608. },
  1609. },
  1610. {
  1611. ID: 5,
  1612. Pairs: []struct {
  1613. FoodGroup uint16
  1614. SubGroup uint16
  1615. }{},
  1616. },
  1617. }
  1618. cases := []struct {
  1619. // name is the unit test name
  1620. name string
  1621. // instance is the session of the user requesting the chat service
  1622. // info
  1623. instance *state.SessionInstance
  1624. // inputSNAC is the SNAC sent by the sender client
  1625. inputSNAC wire.SNACMessage
  1626. // expectSNACFrame is the SNAC frame sent from the server to the recipient
  1627. // client
  1628. expectOutput wire.SNACMessage
  1629. // expectErr is the expected error returned by the router
  1630. expectErr error
  1631. // timeNow returns the current time
  1632. timeNow func() time.Time
  1633. }{
  1634. {
  1635. name: "get rate limits for AIM > 1.x clients",
  1636. instance: newTestInstance("me", sessOptSetFoodGroupVersion(wire.OService, 3), sessOptSetRateClasses(rateClasses)),
  1637. inputSNAC: wire.SNACMessage{
  1638. Frame: wire.SNACFrame{RequestID: 1234},
  1639. },
  1640. expectOutput: wire.SNACMessage{
  1641. Frame: wire.SNACFrame{
  1642. FoodGroup: wire.OService,
  1643. SubGroup: wire.OServiceRateParamsReply,
  1644. RequestID: 1234,
  1645. },
  1646. Body: wire.SNAC_0x01_0x07_OServiceRateParamsReply{
  1647. RateClasses: []wire.RateParamsSNAC{
  1648. {
  1649. ID: 1,
  1650. WindowSize: 80,
  1651. ClearLevel: 2500,
  1652. AlertLevel: 2000,
  1653. LimitLevel: 1500,
  1654. DisconnectLevel: 800,
  1655. MaxLevel: 6000,
  1656. CurrentLevel: 6000,
  1657. V2Params: &struct {
  1658. LastTime uint32
  1659. DroppingSNACs uint8
  1660. }{
  1661. LastTime: 999,
  1662. DroppingSNACs: 0x00,
  1663. },
  1664. },
  1665. {
  1666. ID: 2,
  1667. WindowSize: 80,
  1668. ClearLevel: 3000,
  1669. AlertLevel: 2000,
  1670. LimitLevel: 1500,
  1671. DisconnectLevel: 1000,
  1672. MaxLevel: 6000,
  1673. CurrentLevel: 6000,
  1674. V2Params: &struct {
  1675. LastTime uint32
  1676. DroppingSNACs uint8
  1677. }{
  1678. LastTime: 999,
  1679. DroppingSNACs: 0x00,
  1680. },
  1681. },
  1682. {
  1683. ID: 3,
  1684. WindowSize: 20,
  1685. ClearLevel: 5100,
  1686. AlertLevel: 5000,
  1687. LimitLevel: 4000,
  1688. DisconnectLevel: 3000,
  1689. MaxLevel: 6000,
  1690. CurrentLevel: 6000,
  1691. V2Params: &struct {
  1692. LastTime uint32
  1693. DroppingSNACs uint8
  1694. }{
  1695. LastTime: 999,
  1696. DroppingSNACs: 0x00,
  1697. },
  1698. },
  1699. {
  1700. ID: 4,
  1701. WindowSize: 20,
  1702. ClearLevel: 5500,
  1703. AlertLevel: 5300,
  1704. LimitLevel: 4200,
  1705. DisconnectLevel: 3000,
  1706. MaxLevel: 8000,
  1707. CurrentLevel: 8000,
  1708. V2Params: &struct {
  1709. LastTime uint32
  1710. DroppingSNACs uint8
  1711. }{
  1712. LastTime: 999,
  1713. DroppingSNACs: 0x00,
  1714. },
  1715. },
  1716. {
  1717. ID: 5,
  1718. WindowSize: 10,
  1719. ClearLevel: 5500,
  1720. AlertLevel: 5300,
  1721. LimitLevel: 4200,
  1722. DisconnectLevel: 3000,
  1723. MaxLevel: 8000,
  1724. CurrentLevel: 8000,
  1725. V2Params: &struct {
  1726. LastTime uint32
  1727. DroppingSNACs uint8
  1728. }{
  1729. LastTime: 999,
  1730. DroppingSNACs: 0x00,
  1731. },
  1732. },
  1733. },
  1734. RateGroups: expectRateGroups,
  1735. },
  1736. },
  1737. timeNow: func() time.Time {
  1738. return time.Unix(1000, 0)
  1739. },
  1740. },
  1741. {
  1742. name: "get rate limits for AIM 1.x client",
  1743. instance: newTestInstance("me", sessClientID("AOL Instant Messenger (TM), version 1."), sessOptSetRateClasses(rateClasses)),
  1744. inputSNAC: wire.SNACMessage{
  1745. Frame: wire.SNACFrame{RequestID: 1234},
  1746. },
  1747. expectOutput: wire.SNACMessage{
  1748. Frame: wire.SNACFrame{
  1749. FoodGroup: wire.OService,
  1750. SubGroup: wire.OServiceRateParamsReply,
  1751. RequestID: 1234,
  1752. },
  1753. Body: wire.SNAC_0x01_0x07_OServiceRateParamsReply{
  1754. RateClasses: []wire.RateParamsSNAC{
  1755. {
  1756. ID: 1,
  1757. WindowSize: 80,
  1758. ClearLevel: 2500,
  1759. AlertLevel: 2000,
  1760. LimitLevel: 1500,
  1761. DisconnectLevel: 800,
  1762. MaxLevel: 6000,
  1763. },
  1764. {
  1765. ID: 2,
  1766. WindowSize: 80,
  1767. ClearLevel: 3000,
  1768. AlertLevel: 2000,
  1769. LimitLevel: 1500,
  1770. DisconnectLevel: 1000,
  1771. MaxLevel: 6000,
  1772. },
  1773. {
  1774. ID: 3,
  1775. WindowSize: 20,
  1776. ClearLevel: 5100,
  1777. AlertLevel: 5000,
  1778. LimitLevel: 4000,
  1779. DisconnectLevel: 3000,
  1780. MaxLevel: 6000,
  1781. },
  1782. {
  1783. ID: 4,
  1784. WindowSize: 20,
  1785. ClearLevel: 5500,
  1786. AlertLevel: 5300,
  1787. LimitLevel: 4200,
  1788. DisconnectLevel: 3000,
  1789. MaxLevel: 8000,
  1790. },
  1791. {
  1792. ID: 5,
  1793. WindowSize: 10,
  1794. ClearLevel: 5500,
  1795. AlertLevel: 5300,
  1796. LimitLevel: 4200,
  1797. DisconnectLevel: 3000,
  1798. MaxLevel: 8000,
  1799. },
  1800. },
  1801. RateGroups: expectRateGroups,
  1802. },
  1803. },
  1804. },
  1805. }
  1806. for _, tc := range cases {
  1807. t.Run(tc.name, func(t *testing.T) {
  1808. svc := OServiceService{
  1809. cfg: config.Config{},
  1810. logger: slog.Default(),
  1811. snacRateLimits: wire.DefaultSNACRateLimits(),
  1812. timeNow: tc.timeNow,
  1813. }
  1814. have := svc.RateParamsQuery(context.Background(), tc.instance, tc.inputSNAC.Frame)
  1815. assert.ElementsMatch(t, tc.expectOutput.Body.(wire.SNAC_0x01_0x07_OServiceRateParamsReply).RateGroups[0].Pairs,
  1816. have.Body.(wire.SNAC_0x01_0x07_OServiceRateParamsReply).RateGroups[0].Pairs)
  1817. assert.ElementsMatch(t, tc.expectOutput.Body.(wire.SNAC_0x01_0x07_OServiceRateParamsReply).RateGroups[1].Pairs,
  1818. have.Body.(wire.SNAC_0x01_0x07_OServiceRateParamsReply).RateGroups[1].Pairs)
  1819. assert.ElementsMatch(t, tc.expectOutput.Body.(wire.SNAC_0x01_0x07_OServiceRateParamsReply).RateGroups[2].Pairs,
  1820. have.Body.(wire.SNAC_0x01_0x07_OServiceRateParamsReply).RateGroups[2].Pairs)
  1821. assert.ElementsMatch(t, tc.expectOutput.Body.(wire.SNAC_0x01_0x07_OServiceRateParamsReply).RateGroups[3].Pairs,
  1822. have.Body.(wire.SNAC_0x01_0x07_OServiceRateParamsReply).RateGroups[3].Pairs)
  1823. assert.ElementsMatch(t, tc.expectOutput.Body.(wire.SNAC_0x01_0x07_OServiceRateParamsReply).RateGroups[4].Pairs,
  1824. have.Body.(wire.SNAC_0x01_0x07_OServiceRateParamsReply).RateGroups[4].Pairs)
  1825. })
  1826. }
  1827. }
  1828. func TestOServiceService_HostOnline(t *testing.T) {
  1829. cases := []struct {
  1830. // name is the unit test name
  1831. name string
  1832. // service is the OSCAR service type
  1833. service uint16
  1834. // expectSNACFrame is the SNAC frame sent from the server to the recipient
  1835. // client
  1836. expectOutput wire.SNACMessage
  1837. }{
  1838. {
  1839. name: "Admin service",
  1840. service: wire.Admin,
  1841. expectOutput: wire.SNACMessage{
  1842. Frame: wire.SNACFrame{
  1843. FoodGroup: wire.OService,
  1844. SubGroup: wire.OServiceHostOnline,
  1845. RequestID: wire.ReqIDFromServer,
  1846. },
  1847. Body: wire.SNAC_0x01_0x03_OServiceHostOnline{
  1848. FoodGroups: []uint16{
  1849. wire.OService,
  1850. wire.Admin,
  1851. },
  1852. },
  1853. },
  1854. },
  1855. {
  1856. name: "Alert service",
  1857. service: wire.Alert,
  1858. expectOutput: wire.SNACMessage{
  1859. Frame: wire.SNACFrame{
  1860. FoodGroup: wire.OService,
  1861. SubGroup: wire.OServiceHostOnline,
  1862. RequestID: wire.ReqIDFromServer,
  1863. },
  1864. Body: wire.SNAC_0x01_0x03_OServiceHostOnline{
  1865. FoodGroups: []uint16{
  1866. wire.Alert,
  1867. wire.OService,
  1868. },
  1869. },
  1870. },
  1871. },
  1872. {
  1873. name: "BART service",
  1874. service: wire.BART,
  1875. expectOutput: wire.SNACMessage{
  1876. Frame: wire.SNACFrame{
  1877. FoodGroup: wire.OService,
  1878. SubGroup: wire.OServiceHostOnline,
  1879. RequestID: wire.ReqIDFromServer,
  1880. },
  1881. Body: wire.SNAC_0x01_0x03_OServiceHostOnline{
  1882. FoodGroups: []uint16{
  1883. wire.BART,
  1884. wire.OService,
  1885. },
  1886. },
  1887. },
  1888. },
  1889. {
  1890. name: "BOS service",
  1891. service: wire.BOS,
  1892. expectOutput: wire.SNACMessage{
  1893. Frame: wire.SNACFrame{
  1894. FoodGroup: wire.OService,
  1895. SubGroup: wire.OServiceHostOnline,
  1896. RequestID: wire.ReqIDFromServer,
  1897. },
  1898. Body: wire.SNAC_0x01_0x03_OServiceHostOnline{
  1899. FoodGroups: []uint16{
  1900. wire.Alert,
  1901. wire.BART,
  1902. wire.Buddy,
  1903. wire.Feedbag,
  1904. wire.ICBM,
  1905. wire.ICQ,
  1906. wire.Locate,
  1907. wire.OService,
  1908. wire.PermitDeny,
  1909. wire.UserLookup,
  1910. wire.Invite,
  1911. wire.Popup,
  1912. wire.Stats,
  1913. },
  1914. },
  1915. },
  1916. },
  1917. {
  1918. name: "Chat service",
  1919. service: wire.Chat,
  1920. expectOutput: wire.SNACMessage{
  1921. Frame: wire.SNACFrame{
  1922. FoodGroup: wire.OService,
  1923. SubGroup: wire.OServiceHostOnline,
  1924. RequestID: wire.ReqIDFromServer,
  1925. },
  1926. Body: wire.SNAC_0x01_0x03_OServiceHostOnline{
  1927. FoodGroups: []uint16{
  1928. wire.OService,
  1929. wire.Chat,
  1930. },
  1931. },
  1932. },
  1933. },
  1934. {
  1935. name: "ChatNav service",
  1936. service: wire.ChatNav,
  1937. expectOutput: wire.SNACMessage{
  1938. Frame: wire.SNACFrame{
  1939. FoodGroup: wire.OService,
  1940. SubGroup: wire.OServiceHostOnline,
  1941. RequestID: wire.ReqIDFromServer,
  1942. },
  1943. Body: wire.SNAC_0x01_0x03_OServiceHostOnline{
  1944. FoodGroups: []uint16{
  1945. wire.ChatNav,
  1946. wire.OService,
  1947. },
  1948. },
  1949. },
  1950. },
  1951. {
  1952. name: "ODir service",
  1953. service: wire.ODir,
  1954. expectOutput: wire.SNACMessage{
  1955. Frame: wire.SNACFrame{
  1956. FoodGroup: wire.OService,
  1957. SubGroup: wire.OServiceHostOnline,
  1958. RequestID: wire.ReqIDFromServer,
  1959. },
  1960. Body: wire.SNAC_0x01_0x03_OServiceHostOnline{
  1961. FoodGroups: []uint16{
  1962. wire.ODir,
  1963. wire.OService,
  1964. },
  1965. },
  1966. },
  1967. },
  1968. {
  1969. name: "Oops, unsupported service",
  1970. service: wire.Kerberos,
  1971. expectOutput: wire.SNACMessage{
  1972. Frame: wire.SNACFrame{
  1973. FoodGroup: wire.OService,
  1974. SubGroup: wire.OServiceErr,
  1975. },
  1976. },
  1977. },
  1978. }
  1979. for _, tc := range cases {
  1980. t.Run(tc.name, func(t *testing.T) {
  1981. svc := NewOServiceService(config.Config{}, nil, slog.Default(), nil, nil, nil, nil, nil, wire.DefaultSNACRateLimits(), nil, nil, nil, nil)
  1982. have := svc.HostOnline(tc.service)
  1983. assert.Equal(t, tc.expectOutput, have)
  1984. })
  1985. }
  1986. }
  1987. func TestOServiceService_ClientVersions(t *testing.T) {
  1988. svc := OServiceService{
  1989. cfg: config.Config{},
  1990. logger: slog.Default(),
  1991. }
  1992. want := []wire.SNACMessage{
  1993. {
  1994. Frame: wire.SNACFrame{
  1995. FoodGroup: wire.OService,
  1996. SubGroup: wire.OServiceHostVersions,
  1997. RequestID: 1234,
  1998. },
  1999. Body: wire.SNAC_0x01_0x18_OServiceHostVersions{
  2000. Versions: []uint16{5, 6, 7, 8},
  2001. },
  2002. },
  2003. {
  2004. Frame: wire.SNACFrame{
  2005. FoodGroup: wire.OService,
  2006. SubGroup: wire.OServiceMotd,
  2007. RequestID: wire.ReqIDFromServer,
  2008. },
  2009. Body: wire.SNAC_0x01_0x13_OServiceMOTD{
  2010. MessageType: 0x0004,
  2011. TLVRestBlock: wire.TLVRestBlock{
  2012. TLVList: wire.TLVList{
  2013. wire.NewTLVBE(wire.OServiceTLVTagsMOTDMessage, "Welcome to Open OSCAR Server"),
  2014. },
  2015. },
  2016. },
  2017. },
  2018. }
  2019. instance := newTestInstance("me")
  2020. have := svc.ClientVersions(context.Background(), instance, wire.SNACFrame{
  2021. RequestID: 1234,
  2022. }, wire.SNAC_0x01_0x17_OServiceClientVersions{
  2023. Versions: []uint16{5, 6, 7, 8},
  2024. })
  2025. assert.Equal(t, want, have)
  2026. }
  2027. func TestNewOServiceUserInfoUpdate(t *testing.T) {
  2028. memberSince := time.Unix(1_700_000_000, 0)
  2029. profileUpdated := time.Unix(1_700_100_000, 0)
  2030. signonTime := time.Now().Add(-3 * time.Second)
  2031. t.Run("OService version < 4 without profile", func(t *testing.T) {
  2032. session := newTestInstance("me",
  2033. sessOptMemberSince(memberSince),
  2034. sessOptSignonTime(signonTime))
  2035. signon := session.SignonTime()
  2036. onlineLowerBound := uint32(time.Since(signon).Seconds())
  2037. got := newOServiceUserInfoUpdate(session)
  2038. require.Len(t, got.UserInfo, 1)
  2039. memberVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoMemberSince)
  2040. require.True(t, ok)
  2041. require.Equal(t, uint32(memberSince.Unix()), memberVal)
  2042. signonVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoSignonTOD)
  2043. require.True(t, ok)
  2044. require.Equal(t, uint32(signon.Unix()), signonVal)
  2045. onlineVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoOnlineTime)
  2046. require.True(t, ok)
  2047. require.GreaterOrEqual(t, onlineVal, onlineLowerBound)
  2048. require.LessOrEqual(t, onlineVal-onlineLowerBound, uint32(2))
  2049. hasSigTime := got.UserInfo[0].HasTag(wire.OServiceUserInfoSigTime)
  2050. require.False(t, hasSigTime)
  2051. require.False(t, got.UserInfo[0].HasTag(wire.OServiceUserInfoPrimaryInstance))
  2052. })
  2053. t.Run("includes profile update time when set", func(t *testing.T) {
  2054. session := newTestInstance("me",
  2055. sessOptMemberSince(memberSince),
  2056. sessOptSignonTime(signonTime),
  2057. sessOptProfile(state.UserProfile{UpdateTime: profileUpdated}),
  2058. sessOptSetFoodGroupVersion(wire.OService, 4))
  2059. signon := session.SignonTime()
  2060. onlineLowerBound := uint32(time.Since(signon).Seconds())
  2061. got := newOServiceUserInfoUpdate(session)
  2062. require.Len(t, got.UserInfo, 2)
  2063. memberVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoMemberSince)
  2064. require.True(t, ok)
  2065. require.Equal(t, uint32(memberSince.Unix()), memberVal)
  2066. signonVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoSignonTOD)
  2067. require.True(t, ok)
  2068. require.Equal(t, uint32(signon.Unix()), signonVal)
  2069. onlineVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoOnlineTime)
  2070. require.True(t, ok)
  2071. require.GreaterOrEqual(t, onlineVal, onlineLowerBound)
  2072. require.LessOrEqual(t, onlineVal-onlineLowerBound, uint32(2))
  2073. hasSigTime := got.UserInfo[0].HasTag(wire.OServiceUserInfoSigTime)
  2074. require.False(t, hasSigTime)
  2075. // Signature time is in the instance block, not UserInfo[0]
  2076. hasSigTimeInstance := got.UserInfo[1].HasTag(wire.OServiceUserInfoSigTime)
  2077. require.True(t, hasSigTimeInstance)
  2078. sigVal, ok := got.UserInfo[1].Uint32BE(wire.OServiceUserInfoSigTime)
  2079. require.True(t, ok)
  2080. require.Equal(t, uint32(profileUpdated.Unix()), sigVal)
  2081. require.False(t, got.UserInfo[0].HasTag(wire.OServiceUserInfoPrimaryInstance))
  2082. })
  2083. t.Run("appends additional instance info when food group version >= 4", func(t *testing.T) {
  2084. session := newTestInstance("me",
  2085. sessOptMemberSince(memberSince),
  2086. sessOptSignonTime(signonTime),
  2087. sessOptSetFoodGroupVersion(wire.OService, 4))
  2088. signon := session.SignonTime()
  2089. onlineLowerBound := uint32(time.Since(signon).Seconds())
  2090. got := newOServiceUserInfoUpdate(session)
  2091. require.Len(t, got.UserInfo, 2)
  2092. memberVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoMemberSince)
  2093. require.True(t, ok)
  2094. require.Equal(t, uint32(memberSince.Unix()), memberVal)
  2095. signonVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoSignonTOD)
  2096. require.True(t, ok)
  2097. require.Equal(t, uint32(signon.Unix()), signonVal)
  2098. onlineVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoOnlineTime)
  2099. require.True(t, ok)
  2100. require.GreaterOrEqual(t, onlineVal, onlineLowerBound)
  2101. require.LessOrEqual(t, onlineVal-onlineLowerBound, uint32(2))
  2102. hasSigTime := got.UserInfo[0].HasTag(wire.OServiceUserInfoSigTime)
  2103. require.False(t, hasSigTime)
  2104. instanceBytes, ok := got.UserInfo[0].Bytes(wire.OServiceUserInfoMyInstanceNum)
  2105. require.True(t, ok)
  2106. require.Equal(t, []byte{0x01}, instanceBytes)
  2107. primaryBytes, ok := got.UserInfo[1].Bytes(wire.OServiceUserInfoPrimaryInstance)
  2108. require.True(t, ok)
  2109. require.Equal(t, []byte{0x01}, primaryBytes)
  2110. require.Equal(t, got.UserInfo[0].ScreenName, got.UserInfo[1].ScreenName)
  2111. })
  2112. t.Run("includes info for all instances when session has 2 instances", func(t *testing.T) {
  2113. session := newTestInstance("me",
  2114. sessOptMemberSince(memberSince),
  2115. sessOptSignonTime(signonTime),
  2116. sessOptSetFoodGroupVersion(wire.OService, 4))
  2117. // Add a second instance to the session
  2118. session.Session().AddInstance()
  2119. signon := session.SignonTime()
  2120. onlineLowerBound := uint32(time.Since(signon).Seconds())
  2121. got := newOServiceUserInfoUpdate(session)
  2122. require.Len(t, got.UserInfo, 3)
  2123. memberVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoMemberSince)
  2124. require.True(t, ok)
  2125. require.Equal(t, uint32(memberSince.Unix()), memberVal)
  2126. signonVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoSignonTOD)
  2127. require.True(t, ok)
  2128. require.Equal(t, uint32(signon.Unix()), signonVal)
  2129. onlineVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoOnlineTime)
  2130. require.True(t, ok)
  2131. require.GreaterOrEqual(t, onlineVal, onlineLowerBound)
  2132. require.LessOrEqual(t, onlineVal-onlineLowerBound, uint32(2))
  2133. hasSigTime := got.UserInfo[0].HasTag(wire.OServiceUserInfoSigTime)
  2134. require.False(t, hasSigTime)
  2135. instanceBytes, ok := got.UserInfo[0].Bytes(wire.OServiceUserInfoMyInstanceNum)
  2136. require.True(t, ok)
  2137. require.Equal(t, []byte{0x01}, instanceBytes)
  2138. // First instance block
  2139. primary1Bytes, ok := got.UserInfo[1].Bytes(wire.OServiceUserInfoPrimaryInstance)
  2140. require.True(t, ok)
  2141. require.Equal(t, []byte{0x01}, primary1Bytes)
  2142. require.Equal(t, got.UserInfo[0].ScreenName, got.UserInfo[1].ScreenName)
  2143. // Second instance block
  2144. primary2Bytes, ok := got.UserInfo[2].Bytes(wire.OServiceUserInfoPrimaryInstance)
  2145. require.True(t, ok)
  2146. require.Equal(t, []byte{0x02}, primary2Bytes)
  2147. require.Equal(t, got.UserInfo[0].ScreenName, got.UserInfo[2].ScreenName)
  2148. })
  2149. t.Run("marks instance user flags unavailable when session is away", func(t *testing.T) {
  2150. session := newTestInstance("me",
  2151. sessOptUserInfoFlag(wire.OServiceUserFlagUnavailable),
  2152. sessOptSetFoodGroupVersion(wire.OService, 4))
  2153. got := newOServiceUserInfoUpdate(session)
  2154. require.Len(t, got.UserInfo, 2)
  2155. flags, ok := got.UserInfo[1].Uint16BE(wire.OServiceUserInfoUserFlags)
  2156. require.True(t, ok)
  2157. require.Equal(t, wire.OServiceUserFlagOSCARFree|wire.OServiceUserFlagUnavailable, flags)
  2158. })
  2159. t.Run("marks instance status invisible when current instance is invisible", func(t *testing.T) {
  2160. session := newTestInstance("me",
  2161. sessOptInvisible,
  2162. sessOptSetFoodGroupVersion(wire.OService, 4))
  2163. got := newOServiceUserInfoUpdate(session)
  2164. require.Len(t, got.UserInfo, 2)
  2165. status, ok := got.UserInfo[1].Uint32BE(wire.OServiceUserInfoStatus)
  2166. require.True(t, ok)
  2167. require.Equal(t, wire.OServiceUserStatusInvisible, status)
  2168. })
  2169. t.Run("adds buddy icon and profile sig time only for current instance", func(t *testing.T) {
  2170. icon := wire.BARTID{
  2171. Type: 1,
  2172. BARTInfo: wire.BARTInfo{
  2173. Flags: 1,
  2174. Hash: []byte{0xAA, 0xBB, 0xCC},
  2175. },
  2176. }
  2177. session := newTestInstance("me",
  2178. sessOptSetFoodGroupVersion(wire.OService, 4),
  2179. sessOptBuddyIcon(icon),
  2180. sessOptProfile(state.UserProfile{UpdateTime: profileUpdated}))
  2181. session.Session().AddInstance()
  2182. got := newOServiceUserInfoUpdate(session)
  2183. require.Len(t, got.UserInfo, 3)
  2184. require.True(t, got.UserInfo[1].HasTag(wire.OServiceUserInfoBARTInfo))
  2185. require.True(t, got.UserInfo[1].HasTag(wire.OServiceUserInfoSigTime))
  2186. require.False(t, got.UserInfo[2].HasTag(wire.OServiceUserInfoBARTInfo))
  2187. require.False(t, got.UserInfo[2].HasTag(wire.OServiceUserInfoSigTime))
  2188. })
  2189. t.Run("does not add buddy icon when icon type is zero", func(t *testing.T) {
  2190. session := newTestInstance("me",
  2191. sessOptSetFoodGroupVersion(wire.OService, 4),
  2192. sessOptBuddyIcon(wire.BARTID{
  2193. Type: 0,
  2194. BARTInfo: wire.BARTInfo{
  2195. Flags: 1,
  2196. Hash: []byte{0x10, 0x20, 0x30},
  2197. },
  2198. }))
  2199. got := newOServiceUserInfoUpdate(session)
  2200. require.Len(t, got.UserInfo, 2)
  2201. require.False(t, got.UserInfo[1].HasTag(wire.OServiceUserInfoBARTInfo))
  2202. })
  2203. }
  2204. func TestOServiceService_UserInfoQuery(t *testing.T) {
  2205. tests := []struct {
  2206. name string
  2207. instance *state.SessionInstance
  2208. given wire.SNACMessage
  2209. want wire.SNACMessage
  2210. wantErr error
  2211. }{
  2212. {
  2213. name: "happy path windows aim < 6",
  2214. instance: newTestInstance("me"),
  2215. given: wire.SNACMessage{
  2216. Frame: wire.SNACFrame{RequestID: 1234},
  2217. },
  2218. want: wire.SNACMessage{
  2219. Frame: wire.SNACFrame{
  2220. FoodGroup: wire.OService,
  2221. SubGroup: wire.OServiceUserInfoUpdate,
  2222. RequestID: 1234,
  2223. },
  2224. Body: newOServiceUserInfoUpdate(newTestInstance("me")),
  2225. },
  2226. },
  2227. {
  2228. name: "happy path windows aim >= 6",
  2229. instance: newTestInstance("me", sessOptSetFoodGroupVersion(wire.OService, 4)),
  2230. given: wire.SNACMessage{
  2231. Frame: wire.SNACFrame{RequestID: 1234},
  2232. },
  2233. want: wire.SNACMessage{
  2234. Frame: wire.SNACFrame{
  2235. FoodGroup: wire.OService,
  2236. SubGroup: wire.OServiceUserInfoUpdate,
  2237. RequestID: 1234,
  2238. },
  2239. Body: newOServiceUserInfoUpdate(newTestInstance("me", sessOptSetFoodGroupVersion(wire.OService, 4))),
  2240. },
  2241. },
  2242. }
  2243. for _, tt := range tests {
  2244. t.Run(tt.name, func(t *testing.T) {
  2245. svc := OServiceService{
  2246. cfg: config.Config{},
  2247. logger: slog.Default(),
  2248. }
  2249. have := svc.UserInfoQuery(context.Background(), tt.instance, tt.given.Frame)
  2250. assert.Equal(t, tt.want, have)
  2251. })
  2252. }
  2253. }
  2254. func TestOServiceService_IdleNotification(t *testing.T) {
  2255. tests := []struct {
  2256. name string
  2257. instance *state.SessionInstance
  2258. bodyIn wire.SNAC_0x01_0x11_OServiceIdleNotification
  2259. // mockParams is the list of params sent to mocks that satisfy this
  2260. // method's dependencies
  2261. mockParams mockParams
  2262. wantErr error
  2263. }{
  2264. {
  2265. name: "set idle from active",
  2266. instance: newTestInstance("me"),
  2267. bodyIn: wire.SNAC_0x01_0x11_OServiceIdleNotification{
  2268. IdleTime: 90,
  2269. },
  2270. mockParams: mockParams{
  2271. buddyBroadcasterParams: buddyBroadcasterParams{
  2272. broadcastBuddyArrivedParams: broadcastBuddyArrivedParams{
  2273. {
  2274. screenName: state.DisplayScreenName("me"),
  2275. },
  2276. },
  2277. },
  2278. },
  2279. },
  2280. {
  2281. name: "set active from idle",
  2282. instance: newTestInstance("me", sessOptIdle(90*time.Second)),
  2283. bodyIn: wire.SNAC_0x01_0x11_OServiceIdleNotification{
  2284. IdleTime: 0,
  2285. },
  2286. mockParams: mockParams{
  2287. buddyBroadcasterParams: buddyBroadcasterParams{
  2288. broadcastBuddyArrivedParams: broadcastBuddyArrivedParams{
  2289. {
  2290. screenName: state.DisplayScreenName("me"),
  2291. },
  2292. },
  2293. },
  2294. },
  2295. },
  2296. }
  2297. for _, tt := range tests {
  2298. t.Run(tt.name, func(t *testing.T) {
  2299. buddyUpdateBroadcaster := newMockbuddyBroadcaster(t)
  2300. for _, params := range tt.mockParams.broadcastBuddyArrivedParams {
  2301. buddyUpdateBroadcaster.EXPECT().
  2302. BroadcastBuddyArrived(mock.Anything, state.NewIdentScreenName(params.screenName.String()), mock.MatchedBy(func(userInfo wire.TLVUserInfo) bool {
  2303. return userInfo.ScreenName == params.screenName.String()
  2304. })).
  2305. Return(params.err)
  2306. }
  2307. svc := OServiceService{
  2308. cfg: config.Config{},
  2309. logger: slog.Default(),
  2310. buddyBroadcaster: buddyUpdateBroadcaster,
  2311. }
  2312. haveErr := svc.IdleNotification(context.TODO(), tt.instance, tt.bodyIn)
  2313. assert.ErrorIs(t, tt.wantErr, haveErr)
  2314. })
  2315. }
  2316. }
  2317. func TestOServiceService_ClientOnline(t *testing.T) {
  2318. chatRoom := state.NewChatRoom("the-chat-room", state.NewIdentScreenName("creator"), state.PrivateExchange)
  2319. chatter1 := newTestInstance("chatter-1", sessOptChatRoomCookie(chatRoom.Cookie()))
  2320. chatter2 := newTestInstance("chatter-2", sessOptChatRoomCookie(chatRoom.Cookie()))
  2321. tests := []struct {
  2322. // name is the name of the test
  2323. name string
  2324. // joiningChatter is the session of the arriving user
  2325. instance *state.SessionInstance
  2326. // bodyIn is the SNAC body sent from the arriving user's client to the
  2327. // server
  2328. bodyIn wire.SNAC_0x01_0x02_OServiceClientOnline
  2329. // service is the OSCAR service type
  2330. service uint16
  2331. // wantErr is the expected error from the handler
  2332. wantErr error
  2333. // mockParams is the list of params sent to mocks that satisfy this
  2334. // method's dependencies
  2335. mockParams mockParams
  2336. // validateSess verifies the session state after the handler has run
  2337. validateSess func(t *testing.T, instance *state.SessionInstance)
  2338. // cfg is the config to use for this test. Zero value uses default config.Config{}
  2339. cfg config.Config
  2340. }{
  2341. {
  2342. name: "notify that BOS user is online",
  2343. instance: newTestInstance("me", sessOptCannedSignonTime, sessOptContactsInit),
  2344. bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
  2345. service: wire.BOS,
  2346. mockParams: mockParams{
  2347. buddyBroadcasterParams: buddyBroadcasterParams{
  2348. broadcastVisibilityParams: broadcastVisibilityParams{
  2349. {
  2350. from: state.NewIdentScreenName("me"),
  2351. filter: nil,
  2352. doSendDepartures: false,
  2353. },
  2354. },
  2355. },
  2356. messageRelayerParams: messageRelayerParams{
  2357. relayToScreenNameParams: relayToScreenNameParams{
  2358. {
  2359. screenName: state.NewIdentScreenName("me"),
  2360. message: wire.SNACMessage{
  2361. Frame: wire.SNACFrame{
  2362. FoodGroup: wire.Stats,
  2363. SubGroup: wire.StatsSetMinReportInterval,
  2364. RequestID: wire.ReqIDFromServer,
  2365. },
  2366. Body: wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{
  2367. MinReportInterval: 1,
  2368. },
  2369. },
  2370. },
  2371. },
  2372. },
  2373. },
  2374. validateSess: func(t *testing.T, instance *state.SessionInstance) {
  2375. assert.True(t, instance.SignonComplete())
  2376. },
  2377. },
  2378. {
  2379. name: "ICQ Lite order: ClientOnline before feedbag use",
  2380. instance: newTestInstance("me", sessOptCannedSignonTime),
  2381. bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
  2382. service: wire.BOS,
  2383. mockParams: mockParams{
  2384. messageRelayerParams: messageRelayerParams{
  2385. relayToScreenNameParams: relayToScreenNameParams{
  2386. {
  2387. screenName: state.NewIdentScreenName("me"),
  2388. message: wire.SNACMessage{
  2389. Frame: wire.SNACFrame{
  2390. FoodGroup: wire.Stats,
  2391. SubGroup: wire.StatsSetMinReportInterval,
  2392. RequestID: wire.ReqIDFromServer,
  2393. },
  2394. Body: wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{
  2395. MinReportInterval: 1,
  2396. },
  2397. },
  2398. },
  2399. },
  2400. },
  2401. },
  2402. validateSess: func(t *testing.T, instance *state.SessionInstance) {
  2403. assert.True(t, instance.SignonComplete())
  2404. assert.False(t, instance.ContactsInit())
  2405. },
  2406. },
  2407. {
  2408. name: "notify that BOS user is online via Kerberos auth, does not have stored profile",
  2409. instance: newTestInstance("me", sessOptCannedSignonTime, sessOptKerberosAuth, sessOptContactsInit),
  2410. bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
  2411. service: wire.BOS,
  2412. mockParams: mockParams{
  2413. buddyBroadcasterParams: buddyBroadcasterParams{
  2414. broadcastVisibilityParams: broadcastVisibilityParams{
  2415. {
  2416. from: state.NewIdentScreenName("me"),
  2417. filter: nil,
  2418. doSendDepartures: false,
  2419. },
  2420. },
  2421. },
  2422. messageRelayerParams: messageRelayerParams{
  2423. relayToScreenNameParams: relayToScreenNameParams{
  2424. {
  2425. screenName: state.NewIdentScreenName("me"),
  2426. message: wire.SNACMessage{
  2427. Frame: wire.SNACFrame{
  2428. FoodGroup: wire.Stats,
  2429. SubGroup: wire.StatsSetMinReportInterval,
  2430. RequestID: wire.ReqIDFromServer,
  2431. },
  2432. Body: wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{
  2433. MinReportInterval: 1,
  2434. },
  2435. },
  2436. },
  2437. },
  2438. },
  2439. profileManagerParams: profileManagerParams{
  2440. retrieveProfileParams: retrieveProfileParams{
  2441. {
  2442. screenName: state.NewIdentScreenName("me"),
  2443. result: state.UserProfile{},
  2444. },
  2445. },
  2446. },
  2447. },
  2448. validateSess: func(t *testing.T, instance *state.SessionInstance) {
  2449. assert.True(t, instance.SignonComplete())
  2450. assert.True(t, instance.Session().Profile().IsZero())
  2451. },
  2452. },
  2453. {
  2454. name: "notify that BOS user is online via Kerberos auth, has stored profile",
  2455. instance: newTestInstance("me", sessOptCannedSignonTime, sessOptKerberosAuth, sessOptContactsInit),
  2456. bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
  2457. service: wire.BOS,
  2458. mockParams: mockParams{
  2459. buddyBroadcasterParams: buddyBroadcasterParams{
  2460. broadcastVisibilityParams: broadcastVisibilityParams{
  2461. {
  2462. from: state.NewIdentScreenName("me"),
  2463. filter: nil,
  2464. doSendDepartures: false,
  2465. },
  2466. },
  2467. },
  2468. messageRelayerParams: messageRelayerParams{
  2469. relayToScreenNameParams: relayToScreenNameParams{
  2470. {
  2471. screenName: state.NewIdentScreenName("me"),
  2472. message: wire.SNACMessage{
  2473. Frame: wire.SNACFrame{
  2474. FoodGroup: wire.Stats,
  2475. SubGroup: wire.StatsSetMinReportInterval,
  2476. RequestID: wire.ReqIDFromServer,
  2477. },
  2478. Body: wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{
  2479. MinReportInterval: 1,
  2480. },
  2481. },
  2482. },
  2483. },
  2484. relayToSelfParams: relayToSelfParams{
  2485. {
  2486. screenName: state.NewIdentScreenName("me"),
  2487. message: wire.SNACMessage{
  2488. Frame: wire.SNACFrame{
  2489. FoodGroup: wire.OService,
  2490. SubGroup: wire.OServiceUserInfoUpdate,
  2491. },
  2492. Body: newOServiceUserInfoUpdate(newTestInstance("me", sessOptCannedSignonTime, sessOptProfile(
  2493. state.UserProfile{
  2494. ProfileText: "profile-result",
  2495. MIMEType: `text/aolrtf; charset="us-ascii"`,
  2496. UpdateTime: time.Unix(100000, 0),
  2497. },
  2498. ))),
  2499. },
  2500. },
  2501. },
  2502. },
  2503. profileManagerParams: profileManagerParams{
  2504. retrieveProfileParams: retrieveProfileParams{
  2505. {
  2506. screenName: state.NewIdentScreenName("me"),
  2507. result: state.UserProfile{
  2508. ProfileText: "profile-result",
  2509. MIMEType: `text/aolrtf; charset="us-ascii"`,
  2510. UpdateTime: time.Unix(100000, 0),
  2511. },
  2512. },
  2513. },
  2514. },
  2515. },
  2516. validateSess: func(t *testing.T, instance *state.SessionInstance) {
  2517. assert.True(t, instance.SignonComplete())
  2518. assert.False(t, instance.Session().Profile().IsZero(), "profile update time is non-zero")
  2519. assert.Equal(t, "profile-result", instance.Session().Profile().ProfileText, "profile text matches")
  2520. assert.Equal(t, `text/aolrtf; charset="us-ascii"`, instance.Session().Profile().MIMEType, "profile mimetype matches")
  2521. },
  2522. },
  2523. {
  2524. name: "notify that BOS user is online with 0 offline messages, no notification sent",
  2525. instance: newTestInstance("me", sessOptCannedSignonTime, sessOptOfflineMsgCount(0), sessOptContactsInit),
  2526. bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
  2527. service: wire.BOS,
  2528. mockParams: mockParams{
  2529. buddyBroadcasterParams: buddyBroadcasterParams{
  2530. broadcastVisibilityParams: broadcastVisibilityParams{
  2531. {
  2532. from: state.NewIdentScreenName("me"),
  2533. filter: nil,
  2534. doSendDepartures: false,
  2535. },
  2536. },
  2537. },
  2538. messageRelayerParams: messageRelayerParams{
  2539. relayToScreenNameParams: relayToScreenNameParams{
  2540. {
  2541. screenName: state.NewIdentScreenName("me"),
  2542. message: wire.SNACMessage{
  2543. Frame: wire.SNACFrame{
  2544. FoodGroup: wire.Stats,
  2545. SubGroup: wire.StatsSetMinReportInterval,
  2546. RequestID: wire.ReqIDFromServer,
  2547. },
  2548. Body: wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{
  2549. MinReportInterval: 1,
  2550. },
  2551. },
  2552. },
  2553. },
  2554. },
  2555. },
  2556. validateSess: func(t *testing.T, instance *state.SessionInstance) {
  2557. assert.True(t, instance.SignonComplete())
  2558. assert.Equal(t, 0, instance.OfflineMsgCount())
  2559. },
  2560. },
  2561. {
  2562. name: "notify that BOS user is online with offline messages, send notification and reset count",
  2563. instance: newTestInstance("me", sessOptCannedSignonTime, sessOptOfflineMsgCount(3), sessOptContactsInit),
  2564. bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
  2565. service: wire.BOS,
  2566. mockParams: mockParams{
  2567. buddyBroadcasterParams: buddyBroadcasterParams{
  2568. broadcastVisibilityParams: broadcastVisibilityParams{
  2569. {
  2570. from: state.NewIdentScreenName("me"),
  2571. filter: nil,
  2572. doSendDepartures: false,
  2573. },
  2574. },
  2575. },
  2576. messageRelayerParams: messageRelayerParams{
  2577. relayToScreenNameParams: relayToScreenNameParams{
  2578. {
  2579. screenName: state.NewIdentScreenName("me"),
  2580. message: wire.SNACMessage{
  2581. Frame: wire.SNACFrame{
  2582. FoodGroup: wire.Stats,
  2583. SubGroup: wire.StatsSetMinReportInterval,
  2584. RequestID: wire.ReqIDFromServer,
  2585. },
  2586. Body: wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{
  2587. MinReportInterval: 1,
  2588. },
  2589. },
  2590. },
  2591. {
  2592. screenName: state.NewIdentScreenName("me"),
  2593. message: func() wire.SNACMessage {
  2594. msg, err := systemMessage("You just received 3 IM(s) while you were offline. If you " +
  2595. "do not wish to receive offline messages, please go to " +
  2596. "<a href=\"https://www.youtube.com/watch?v=dQw4w9WgXcQ&list=RDdQw4w9WgXcQ&start_radio=1&pp=ygUJcmljayByb2xsoAcB\">IM Settings</a>.")
  2597. require.NoError(t, err)
  2598. return msg
  2599. }(),
  2600. },
  2601. },
  2602. },
  2603. offlineMessageManagerParams: offlineMessageManagerParams{
  2604. setOfflineMsgCountParams: setOfflineMsgCountParams{
  2605. {
  2606. screenName: state.NewIdentScreenName("me"),
  2607. count: 0,
  2608. err: nil,
  2609. },
  2610. },
  2611. },
  2612. },
  2613. validateSess: func(t *testing.T, instance *state.SessionInstance) {
  2614. assert.True(t, instance.SignonComplete())
  2615. assert.Equal(t, 0, instance.OfflineMsgCount())
  2616. },
  2617. },
  2618. {
  2619. name: "ICQ user with offline messages, no offline message notification sent",
  2620. instance: newTestInstance("100001", sessOptCannedSignonTime, sessOptOfflineMsgCount(3), sessOptContactsInit, sessOptUIN(100001)),
  2621. bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
  2622. service: wire.BOS,
  2623. mockParams: mockParams{
  2624. buddyBroadcasterParams: buddyBroadcasterParams{
  2625. broadcastVisibilityParams: broadcastVisibilityParams{
  2626. {
  2627. from: state.NewIdentScreenName("100001"),
  2628. filter: nil,
  2629. doSendDepartures: false,
  2630. },
  2631. },
  2632. },
  2633. messageRelayerParams: messageRelayerParams{
  2634. relayToScreenNameParams: relayToScreenNameParams{
  2635. {
  2636. screenName: state.NewIdentScreenName("100001"),
  2637. message: wire.SNACMessage{
  2638. Frame: wire.SNACFrame{
  2639. FoodGroup: wire.Stats,
  2640. SubGroup: wire.StatsSetMinReportInterval,
  2641. RequestID: wire.ReqIDFromServer,
  2642. },
  2643. Body: wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{
  2644. MinReportInterval: 1,
  2645. },
  2646. },
  2647. },
  2648. },
  2649. },
  2650. },
  2651. validateSess: func(t *testing.T, instance *state.SessionInstance) {
  2652. assert.True(t, instance.SignonComplete())
  2653. assert.Equal(t, uint32(100001), instance.UIN())
  2654. assert.Equal(t, 3, instance.OfflineMsgCount())
  2655. },
  2656. },
  2657. {
  2658. name: "notify that BOS user is logged in to multiple locations",
  2659. instance: func() *state.SessionInstance {
  2660. instance1 := newTestInstance("me")
  2661. instance1.SetSignonComplete()
  2662. instance2 := instance1.Session().AddInstance()
  2663. instance2.SetSignonComplete()
  2664. instance3 := instance1.Session().AddInstance()
  2665. instance3.SetContactsInit()
  2666. // instance 3 is not yet signed on
  2667. return instance3
  2668. }(),
  2669. bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
  2670. service: wire.BOS,
  2671. mockParams: mockParams{
  2672. buddyBroadcasterParams: buddyBroadcasterParams{
  2673. broadcastVisibilityParams: broadcastVisibilityParams{
  2674. {
  2675. from: state.NewIdentScreenName("me"),
  2676. filter: nil,
  2677. doSendDepartures: false,
  2678. },
  2679. },
  2680. },
  2681. messageRelayerParams: messageRelayerParams{
  2682. relayToScreenNameParams: relayToScreenNameParams{
  2683. {
  2684. screenName: state.NewIdentScreenName("me"),
  2685. message: wire.SNACMessage{
  2686. Frame: wire.SNACFrame{
  2687. FoodGroup: wire.Stats,
  2688. SubGroup: wire.StatsSetMinReportInterval,
  2689. RequestID: wire.ReqIDFromServer,
  2690. },
  2691. Body: wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{
  2692. MinReportInterval: 1,
  2693. },
  2694. },
  2695. },
  2696. },
  2697. relayToOtherInstancesParams: relayToOtherInstancesParams{
  2698. {
  2699. screenName: state.NewIdentScreenName("me"),
  2700. message: func() wire.SNACMessage {
  2701. msg, err := systemMessage("Your screen name (me) is now signed into Open OSCAR Server in 3 locations. Click " +
  2702. "<a href=\"https://www.youtube.com/watch?v=dQw4w9WgXcQ&list=RDdQw4w9WgXcQ&start_radio=1&pp=ygUJcmljayByb2xsoAcB\">here</a> " +
  2703. "for more information.")
  2704. require.NoError(t, err)
  2705. return msg
  2706. }(),
  2707. },
  2708. },
  2709. },
  2710. },
  2711. validateSess: func(t *testing.T, instance *state.SessionInstance) {
  2712. assert.True(t, instance.Session().Instance(3).SignonComplete())
  2713. },
  2714. },
  2715. {
  2716. name: "BOS user is logged in to multiple locations, DisableMultiLoginNotif is true, no notification sent",
  2717. instance: func() *state.SessionInstance {
  2718. instance1 := newTestInstance("me")
  2719. instance1.SetSignonComplete()
  2720. instance2 := instance1.Session().AddInstance()
  2721. instance2.SetSignonComplete()
  2722. instance3 := instance1.Session().AddInstance()
  2723. instance3.SetContactsInit()
  2724. // instance 3 is not yet signed on
  2725. return instance3
  2726. }(),
  2727. bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
  2728. service: wire.BOS,
  2729. cfg: config.Config{
  2730. DisableMultiLoginNotif: true,
  2731. },
  2732. mockParams: mockParams{
  2733. buddyBroadcasterParams: buddyBroadcasterParams{
  2734. broadcastVisibilityParams: broadcastVisibilityParams{
  2735. {
  2736. from: state.NewIdentScreenName("me"),
  2737. filter: nil,
  2738. doSendDepartures: false,
  2739. },
  2740. },
  2741. },
  2742. messageRelayerParams: messageRelayerParams{
  2743. relayToScreenNameParams: relayToScreenNameParams{
  2744. {
  2745. screenName: state.NewIdentScreenName("me"),
  2746. message: wire.SNACMessage{
  2747. Frame: wire.SNACFrame{
  2748. FoodGroup: wire.Stats,
  2749. SubGroup: wire.StatsSetMinReportInterval,
  2750. RequestID: wire.ReqIDFromServer,
  2751. },
  2752. Body: wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{
  2753. MinReportInterval: 1,
  2754. },
  2755. },
  2756. },
  2757. },
  2758. // relayToOtherInstancesParams is intentionally omitted - notification should not be sent
  2759. },
  2760. },
  2761. validateSess: func(t *testing.T, instance *state.SessionInstance) {
  2762. assert.True(t, instance.Session().Instance(3).SignonComplete())
  2763. },
  2764. },
  2765. {
  2766. name: "notify that BOS user is online with offline messages, SetOfflineMsgCount fails",
  2767. instance: newTestInstance("me", sessOptCannedSignonTime, sessOptOfflineMsgCount(2), sessOptContactsInit),
  2768. bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
  2769. service: wire.BOS,
  2770. wantErr: assert.AnError,
  2771. mockParams: mockParams{
  2772. buddyBroadcasterParams: buddyBroadcasterParams{
  2773. broadcastVisibilityParams: broadcastVisibilityParams{
  2774. {
  2775. from: state.NewIdentScreenName("me"),
  2776. filter: nil,
  2777. doSendDepartures: false,
  2778. },
  2779. },
  2780. },
  2781. messageRelayerParams: messageRelayerParams{
  2782. relayToScreenNameParams: relayToScreenNameParams{
  2783. {
  2784. screenName: state.NewIdentScreenName("me"),
  2785. message: wire.SNACMessage{
  2786. Frame: wire.SNACFrame{
  2787. FoodGroup: wire.Stats,
  2788. SubGroup: wire.StatsSetMinReportInterval,
  2789. RequestID: wire.ReqIDFromServer,
  2790. },
  2791. Body: wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{
  2792. MinReportInterval: 1,
  2793. },
  2794. },
  2795. },
  2796. },
  2797. },
  2798. offlineMessageManagerParams: offlineMessageManagerParams{
  2799. setOfflineMsgCountParams: setOfflineMsgCountParams{
  2800. {
  2801. screenName: state.NewIdentScreenName("me"),
  2802. count: 0,
  2803. err: assert.AnError,
  2804. },
  2805. },
  2806. },
  2807. },
  2808. validateSess: func(t *testing.T, instance *state.SessionInstance) {
  2809. assert.True(t, instance.SignonComplete())
  2810. assert.Equal(t, 2, instance.OfflineMsgCount())
  2811. },
  2812. },
  2813. {
  2814. name: "upon joining, send chat room metadata and participant list to joining user; alert arrival to existing participants",
  2815. instance: chatter1,
  2816. bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
  2817. service: wire.Chat,
  2818. mockParams: mockParams{
  2819. chatMessageRelayerParams: chatMessageRelayerParams{
  2820. chatRelayToAllExceptParams: chatRelayToAllExceptParams{
  2821. {
  2822. screenName: state.NewIdentScreenName("chatter-1"),
  2823. cookie: chatRoom.Cookie(),
  2824. message: wire.SNACMessage{
  2825. Frame: wire.SNACFrame{
  2826. FoodGroup: wire.Chat,
  2827. SubGroup: wire.ChatUsersJoined,
  2828. },
  2829. Body: wire.SNAC_0x0E_0x03_ChatUsersJoined{
  2830. Users: []wire.TLVUserInfo{
  2831. chatter1.Session().TLVUserInfo(),
  2832. },
  2833. },
  2834. },
  2835. },
  2836. },
  2837. chatAllSessionsParams: chatAllSessionsParams{
  2838. {
  2839. cookie: chatRoom.Cookie(),
  2840. sessions: []*state.Session{
  2841. chatter1.Session(),
  2842. chatter2.Session(),
  2843. },
  2844. },
  2845. },
  2846. chatRelayToScreenNameParams: chatRelayToScreenNameParams{
  2847. {
  2848. cookie: chatRoom.Cookie(),
  2849. screenName: chatter1.IdentScreenName(),
  2850. message: wire.SNACMessage{
  2851. Frame: wire.SNACFrame{
  2852. FoodGroup: wire.Chat,
  2853. SubGroup: wire.ChatRoomInfoUpdate,
  2854. },
  2855. Body: wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
  2856. Exchange: chatRoom.Exchange(),
  2857. Cookie: chatRoom.Cookie(),
  2858. InstanceNumber: chatRoom.InstanceNumber(),
  2859. DetailLevel: chatRoom.DetailLevel(),
  2860. TLVBlock: wire.TLVBlock{
  2861. TLVList: chatRoom.TLVList(),
  2862. },
  2863. },
  2864. },
  2865. },
  2866. {
  2867. cookie: chatRoom.Cookie(),
  2868. screenName: chatter1.IdentScreenName(),
  2869. message: wire.SNACMessage{
  2870. Frame: wire.SNACFrame{
  2871. FoodGroup: wire.Chat,
  2872. SubGroup: wire.ChatUsersJoined,
  2873. },
  2874. Body: wire.SNAC_0x0E_0x03_ChatUsersJoined{
  2875. Users: []wire.TLVUserInfo{
  2876. chatter1.Session().TLVUserInfo(),
  2877. chatter2.Session().TLVUserInfo(),
  2878. },
  2879. },
  2880. },
  2881. },
  2882. },
  2883. },
  2884. chatRoomRegistryParams: chatRoomRegistryParams{
  2885. chatRoomByCookieParams: chatRoomByCookieParams{
  2886. {
  2887. cookie: chatRoom.Cookie(),
  2888. room: chatRoom,
  2889. },
  2890. },
  2891. },
  2892. },
  2893. validateSess: func(t *testing.T, instance *state.SessionInstance) {
  2894. assert.True(t, instance.SignonComplete())
  2895. },
  2896. },
  2897. }
  2898. for _, tt := range tests {
  2899. t.Run(tt.name, func(t *testing.T) {
  2900. buddyUpdateBroadcaster := newMockbuddyBroadcaster(t)
  2901. for _, params := range tt.mockParams.broadcastVisibilityParams {
  2902. buddyUpdateBroadcaster.EXPECT().
  2903. BroadcastVisibility(matchContext(), matchSession(params.from), params.filter, params.doSendDepartures).
  2904. Return(params.err)
  2905. }
  2906. messageRelayer := newMockMessageRelayer(t)
  2907. for _, params := range tt.mockParams.relayToScreenNameParams {
  2908. messageRelayer.EXPECT().
  2909. RelayToScreenName(matchContext(), params.screenName, params.message)
  2910. }
  2911. for _, params := range tt.mockParams.relayToSelfParams {
  2912. messageRelayer.EXPECT().
  2913. RelayToSelf(matchContext(), mock.Anything, mock.MatchedBy(func(msg wire.SNACMessage) bool {
  2914. return msg.Frame.FoodGroup == params.message.Frame.FoodGroup &&
  2915. msg.Frame.SubGroup == params.message.Frame.SubGroup
  2916. }))
  2917. }
  2918. for _, params := range tt.mockParams.relayToOtherInstancesParams {
  2919. messageRelayer.EXPECT().
  2920. RelayToOtherInstances(matchContext(), matchSession(params.screenName), params.message)
  2921. }
  2922. chatRoomManager := newMockChatRoomRegistry(t)
  2923. for _, params := range tt.mockParams.chatRoomByCookieParams {
  2924. chatRoomManager.EXPECT().
  2925. ChatRoomByCookie(context.Background(), params.cookie).
  2926. Return(params.room, params.err)
  2927. }
  2928. chatMessageRelayer := newMockChatMessageRelayer(t)
  2929. for _, params := range tt.mockParams.chatRelayToAllExceptParams {
  2930. chatMessageRelayer.EXPECT().
  2931. RelayToAllExcept(matchContext(), params.cookie, params.screenName, params.message)
  2932. }
  2933. for _, params := range tt.mockParams.chatAllSessionsParams {
  2934. chatMessageRelayer.EXPECT().
  2935. AllSessions(params.cookie).
  2936. Return(params.sessions)
  2937. }
  2938. for _, params := range tt.mockParams.chatRelayToScreenNameParams {
  2939. chatMessageRelayer.EXPECT().
  2940. RelayToScreenName(matchContext(), params.cookie, params.screenName, params.message)
  2941. }
  2942. profileManager := newMockProfileManager(t)
  2943. for _, params := range tt.mockParams.retrieveProfileParams {
  2944. profileManager.EXPECT().
  2945. Profile(matchContext(), params.screenName).
  2946. Return(params.result, params.err)
  2947. }
  2948. offlineMessageManager := newMockOfflineMessageManager(t)
  2949. for _, params := range tt.mockParams.setOfflineMsgCountParams {
  2950. offlineMessageManager.EXPECT().
  2951. SetOfflineMsgCount(matchContext(), params.screenName, params.count).
  2952. Return(params.err)
  2953. }
  2954. svc := NewOServiceService(tt.cfg, messageRelayer, slog.Default(), nil, chatRoomManager, nil, nil, nil, wire.DefaultSNACRateLimits(), chatMessageRelayer, profileManager, offlineMessageManager, nil)
  2955. svc.buddyBroadcaster = buddyUpdateBroadcaster
  2956. haveErr := svc.ClientOnline(context.Background(), tt.service, tt.bodyIn, tt.instance)
  2957. assert.ErrorIs(t, haveErr, tt.wantErr)
  2958. tt.validateSess(t, tt.instance)
  2959. })
  2960. }
  2961. }
  2962. func TestOServiceService_SetPrivacyFlags(t *testing.T) {
  2963. svc := OServiceService{
  2964. cfg: config.Config{},
  2965. logger: slog.Default(),
  2966. }
  2967. body := wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags{
  2968. PrivacyFlags: wire.OServicePrivacyFlagMember | wire.OServicePrivacyFlagIdle,
  2969. }
  2970. svc.SetPrivacyFlags(context.Background(), body)
  2971. }
  2972. func TestOServiceService_RateLimitUpdates(t *testing.T) {
  2973. rateClasses := [5]wire.RateClass{
  2974. {
  2975. ID: 1,
  2976. WindowSize: 80,
  2977. ClearLevel: 2500,
  2978. AlertLevel: 2000,
  2979. LimitLevel: 1500,
  2980. DisconnectLevel: 800,
  2981. MaxLevel: 6000,
  2982. },
  2983. {
  2984. ID: 2,
  2985. WindowSize: 80,
  2986. ClearLevel: 3000,
  2987. AlertLevel: 2000,
  2988. LimitLevel: 1500,
  2989. DisconnectLevel: 1000,
  2990. MaxLevel: 6000,
  2991. },
  2992. {
  2993. ID: 3,
  2994. WindowSize: 20,
  2995. ClearLevel: 5100,
  2996. AlertLevel: 5000,
  2997. LimitLevel: 4000,
  2998. DisconnectLevel: 3000,
  2999. MaxLevel: 6000,
  3000. },
  3001. {
  3002. ID: 4,
  3003. WindowSize: 20,
  3004. ClearLevel: 5500,
  3005. AlertLevel: 5300,
  3006. LimitLevel: 4200,
  3007. DisconnectLevel: 3000,
  3008. MaxLevel: 8000,
  3009. },
  3010. {
  3011. ID: 5,
  3012. WindowSize: 10,
  3013. ClearLevel: 5500,
  3014. AlertLevel: 5300,
  3015. LimitLevel: 4200,
  3016. DisconnectLevel: 3000,
  3017. MaxLevel: 8000,
  3018. },
  3019. }
  3020. svc := OServiceService{
  3021. cfg: config.Config{},
  3022. logger: slog.Default(),
  3023. }
  3024. t.Run("(win aim 1.x) transition state from clear > alert > limited > clear, then change rate limit param", func(t *testing.T) {
  3025. now := time.Now()
  3026. instance := newTestInstance("me")
  3027. instance.Session().SetRateClasses(now, wire.NewRateLimitClasses(rateClasses))
  3028. classId := wire.RateLimitClassID(3)
  3029. instance.Session().SubscribeRateLimits([]wire.RateLimitClassID{classId})
  3030. // get into an alert state
  3031. maxTries := 4
  3032. for i := 1; i <= maxTries; i++ {
  3033. now = now.Add(time.Millisecond)
  3034. if s := instance.Session().EvaluateRateLimit(now, classId); s == wire.RateLimitStatusAlert {
  3035. break
  3036. }
  3037. if i == maxTries {
  3038. t.Fail()
  3039. return
  3040. }
  3041. }
  3042. outputSNACs := svc.RateLimitUpdates(context.Background(), instance, now)
  3043. expect := wire.SNACMessage{
  3044. Frame: wire.SNACFrame{
  3045. FoodGroup: wire.OService,
  3046. SubGroup: wire.OServiceRateParamChange,
  3047. RequestID: wire.ReqIDFromServer,
  3048. },
  3049. Body: wire.SNAC_0x01_0x0A_OServiceRateParamsChange{
  3050. Code: 2,
  3051. Rate: wire.RateParamsSNAC{
  3052. ID: 3,
  3053. WindowSize: 20,
  3054. ClearLevel: 5100,
  3055. AlertLevel: 5000,
  3056. LimitLevel: 4000,
  3057. DisconnectLevel: 3000,
  3058. CurrentLevel: 4886,
  3059. MaxLevel: 6000,
  3060. },
  3061. },
  3062. }
  3063. assert.Equal(t, expect, outputSNACs[0])
  3064. // get into a rate-limited state
  3065. maxTries = 4
  3066. for i := 1; i <= maxTries; i++ {
  3067. now = now.Add(time.Millisecond)
  3068. if s := instance.Session().EvaluateRateLimit(now, classId); s == wire.RateLimitStatusLimited {
  3069. break
  3070. }
  3071. if i == maxTries {
  3072. t.Fail()
  3073. return
  3074. }
  3075. }
  3076. outputSNACs = svc.RateLimitUpdates(context.Background(), instance, now)
  3077. expect = wire.SNACMessage{
  3078. Frame: wire.SNACFrame{
  3079. FoodGroup: wire.OService,
  3080. SubGroup: wire.OServiceRateParamChange,
  3081. RequestID: wire.ReqIDFromServer,
  3082. },
  3083. Body: wire.SNAC_0x01_0x0A_OServiceRateParamsChange{
  3084. Code: 3,
  3085. Rate: wire.RateParamsSNAC{
  3086. ID: 3,
  3087. WindowSize: 20,
  3088. ClearLevel: 5100,
  3089. AlertLevel: 5000,
  3090. LimitLevel: 4000,
  3091. DisconnectLevel: 3000,
  3092. CurrentLevel: 3978,
  3093. MaxLevel: 6000,
  3094. },
  3095. },
  3096. }
  3097. assert.Equal(t, expect, outputSNACs[0])
  3098. // simulate waiting a minute for the clear threshold
  3099. now = now.Add(time.Minute)
  3100. // verify that the clear threshold has been reached
  3101. outputSNACs = svc.RateLimitUpdates(context.Background(), instance, now)
  3102. expect = wire.SNACMessage{
  3103. Frame: wire.SNACFrame{
  3104. FoodGroup: wire.OService,
  3105. SubGroup: wire.OServiceRateParamChange,
  3106. RequestID: wire.ReqIDFromServer,
  3107. },
  3108. Body: wire.SNAC_0x01_0x0A_OServiceRateParamsChange{
  3109. Code: 4,
  3110. Rate: wire.RateParamsSNAC{
  3111. ID: 3,
  3112. WindowSize: 20,
  3113. ClearLevel: 5100,
  3114. AlertLevel: 5000,
  3115. LimitLevel: 4000,
  3116. DisconnectLevel: 3000,
  3117. CurrentLevel: 6000,
  3118. MaxLevel: 6000,
  3119. },
  3120. },
  3121. }
  3122. assert.Equal(t, expect, outputSNACs[0])
  3123. // verify rate class param changes are detected
  3124. classesCopy := rateClasses
  3125. classesCopy[2].DisconnectLevel--
  3126. instance.Session().SetRateClasses(now, wire.NewRateLimitClasses(classesCopy))
  3127. outputSNACs = svc.RateLimitUpdates(context.Background(), instance, now)
  3128. expect = wire.SNACMessage{
  3129. Frame: wire.SNACFrame{
  3130. FoodGroup: wire.OService,
  3131. SubGroup: wire.OServiceRateParamChange,
  3132. RequestID: wire.ReqIDFromServer,
  3133. },
  3134. Body: wire.SNAC_0x01_0x0A_OServiceRateParamsChange{
  3135. Code: 1,
  3136. Rate: wire.RateParamsSNAC{
  3137. ID: 3,
  3138. WindowSize: 20,
  3139. ClearLevel: 5100,
  3140. AlertLevel: 5000,
  3141. LimitLevel: 4000,
  3142. DisconnectLevel: 2999,
  3143. CurrentLevel: 6000,
  3144. MaxLevel: 6000,
  3145. },
  3146. },
  3147. }
  3148. assert.Equal(t, expect, outputSNACs[0])
  3149. })
  3150. t.Run("(win aim > 1.x) transition state from clear > alert > limited > clear", func(t *testing.T) {
  3151. now := time.Now()
  3152. instance := newTestInstance("me")
  3153. instance.Session().SetRateClasses(now, wire.NewRateLimitClasses(rateClasses))
  3154. var versions [wire.MDir + 1]uint16
  3155. versions[wire.OService] = 3
  3156. instance.SetFoodGroupVersions(versions)
  3157. classId := wire.RateLimitClassID(3)
  3158. instance.Session().SubscribeRateLimits([]wire.RateLimitClassID{classId})
  3159. // get into an alert state
  3160. maxTries := 4
  3161. for i := 1; i <= maxTries; i++ {
  3162. now = now.Add(time.Millisecond)
  3163. if s := instance.Session().EvaluateRateLimit(now, classId); s == wire.RateLimitStatusAlert {
  3164. break
  3165. }
  3166. if i == maxTries {
  3167. t.Fail()
  3168. return
  3169. }
  3170. }
  3171. outputSNACs := svc.RateLimitUpdates(context.Background(), instance, now)
  3172. expect := wire.SNACMessage{
  3173. Frame: wire.SNACFrame{
  3174. FoodGroup: wire.OService,
  3175. SubGroup: wire.OServiceRateParamChange,
  3176. RequestID: wire.ReqIDFromServer,
  3177. },
  3178. Body: wire.SNAC_0x01_0x0A_OServiceRateParamsChange{
  3179. Code: 2,
  3180. Rate: wire.RateParamsSNAC{
  3181. ID: 3,
  3182. WindowSize: 20,
  3183. ClearLevel: 5100,
  3184. AlertLevel: 5000,
  3185. LimitLevel: 4000,
  3186. DisconnectLevel: 3000,
  3187. CurrentLevel: 4886,
  3188. MaxLevel: 6000,
  3189. V2Params: &struct {
  3190. LastTime uint32
  3191. DroppingSNACs uint8
  3192. }{
  3193. DroppingSNACs: 0,
  3194. },
  3195. },
  3196. },
  3197. }
  3198. assert.Equal(t, expect, outputSNACs[0])
  3199. // get into a rate-limited state
  3200. maxTries = 4
  3201. for i := 1; i <= maxTries; i++ {
  3202. now = now.Add(time.Millisecond)
  3203. if s := instance.Session().EvaluateRateLimit(now, classId); s == wire.RateLimitStatusLimited {
  3204. break
  3205. }
  3206. if i == maxTries {
  3207. t.Fail()
  3208. return
  3209. }
  3210. }
  3211. outputSNACs = svc.RateLimitUpdates(context.Background(), instance, now)
  3212. expect = wire.SNACMessage{
  3213. Frame: wire.SNACFrame{
  3214. FoodGroup: wire.OService,
  3215. SubGroup: wire.OServiceRateParamChange,
  3216. RequestID: wire.ReqIDFromServer,
  3217. },
  3218. Body: wire.SNAC_0x01_0x0A_OServiceRateParamsChange{
  3219. Code: 3,
  3220. Rate: wire.RateParamsSNAC{
  3221. ID: 3,
  3222. WindowSize: 20,
  3223. ClearLevel: 5100,
  3224. AlertLevel: 5000,
  3225. LimitLevel: 4000,
  3226. DisconnectLevel: 3000,
  3227. CurrentLevel: 3978,
  3228. MaxLevel: 6000,
  3229. V2Params: &struct {
  3230. LastTime uint32
  3231. DroppingSNACs uint8
  3232. }{
  3233. DroppingSNACs: 1,
  3234. },
  3235. },
  3236. },
  3237. }
  3238. assert.Equal(t, expect, outputSNACs[0])
  3239. // simulate waiting a minute for the clear threshold
  3240. now = now.Add(time.Minute)
  3241. // verify that the clear threshold has been reached
  3242. outputSNACs = svc.RateLimitUpdates(context.Background(), instance, now)
  3243. expect = wire.SNACMessage{
  3244. Frame: wire.SNACFrame{
  3245. FoodGroup: wire.OService,
  3246. SubGroup: wire.OServiceRateParamChange,
  3247. RequestID: wire.ReqIDFromServer,
  3248. },
  3249. Body: wire.SNAC_0x01_0x0A_OServiceRateParamsChange{
  3250. Code: 4,
  3251. Rate: wire.RateParamsSNAC{
  3252. ID: 3,
  3253. WindowSize: 20,
  3254. ClearLevel: 5100,
  3255. AlertLevel: 5000,
  3256. LimitLevel: 4000,
  3257. DisconnectLevel: 3000,
  3258. CurrentLevel: 6000,
  3259. MaxLevel: 6000,
  3260. V2Params: &struct {
  3261. LastTime uint32
  3262. DroppingSNACs uint8
  3263. }{
  3264. DroppingSNACs: 0,
  3265. LastTime: 60,
  3266. },
  3267. },
  3268. },
  3269. }
  3270. assert.Equal(t, expect, outputSNACs[0])
  3271. // verify rate class param changes are detected
  3272. classesCopy := rateClasses
  3273. classesCopy[2].DisconnectLevel--
  3274. instance.Session().SetRateClasses(now, wire.NewRateLimitClasses(classesCopy))
  3275. outputSNACs = svc.RateLimitUpdates(context.Background(), instance, now)
  3276. expect = wire.SNACMessage{
  3277. Frame: wire.SNACFrame{
  3278. FoodGroup: wire.OService,
  3279. SubGroup: wire.OServiceRateParamChange,
  3280. RequestID: wire.ReqIDFromServer,
  3281. },
  3282. Body: wire.SNAC_0x01_0x0A_OServiceRateParamsChange{
  3283. Code: 1,
  3284. Rate: wire.RateParamsSNAC{
  3285. ID: 3,
  3286. WindowSize: 20,
  3287. ClearLevel: 5100,
  3288. AlertLevel: 5000,
  3289. LimitLevel: 4000,
  3290. DisconnectLevel: 2999,
  3291. CurrentLevel: 6000,
  3292. MaxLevel: 6000,
  3293. V2Params: &struct {
  3294. LastTime uint32
  3295. DroppingSNACs uint8
  3296. }{
  3297. DroppingSNACs: 0,
  3298. LastTime: 0,
  3299. },
  3300. },
  3301. },
  3302. }
  3303. assert.Equal(t, expect, outputSNACs[0])
  3304. })
  3305. }
  3306. func TestOServiceService_RateParamsSubAdd(t *testing.T) {
  3307. svc := OServiceService{
  3308. logger: slog.New(slog.NewTextHandler(io.Discard, nil)), // silence logs
  3309. }
  3310. classes := [5]wire.RateClass{
  3311. {
  3312. ID: 1,
  3313. WindowSize: 80,
  3314. ClearLevel: 2500,
  3315. AlertLevel: 2000,
  3316. LimitLevel: 1500,
  3317. DisconnectLevel: 800,
  3318. MaxLevel: 6000,
  3319. },
  3320. {
  3321. ID: 2,
  3322. WindowSize: 80,
  3323. ClearLevel: 3000,
  3324. AlertLevel: 2000,
  3325. LimitLevel: 1500,
  3326. DisconnectLevel: 1000,
  3327. MaxLevel: 6000,
  3328. },
  3329. {
  3330. ID: 3,
  3331. WindowSize: 20,
  3332. ClearLevel: 5100,
  3333. AlertLevel: 5000,
  3334. LimitLevel: 4000,
  3335. DisconnectLevel: 3000,
  3336. MaxLevel: 6000,
  3337. },
  3338. {
  3339. ID: 4,
  3340. WindowSize: 20,
  3341. ClearLevel: 5500,
  3342. AlertLevel: 5300,
  3343. LimitLevel: 4200,
  3344. DisconnectLevel: 3000,
  3345. MaxLevel: 8000,
  3346. },
  3347. {
  3348. ID: 5,
  3349. WindowSize: 10,
  3350. ClearLevel: 5500,
  3351. AlertLevel: 5300,
  3352. LimitLevel: 4200,
  3353. DisconnectLevel: 3000,
  3354. MaxLevel: 8000,
  3355. },
  3356. }
  3357. t.Run("happy path", func(t *testing.T) {
  3358. classes := classes
  3359. instance := newTestInstance("me")
  3360. instance.Session().SetRateClasses(time.Now(), wire.NewRateLimitClasses(classes))
  3361. deltas, _ := instance.Session().ObserveRateChanges(time.Now())
  3362. assert.Len(t, deltas, 0)
  3363. // expect 3 rate limit class changes
  3364. classes[0].MaxLevel = 8888
  3365. classes[1].MaxLevel = 8888
  3366. classes[2].MaxLevel = 8888
  3367. classes[3].MaxLevel = 8888
  3368. classes[4].MaxLevel = 8888
  3369. instance.Session().SetRateClasses(time.Now(), wire.NewRateLimitClasses(classes))
  3370. snac := wire.SNAC_0x01_0x08_OServiceRateParamsSubAdd{
  3371. ClassIDs: []uint16{2, 5},
  3372. }
  3373. svc.RateParamsSubAdd(context.Background(), instance, snac)
  3374. deltas, _ = instance.Session().ObserveRateChanges(time.Now())
  3375. assert.Len(t, deltas, 2)
  3376. // expect 5 rate limit class changes
  3377. classes[0].MaxLevel = 9999
  3378. classes[1].MaxLevel = 9999
  3379. classes[2].MaxLevel = 9999
  3380. classes[3].MaxLevel = 9999
  3381. classes[4].MaxLevel = 9999
  3382. instance.Session().SetRateClasses(time.Now(), wire.NewRateLimitClasses(classes))
  3383. snac = wire.SNAC_0x01_0x08_OServiceRateParamsSubAdd{
  3384. ClassIDs: []uint16{1, 3, 4},
  3385. }
  3386. svc.RateParamsSubAdd(context.Background(), instance, snac)
  3387. deltas, _ = instance.Session().ObserveRateChanges(time.Now())
  3388. assert.Len(t, deltas, 5)
  3389. })
  3390. t.Run("empty subscribe list", func(t *testing.T) {
  3391. classes := classes
  3392. instance := newTestInstance("me")
  3393. instance.Session().SetRateClasses(time.Now(), wire.NewRateLimitClasses(classes))
  3394. deltas, _ := instance.Session().ObserveRateChanges(time.Now())
  3395. assert.Len(t, deltas, 0)
  3396. // expect 3 rate limit class changes
  3397. classes[0].MaxLevel = 8888
  3398. classes[1].MaxLevel = 8888
  3399. classes[2].MaxLevel = 8888
  3400. classes[3].MaxLevel = 8888
  3401. classes[4].MaxLevel = 8888
  3402. instance.Session().SetRateClasses(time.Now(), wire.NewRateLimitClasses(classes))
  3403. snac := wire.SNAC_0x01_0x08_OServiceRateParamsSubAdd{
  3404. ClassIDs: []uint16{},
  3405. }
  3406. svc.RateParamsSubAdd(context.Background(), instance, snac)
  3407. deltas, _ = instance.Session().ObserveRateChanges(time.Now())
  3408. assert.Empty(t, deltas)
  3409. })
  3410. t.Run("class IDs out of range", func(t *testing.T) {
  3411. classes := classes
  3412. instance := newTestInstance("me")
  3413. instance.Session().SetRateClasses(time.Now(), wire.NewRateLimitClasses(classes))
  3414. deltas, _ := instance.Session().ObserveRateChanges(time.Now())
  3415. assert.Len(t, deltas, 0)
  3416. // expect 3 rate limit class changes
  3417. classes[0].MaxLevel = 8888
  3418. classes[1].MaxLevel = 8888
  3419. classes[2].MaxLevel = 8888
  3420. classes[3].MaxLevel = 8888
  3421. classes[4].MaxLevel = 8888
  3422. instance.Session().SetRateClasses(time.Now(), wire.NewRateLimitClasses(classes))
  3423. snac := wire.SNAC_0x01_0x08_OServiceRateParamsSubAdd{
  3424. ClassIDs: []uint16{0, 6},
  3425. }
  3426. svc.RateParamsSubAdd(context.Background(), instance, snac)
  3427. deltas, _ = instance.Session().ObserveRateChanges(time.Now())
  3428. assert.Empty(t, deltas)
  3429. })
  3430. }