oservice_test.go 110 KB

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