| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429 |
- package foodgroup
- import (
- "bytes"
- "context"
- "io"
- "log/slog"
- "sync"
- "testing"
- "time"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/stretchr/testify/require"
- "github.com/mk6i/open-oscar-server/config"
- "github.com/mk6i/open-oscar-server/state"
- "github.com/mk6i/open-oscar-server/wire"
- )
- func TestOServiceService_ServiceRequest(t *testing.T) {
- chatRoom := state.NewChatRoom("the-chat-room", state.NewIdentScreenName(""), state.PrivateExchange)
- cases := []struct {
- // name is the unit test name
- name string
- // service is the OSCAR service type
- service uint16
- // listener is the connection listener
- listenerGroup config.ListenerGroup
- // instance is the session of the user requesting the chat service
- // info
- instance *state.SessionInstance
- // inputSNAC is the SNAC sent by the sender client
- inputSNAC wire.SNACMessage
- // expectSNACFrame is the SNAC frame sent from the server to the recipient
- // client
- expectOutput wire.SNACMessage
- // mockParams is the list of params sent to mocks that satisfy this
- // method's dependencies
- mockParams mockParams
- // expectErr is the expected error returned by the router
- expectErr error
- }{
- {
- name: "request info for connecting to admin svc, return admin svc connection metadata",
- service: wire.BOS,
- listenerGroup: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:1234"},
- instance: newTestInstance("me"),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
- FoodGroup: wire.Admin,
- },
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceServiceResponse,
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.Admin),
- wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1234"),
- wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
- wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
- },
- },
- },
- },
- mockParams: mockParams{
- cookieBakerParams: cookieBakerParams{
- cookieIssueParams: cookieIssueParams{
- {
- dataIn: []byte{
- 0x00, 0x07, // admin service
- 0x02, 'm', 'e',
- 0x0, // no client ID
- 0x0, // no chat cookie
- 0x0, // multi conn flag
- 0x0, // kerberos flag
- 0x01, // session num
- },
- cookieOut: []byte("the-cookie"),
- },
- },
- },
- },
- },
- {
- name: "request info for connecting to alert svc, return alert svc connection metadata",
- service: wire.BOS,
- listenerGroup: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:1234"},
- instance: newTestInstance("me"),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
- FoodGroup: wire.Alert,
- },
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceServiceResponse,
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.Alert),
- wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1234"),
- wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
- wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
- },
- },
- },
- },
- mockParams: mockParams{
- cookieBakerParams: cookieBakerParams{
- cookieIssueParams: cookieIssueParams{
- {
- dataIn: []byte{
- 0x00, 0x18, // alert service
- 0x02, 'm', 'e',
- 0x0, // no client ID
- 0x0, // no chat cookie
- 0x0, // multi conn flag
- 0x0, // kerberos flag
- 0x01, // session num
- },
- cookieOut: []byte("the-cookie"),
- },
- },
- },
- },
- },
- {
- name: "request info for connecting to BART service, return BART connection metadata",
- service: wire.BOS,
- listenerGroup: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:1234"},
- instance: newTestInstance("me"),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
- FoodGroup: wire.BART,
- },
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceServiceResponse,
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.BART),
- wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1234"),
- wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
- wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
- },
- },
- },
- },
- mockParams: mockParams{
- cookieBakerParams: cookieBakerParams{
- cookieIssueParams: cookieIssueParams{
- {
- dataIn: []byte{
- 0x00, 0x10, // chatnav service
- 0x02, 'm', 'e',
- 0x0, // no client ID
- 0x0, // no chat cookie
- 0x0, // multi conn flag
- 0x0, // kerberos flag
- 0x01, // session num
- },
- cookieOut: []byte("the-cookie"),
- },
- },
- },
- },
- },
- {
- name: "request info for connecting to chat nav, return chat nav connection metadata",
- service: wire.BOS,
- listenerGroup: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:1234"},
- instance: newTestInstance("me"),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
- FoodGroup: wire.ChatNav,
- },
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceServiceResponse,
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.ChatNav),
- wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1234"),
- wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
- wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
- },
- },
- },
- },
- mockParams: mockParams{
- cookieBakerParams: cookieBakerParams{
- cookieIssueParams: cookieIssueParams{
- {
- dataIn: []byte{
- 0x00, 0x0d, // chatnav service
- 0x02, 'm', 'e',
- 0x0, // no client ID
- 0x0, // no chat cookie
- 0x0, // multi conn flag
- 0x0, // kerberos flag
- 0x01, // session num
- },
- cookieOut: []byte("the-cookie"),
- },
- },
- },
- },
- },
- {
- name: "request info for connecting to chat room, return chat service and chat room metadata",
- service: wire.BOS,
- listenerGroup: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:1234"},
- instance: newTestInstance("me"),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
- FoodGroup: wire.Chat,
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(0x01, wire.SNAC_0x01_0x04_TLVRoomInfo{
- Exchange: chatRoom.Exchange(),
- Cookie: chatRoom.Cookie(),
- InstanceNumber: chatRoom.InstanceNumber(),
- }),
- },
- },
- },
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceServiceResponse,
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.Chat),
- wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1234"),
- wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-auth-cookie")),
- wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
- },
- },
- },
- },
- mockParams: func() mockParams {
- return mockParams{
- chatRoomRegistryParams: chatRoomRegistryParams{
- chatRoomByCookieParams: chatRoomByCookieParams{
- {
- cookie: chatRoom.Cookie(),
- room: chatRoom,
- },
- },
- },
- cookieBakerParams: cookieBakerParams{
- cookieIssueParams: cookieIssueParams{
- {
- dataIn: []byte{
- 0x00, 0x0e, // chat service,
- 0x02, 'm', 'e', // screen name
- 0x00, // no client ID
- 0x11, '4', '-', '0', '-', 't', 'h', 'e', '-', 'c', 'h', 'a', 't', '-', 'r', 'o', 'o', 'm',
- 0x0, // multi conn flag
- 0x0, // kerberos flag
- 0x01, // session num
- },
- cookieOut: []byte("the-auth-cookie"),
- },
- },
- },
- }
- }(),
- },
- {
- name: "request info for connecting to BART service, return BART connection metadata",
- service: wire.BOS,
- listenerGroup: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:1234"},
- instance: newTestInstance("me"),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
- FoodGroup: wire.ODir,
- },
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceServiceResponse,
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.ODir),
- wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1234"),
- wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
- wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
- },
- },
- },
- },
- mockParams: mockParams{
- cookieBakerParams: cookieBakerParams{
- cookieIssueParams: cookieIssueParams{
- {
- dataIn: []byte{
- 0x00, 0x0F, // chatnav service
- 0x02, 'm', 'e',
- 0x0, // no client ID
- 0x0, // no chat cookie
- 0x0, // multi conn flag
- 0x0, // kerberos flag
- 0x01, // session num
- },
- cookieOut: []byte("the-cookie"),
- },
- },
- },
- },
- },
- {
- name: "request info for connecting to non-existent chat room, return ErrChatRoomNotFound",
- service: wire.BOS,
- listenerGroup: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:1234"},
- instance: newTestInstance("me"),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
- FoodGroup: wire.Chat,
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(0x01, wire.SNAC_0x01_0x04_TLVRoomInfo{
- Exchange: 8,
- Cookie: "the-chat-cookie",
- InstanceNumber: 16,
- }),
- },
- },
- },
- },
- mockParams: mockParams{
- chatRoomRegistryParams: chatRoomRegistryParams{
- chatRoomByCookieParams: chatRoomByCookieParams{
- {
- cookie: "the-chat-cookie",
- err: state.ErrChatRoomNotFound,
- },
- },
- },
- },
- expectErr: state.ErrChatRoomNotFound,
- },
- {
- name: "request info from a non-BOS service",
- service: wire.Chat,
- instance: newTestInstance("me"),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
- FoodGroup: wire.ICBM,
- },
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceErr,
- RequestID: 1234,
- },
- Body: wire.SNACError{
- Code: wire.ErrorCodeNotSupportedByHost,
- },
- },
- },
- {
- name: "request info for ICBM service, return invalid SNAC err",
- service: wire.BOS,
- instance: newTestInstance("me"),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
- FoodGroup: wire.ICBM,
- },
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceErr,
- RequestID: 1234,
- },
- Body: wire.SNACError{
- Code: wire.ErrorCodeServiceUnavailable,
- },
- },
- },
- {
- name: "request info for connecting to admin svc with SSL, return admin svc SSL connection metadata",
- service: wire.BOS,
- listenerGroup: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:1234", BOSAdvertisedHostSSL: "127.0.0.1:1235"},
- instance: newTestInstance("me"),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
- FoodGroup: wire.Admin,
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OserviceTLVTagsSSLUseSSL, []byte{}),
- },
- },
- },
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceServiceResponse,
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.Admin),
- wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1235"),
- wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
- wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x02)),
- },
- },
- },
- },
- mockParams: mockParams{
- cookieBakerParams: cookieBakerParams{
- cookieIssueParams: cookieIssueParams{
- {
- dataIn: []byte{
- 0x00, 0x07, // admin service
- 0x02, 'm', 'e',
- 0x0, // no client ID
- 0x0, // no chat cookie
- 0x0, // multi conn flag
- 0x0, // kerberos flag
- 0x01, // session num
- },
- cookieOut: []byte("the-cookie"),
- },
- },
- },
- },
- },
- {
- name: "request info for connecting to alert svc with SSL, return alert svc SSL connection metadata",
- service: wire.BOS,
- listenerGroup: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:1234", BOSAdvertisedHostSSL: "127.0.0.1:1235"},
- instance: newTestInstance("me"),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
- FoodGroup: wire.Alert,
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OserviceTLVTagsSSLUseSSL, []byte{}),
- },
- },
- },
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceServiceResponse,
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.Alert),
- wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1235"),
- wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
- wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x02)),
- },
- },
- },
- },
- mockParams: mockParams{
- cookieBakerParams: cookieBakerParams{
- cookieIssueParams: cookieIssueParams{
- {
- dataIn: []byte{
- 0x00, 0x18, // alert service
- 0x02, 'm', 'e',
- 0x0, // no client ID
- 0x0, // no chat cookie
- 0x0, // multi conn flag
- 0x0, // kerberos flag
- 0x01, // session num
- },
- cookieOut: []byte("the-cookie"),
- },
- },
- },
- },
- },
- {
- name: "request info for connecting to BART service with SSL, return BART SSL connection metadata",
- service: wire.BOS,
- listenerGroup: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:1234", BOSAdvertisedHostSSL: "127.0.0.1:1235"},
- instance: newTestInstance("me"),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
- FoodGroup: wire.BART,
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OserviceTLVTagsSSLUseSSL, []byte{}),
- },
- },
- },
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceServiceResponse,
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.BART),
- wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1235"),
- wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
- wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x02)),
- },
- },
- },
- },
- mockParams: mockParams{
- cookieBakerParams: cookieBakerParams{
- cookieIssueParams: cookieIssueParams{
- {
- dataIn: []byte{
- 0x00, 0x10, // BART service
- 0x02, 'm', 'e',
- 0x0, // no client ID
- 0x0, // no chat cookie
- 0x0, // multi conn flag
- 0x0, // kerberos flag
- 0x01, // session num
- },
- cookieOut: []byte("the-cookie"),
- },
- },
- },
- },
- },
- {
- name: "request info for connecting to chat nav with SSL, return chat nav SSL connection metadata",
- service: wire.BOS,
- listenerGroup: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:1234", BOSAdvertisedHostSSL: "127.0.0.1:1235"},
- instance: newTestInstance("me"),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
- FoodGroup: wire.ChatNav,
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OserviceTLVTagsSSLUseSSL, []byte{}),
- },
- },
- },
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceServiceResponse,
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.ChatNav),
- wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1235"),
- wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
- wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x02)),
- },
- },
- },
- },
- mockParams: mockParams{
- cookieBakerParams: cookieBakerParams{
- cookieIssueParams: cookieIssueParams{
- {
- dataIn: []byte{
- 0x00, 0x0d, // chatnav service
- 0x02, 'm', 'e',
- 0x0, // no client ID
- 0x0, // no chat cookie
- 0x0, // multi conn flag
- 0x0, // kerberos flag
- 0x01, // session num
- },
- cookieOut: []byte("the-cookie"),
- },
- },
- },
- },
- },
- {
- name: "request info for connecting to chat room with SSL, return chat service SSL connection metadata",
- service: wire.BOS,
- listenerGroup: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:1234", BOSAdvertisedHostSSL: "127.0.0.1:1235"},
- instance: newTestInstance("me"),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
- FoodGroup: wire.Chat,
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(0x01, wire.SNAC_0x01_0x04_TLVRoomInfo{
- Exchange: chatRoom.Exchange(),
- Cookie: chatRoom.Cookie(),
- InstanceNumber: chatRoom.InstanceNumber(),
- }),
- wire.NewTLVBE(wire.OserviceTLVTagsSSLUseSSL, []byte{}),
- },
- },
- },
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceServiceResponse,
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.Chat),
- wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1235"),
- wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-auth-cookie")),
- wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x02)),
- },
- },
- },
- },
- mockParams: func() mockParams {
- return mockParams{
- chatRoomRegistryParams: chatRoomRegistryParams{
- chatRoomByCookieParams: chatRoomByCookieParams{
- {
- cookie: chatRoom.Cookie(),
- room: chatRoom,
- },
- },
- },
- cookieBakerParams: cookieBakerParams{
- cookieIssueParams: cookieIssueParams{
- {
- dataIn: []byte{
- 0x00, 0x0e, // chat service,
- 0x02, 'm', 'e', // screen name
- 0x00, // no client ID
- 0x11, '4', '-', '0', '-', 't', 'h', 'e', '-', 'c', 'h', 'a', 't', '-', 'r', 'o', 'o', 'm',
- 0x0, // multi conn flag
- 0x0, // kerberos flag
- 0x01, // session num
- },
- cookieOut: []byte("the-auth-cookie"),
- },
- },
- },
- }
- }(),
- },
- {
- name: "request info for connecting to ODir service with SSL, return ODir SSL connection metadata",
- service: wire.BOS,
- listenerGroup: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:1234", BOSAdvertisedHostSSL: "127.0.0.1:1235"},
- instance: newTestInstance("me"),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
- FoodGroup: wire.ODir,
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OserviceTLVTagsSSLUseSSL, []byte{}),
- },
- },
- },
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceServiceResponse,
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.ODir),
- wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1235"),
- wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
- wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x02)),
- },
- },
- },
- },
- mockParams: mockParams{
- cookieBakerParams: cookieBakerParams{
- cookieIssueParams: cookieIssueParams{
- {
- dataIn: []byte{
- 0x00, 0x0F, // ODir service
- 0x02, 'm', 'e',
- 0x0, // no client ID
- 0x0, // no chat cookie
- 0x0, // multi conn flag
- 0x0, // kerberos flag
- 0x01, // session num
- },
- cookieOut: []byte("the-cookie"),
- },
- },
- },
- },
- },
- {
- name: "request SSL service but listener doesn't support SSL, return plaintext connection metadata",
- service: wire.BOS,
- listenerGroup: config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:1234"},
- instance: newTestInstance("me"),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x04_OServiceServiceRequest{
- FoodGroup: wire.Admin,
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OserviceTLVTagsSSLUseSSL, []byte{}),
- },
- },
- },
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceServiceResponse,
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.Admin),
- wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:1234"),
- wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
- wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
- },
- },
- },
- },
- mockParams: mockParams{
- cookieBakerParams: cookieBakerParams{
- cookieIssueParams: cookieIssueParams{
- {
- dataIn: []byte{
- 0x00, 0x07, // admin service
- 0x02, 'm', 'e',
- 0x0, // no client ID
- 0x0, // no chat cookie
- 0x0, // multi conn flag
- 0x0, // kerberos flag
- 0x01, // session num
- },
- cookieOut: []byte("the-cookie"),
- },
- },
- },
- },
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- //
- // initialize dependencies
- //
- chatRoomManager := newMockChatRoomRegistry(t)
- for _, params := range tc.mockParams.chatRoomByCookieParams {
- chatRoomManager.EXPECT().
- ChatRoomByCookie(context.Background(), params.cookie).
- Return(params.room, params.err)
- }
- cookieIssuer := newMockCookieBaker(t)
- for _, params := range tc.mockParams.cookieIssueParams {
- cookieIssuer.EXPECT().
- Issue(params.dataIn).
- Return(params.cookieOut, params.err)
- }
- chatMessageRelayer := newMockChatMessageRelayer(t)
- //
- // send input SNAC
- //
- svc := NewOServiceService(config.Config{}, nil, slog.Default(), cookieIssuer, chatRoomManager, nil, nil, nil, wire.DefaultSNACRateLimits(), chatMessageRelayer, nil, nil, nil)
- outputSNAC, err := svc.ServiceRequest(context.Background(), tc.service, tc.instance, tc.inputSNAC.Frame,
- tc.inputSNAC.Body.(wire.SNAC_0x01_0x04_OServiceServiceRequest), tc.listenerGroup)
- assert.ErrorIs(t, err, tc.expectErr)
- if tc.expectErr != nil {
- return
- }
- //
- // verify output
- //
- assert.Equal(t, tc.expectOutput, outputSNAC)
- })
- }
- }
- func TestOServiceService_ServiceRequest_LinkedAccountSignon(t *testing.T) {
- primaryUser := state.NewIdentScreenName("PrimaryUser")
- linkedUser := state.DisplayScreenName("LinkedUser")
- linkedUserIdent := state.NewIdentScreenName(string(linkedUser))
- // cookieDataFor returns the serialized ServerCookie bytes that fnIssueCookie
- // will pass to cookieIssuer.Issue, with the given MultiConnFlag.
- cookieDataFor := func(sn state.DisplayScreenName, flag wire.MultiConnFlag) []byte {
- buf := &bytes.Buffer{}
- assert.NoError(t, wire.MarshalBE(state.ServerCookie{
- Service: wire.BOS,
- ScreenName: sn,
- MultiConnFlag: uint8(flag),
- }, buf))
- return buf.Bytes()
- }
- makeBody := func(includeSUID bool, screenName string) wire.SNAC_0x01_0x04_OServiceServiceRequest {
- body := wire.SNAC_0x01_0x04_OServiceServiceRequest{
- FoodGroup: wire.OService,
- }
- if includeSUID {
- body.TLVList = append(body.TLVList,
- wire.NewTLVBE(uint16(0x0028), []byte("some-uuid-bytes")))
- }
- if screenName != "" {
- body.TLVList = append(body.TLVList,
- wire.NewTLVBE(uint16(0x01), []byte(screenName)))
- }
- return body
- }
- cases := []struct {
- name string
- instance *state.SessionInstance
- inputBody wire.SNAC_0x01_0x04_OServiceServiceRequest
- feedbagItems []wire.FeedbagItem
- feedbagErr error
- // setupCookie is whether to expect cookie issuance
- setupCookie bool
- expectOutput wire.SNACMessage
- wantErrContains string
- wantErr error
- }{
- {
- name: "linked account signon OK, returns BOS cookie with no MultiConnFlag",
- inputBody: makeBody(true, string(linkedUser)),
- feedbagItems: []wire.FeedbagItem{{ClassID: wire.FeedbagClassIdAlInfo, Name: linkedUserIdent.String()}},
- setupCookie: true,
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceServiceResponse,
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.OService),
- wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:5190"),
- wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
- wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
- },
- },
- },
- },
- },
- {
- name: "primary has MultiConnFlagsRecentClient, linked account cookie inherits flag",
- instance: newTestInstance(state.DisplayScreenName(primaryUser.String()), sessOptMultiConnFlag(wire.MultiConnFlagsRecentClient)),
- inputBody: makeBody(true, string(linkedUser)),
- feedbagItems: []wire.FeedbagItem{{ClassID: wire.FeedbagClassIdAlInfo, Name: linkedUserIdent.String()}},
- setupCookie: true,
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceServiceResponse,
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.OService),
- wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:5190"),
- wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
- wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
- },
- },
- },
- },
- },
- {
- name: "primary has MultiConnFlagsSingleClient, linked account cookie inherits flag",
- instance: newTestInstance(state.DisplayScreenName(primaryUser.String()), sessOptMultiConnFlag(wire.MultiConnFlagsSingleClient)),
- inputBody: makeBody(true, string(linkedUser)),
- feedbagItems: []wire.FeedbagItem{{ClassID: wire.FeedbagClassIdAlInfo, Name: linkedUserIdent.String()}},
- setupCookie: true,
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceServiceResponse,
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceTLVTagsGroupID, wire.OService),
- wire.NewTLVBE(wire.OServiceTLVTagsReconnectHere, "127.0.0.1:5190"),
- wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")),
- wire.NewTLVBE(wire.OServiceTLVTagsSSLState, uint8(0x00)),
- },
- },
- },
- },
- },
- {
- name: "missing SUID TLV 0x0028, returns error",
- inputBody: makeBody(false, string(linkedUser)),
- wantErrContains: "unknown OService request",
- },
- {
- name: "missing screenname TLV 0x01, returns error",
- inputBody: makeBody(true, ""),
- wantErrContains: "new session request missing linked screenname TLV 0x01",
- },
- {
- name: "accounts not linked, returns error",
- inputBody: makeBody(true, string(linkedUser)),
- wantErrContains: "linked account session requested but accounts are not linked",
- },
- {
- name: "feedbag lookup error, error propagated",
- inputBody: makeBody(true, string(linkedUser)),
- feedbagErr: io.EOF,
- wantErr: io.EOF,
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- instance := tc.instance
- if instance == nil {
- instance = newTestInstance(state.DisplayScreenName(primaryUser.String()))
- }
- cookieIssuer := newMockCookieBaker(t)
- if tc.setupCookie {
- cookieIssuer.EXPECT().
- Issue(cookieDataFor(linkedUser, instance.MultiConnFlag())).
- Return([]byte("the-cookie"), nil)
- }
- feedbagManager := newMockFeedbagManager(t)
- if tc.inputBody.HasTag(0x0028) {
- if snBytes, ok := tc.inputBody.Bytes(0x01); ok && len(snBytes) > 0 {
- feedbagManager.EXPECT().
- Feedbag(matchContext(), primaryUser).
- Return(tc.feedbagItems, tc.feedbagErr)
- }
- }
- svc := NewOServiceService(config.Config{}, nil, slog.Default(), cookieIssuer, nil, nil, nil, nil,
- wire.DefaultSNACRateLimits(), nil, nil, nil, feedbagManager)
- listener := config.ListenerGroup{BOSAdvertisedHostPlain: "127.0.0.1:5190"}
- outputSNAC, err := svc.ServiceRequest(context.Background(), wire.BOS, instance,
- wire.SNACFrame{RequestID: 1234}, tc.inputBody, listener)
- if tc.wantErrContains != "" {
- assert.ErrorContains(t, err, tc.wantErrContains)
- return
- }
- if tc.wantErr != nil {
- assert.ErrorIs(t, err, tc.wantErr)
- return
- }
- assert.NoError(t, err)
- assert.Equal(t, tc.expectOutput, outputSNAC)
- })
- }
- }
- func TestOServiceService_SetUserInfoFields(t *testing.T) {
- cases := []struct {
- // name is the unit test name
- name string
- // instance is the session of the user whose info is being set
- instance *state.SessionInstance
- // inputSNAC is the SNAC sent from the client to the server
- inputSNAC wire.SNACMessage
- // expectOutput is the SNAC reply sent from the server back to the
- // client
- expectOutput wire.SNACMessage
- // broadcastMessage is the arrival/departure message sent to buddies
- broadcastMessage []struct {
- recipients []string
- msg wire.SNACMessage
- }
- // interestedUserLookups contains all the users who have this user on
- // their buddy list
- interestedUserLookups map[string][]string
- // expectErr is the expected error returned
- expectErr error
- // mockParams is the list of params sent to mocks that satisfy this
- // method's dependencies
- mockParams mockParams
- // checkSession validates the state of the session
- checkSession func(*testing.T, *state.Session)
- }{
- {
- name: "set user status to visible aim < 6",
- instance: newTestInstance("me", sessOptInvisible),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
- },
- },
- },
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceUserInfoUpdate,
- RequestID: 1234,
- },
- Body: func(val any) bool {
- snac, ok := val.(wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate)
- if !ok {
- return false
- }
- if len(snac.UserInfo) == 0 {
- return false
- }
- status, hasStatus := snac.UserInfo[0].Uint32BE(wire.OServiceUserInfoStatus)
- return hasStatus && status == uint32(0x0000)
- },
- },
- mockParams: mockParams{
- buddyBroadcasterParams: buddyBroadcasterParams{
- broadcastBuddyArrivedParams: broadcastBuddyArrivedParams{
- {
- screenName: state.DisplayScreenName("me"),
- },
- },
- },
- },
- checkSession: func(t *testing.T, session *state.Session) {
- assert.False(t, session.Invisible())
- },
- },
- {
- name: "set user status to invisible aim < 6",
- instance: newTestInstance("me"),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0100)),
- },
- },
- },
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceUserInfoUpdate,
- RequestID: 1234,
- },
- Body: func(val any) bool {
- snac, ok := val.(wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate)
- if !ok {
- return false
- }
- if len(snac.UserInfo) == 0 {
- return false
- }
- status, hasStatus := snac.UserInfo[0].Uint32BE(wire.OServiceUserInfoStatus)
- return hasStatus && status == uint32(0x0100)
- },
- },
- mockParams: mockParams{
- buddyBroadcasterParams: buddyBroadcasterParams{
- broadcastBuddyDepartedParams: broadcastBuddyDepartedParams{
- {
- screenName: state.NewIdentScreenName("me"),
- },
- },
- },
- },
- checkSession: func(t *testing.T, session *state.Session) {
- assert.True(t, session.Invisible())
- },
- },
- {
- name: "set user status to visible aim >= 6",
- instance: newTestInstance("me", sessOptInvisible, sessOptSetFoodGroupVersion(wire.OService, 4)),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
- },
- },
- },
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceUserInfoUpdate,
- RequestID: 1234,
- },
- Body: func(val any) bool {
- snac, ok := val.(wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate)
- if !ok {
- return false
- }
- if len(snac.UserInfo) == 0 {
- return false
- }
- status, hasStatus := snac.UserInfo[0].Uint32BE(wire.OServiceUserInfoStatus)
- return hasStatus && status == uint32(0x0000)
- },
- },
- mockParams: mockParams{
- buddyBroadcasterParams: buddyBroadcasterParams{
- broadcastBuddyArrivedParams: broadcastBuddyArrivedParams{
- {
- screenName: state.DisplayScreenName("me"),
- },
- },
- },
- },
- checkSession: func(t *testing.T, session *state.Session) {
- assert.False(t, session.Invisible())
- },
- },
- {
- name: "set user status to invisible aim >= 6",
- instance: newTestInstance("me", sessOptSetFoodGroupVersion(wire.OService, 4)),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0100)),
- },
- },
- },
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceUserInfoUpdate,
- RequestID: 1234,
- },
- Body: func(val any) bool {
- snac, ok := val.(wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate)
- if !ok {
- return false
- }
- if len(snac.UserInfo) == 0 {
- return false
- }
- status, hasStatus := snac.UserInfo[0].Uint32BE(wire.OServiceUserInfoStatus)
- return hasStatus && status == uint32(0x0100)
- },
- },
- mockParams: mockParams{
- buddyBroadcasterParams: buddyBroadcasterParams{
- broadcastBuddyDepartedParams: broadcastBuddyDepartedParams{
- {
- screenName: state.NewIdentScreenName("me"),
- },
- },
- },
- },
- checkSession: func(t *testing.T, session *state.Session) {
- assert.True(t, session.Invisible())
- },
- },
- {
- name: "set ICQ direct connect info",
- instance: newTestInstance("1000003", sessOptUserInfoFlag(wire.OServiceUserFlagICQ)),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceUserInfoICQDC, wire.ICQDCInfo{
- DCType: 4,
- ProtoVersion: 10,
- }),
- },
- },
- },
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceUserInfoUpdate,
- RequestID: 1234,
- },
- Body: func(val any) bool {
- snac, ok := val.(wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate)
- if !ok || len(snac.UserInfo) == 0 {
- return false
- }
- dc, hasDC := snac.UserInfo[0].Bytes(wire.OServiceUserInfoICQDC)
- if !hasDC {
- return false
- }
- var got wire.ICQDCInfo
- if err := wire.UnmarshalBE(&got, bytes.NewReader(dc)); err != nil {
- return false
- }
- return got.DCType == 4 && got.ProtoVersion == 10
- },
- },
- checkSession: func(t *testing.T, session *state.Session) {
- info := session.Instances()[0].ICQDCInfo()
- assert.Equal(t, uint8(4), info.DCType)
- assert.Equal(t, uint16(10), info.ProtoVersion)
- },
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- buddyUpdateBroadcaster := newMockbuddyBroadcaster(t)
- for _, params := range tc.mockParams.broadcastBuddyArrivedParams {
- buddyUpdateBroadcaster.EXPECT().
- BroadcastBuddyArrived(mock.Anything, state.NewIdentScreenName(params.screenName.String()), mock.MatchedBy(func(userInfo wire.TLVUserInfo) bool {
- return userInfo.ScreenName == params.screenName.String()
- })).
- Return(params.err)
- }
- for _, params := range tc.mockParams.broadcastBuddyDepartedParams {
- buddyUpdateBroadcaster.EXPECT().
- BroadcastBuddyDeparted(mock.Anything, params.screenName).
- Return(params.err)
- }
- svc := OServiceService{
- cfg: config.Config{},
- logger: slog.Default(),
- buddyBroadcaster: buddyUpdateBroadcaster,
- }
- outputSNAC, err := svc.SetUserInfoFields(context.TODO(), tc.instance, tc.inputSNAC.Frame,
- tc.inputSNAC.Body.(wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields))
- assert.ErrorIs(t, err, tc.expectErr)
- if tc.expectErr != nil {
- return
- }
- assert.Equal(t, tc.expectOutput.Frame, outputSNAC.Frame)
- if matcherFn, ok := tc.expectOutput.Body.(func(val any) bool); ok {
- assert.True(t, matcherFn(outputSNAC.Body), "Body matcher function failed")
- } else {
- assert.Equal(t, tc.expectOutput.Body, outputSNAC.Body)
- }
- tc.checkSession(t, tc.instance.Session())
- })
- }
- }
- func TestOServiceService_RateParamsQuery(t *testing.T) {
- rateClasses := wire.NewRateLimitClasses([5]wire.RateClass{
- {
- ID: 1,
- WindowSize: 80,
- ClearLevel: 2500,
- AlertLevel: 2000,
- LimitLevel: 1500,
- DisconnectLevel: 800,
- MaxLevel: 6000,
- },
- {
- ID: 2,
- WindowSize: 80,
- ClearLevel: 3000,
- AlertLevel: 2000,
- LimitLevel: 1500,
- DisconnectLevel: 1000,
- MaxLevel: 6000,
- },
- {
- ID: 3,
- WindowSize: 20,
- ClearLevel: 5100,
- AlertLevel: 5000,
- LimitLevel: 4000,
- DisconnectLevel: 3000,
- MaxLevel: 6000,
- },
- {
- ID: 4,
- WindowSize: 20,
- ClearLevel: 5500,
- AlertLevel: 5300,
- LimitLevel: 4200,
- DisconnectLevel: 3000,
- MaxLevel: 8000,
- },
- {
- ID: 5,
- WindowSize: 10,
- ClearLevel: 5500,
- AlertLevel: 5300,
- LimitLevel: 4200,
- DisconnectLevel: 3000,
- MaxLevel: 8000,
- },
- })
- expectRateGroups := []struct {
- ID uint16
- Pairs []struct {
- FoodGroup uint16
- SubGroup uint16
- } `oscar:"count_prefix=uint16"`
- }{
- {
- ID: 1,
- Pairs: []struct {
- FoodGroup uint16
- SubGroup uint16
- }{
- {FoodGroup: wire.OService, SubGroup: wire.OServiceErr},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceClientOnline},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceHostOnline},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceServiceRequest},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceServiceResponse},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceRateParamsQuery},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceRateParamsReply},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceRateParamsSubAdd},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceRateDelParamSub},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceRateParamChange},
- {FoodGroup: wire.OService, SubGroup: wire.OServicePauseReq},
- {FoodGroup: wire.OService, SubGroup: wire.OServicePauseAck},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceResume},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceUserInfoQuery},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceUserInfoUpdate},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceEvilNotification},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceIdleNotification},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceMigrateGroups},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceMotd},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceSetPrivacyFlags},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceWellKnownUrls},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceNoop},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceClientVersions},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceHostVersions},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceMaxConfigQuery},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceMaxConfigReply},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceStoreConfig},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceConfigQuery},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceConfigReply},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceSetUserInfoFields},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceProbeReq},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceProbeAck},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceBartReply},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceBartQuery2},
- {FoodGroup: wire.OService, SubGroup: wire.OServiceBartReply2},
- {FoodGroup: wire.Locate, SubGroup: wire.LocateErr},
- {FoodGroup: wire.Locate, SubGroup: wire.LocateRightsQuery},
- {FoodGroup: wire.Locate, SubGroup: wire.LocateRightsReply},
- {FoodGroup: wire.Locate, SubGroup: wire.LocateSetInfo},
- {FoodGroup: wire.Locate, SubGroup: wire.LocateUserInfoReply},
- {FoodGroup: wire.Locate, SubGroup: wire.LocateWatcherSubRequest},
- {FoodGroup: wire.Locate, SubGroup: wire.LocateWatcherNotification},
- {FoodGroup: wire.Locate, SubGroup: wire.LocateSetDirReply},
- {FoodGroup: wire.Locate, SubGroup: wire.LocateGetDirReply},
- {FoodGroup: wire.Locate, SubGroup: wire.LocateGroupCapabilityQuery},
- {FoodGroup: wire.Locate, SubGroup: wire.LocateGroupCapabilityReply},
- {FoodGroup: wire.Locate, SubGroup: wire.LocateSetKeywordInfo},
- {FoodGroup: wire.Locate, SubGroup: wire.LocateSetKeywordReply},
- {FoodGroup: wire.Locate, SubGroup: wire.LocateGetKeywordInfo},
- {FoodGroup: wire.Locate, SubGroup: wire.LocateGetKeywordReply},
- {FoodGroup: wire.Locate, SubGroup: wire.LocateFindListByEmail},
- {FoodGroup: wire.Locate, SubGroup: wire.LocateFindListReply},
- {FoodGroup: wire.Locate, SubGroup: wire.LocateUserInfoQuery2},
- {FoodGroup: wire.Buddy, SubGroup: wire.BuddyErr},
- {FoodGroup: wire.Buddy, SubGroup: wire.BuddyRightsQuery},
- {FoodGroup: wire.Buddy, SubGroup: wire.BuddyRightsReply},
- {FoodGroup: wire.Buddy, SubGroup: wire.BuddyWatcherListQuery},
- {FoodGroup: wire.Buddy, SubGroup: wire.BuddyWatcherListResponse},
- {FoodGroup: wire.Buddy, SubGroup: wire.BuddyWatcherSubRequest},
- {FoodGroup: wire.Buddy, SubGroup: wire.BuddyWatcherNotification},
- {FoodGroup: wire.Buddy, SubGroup: wire.BuddyRejectNotification},
- {FoodGroup: wire.Buddy, SubGroup: wire.BuddyArrived},
- {FoodGroup: wire.Buddy, SubGroup: wire.BuddyDeparted},
- {FoodGroup: wire.Buddy, SubGroup: wire.BuddyAddTempBuddies},
- {FoodGroup: wire.Buddy, SubGroup: wire.BuddyDelTempBuddies},
- {FoodGroup: wire.ICBM, SubGroup: wire.ICBMErr},
- {FoodGroup: wire.ICBM, SubGroup: wire.ICBMAddParameters},
- {FoodGroup: wire.ICBM, SubGroup: wire.ICBMDelParameters},
- {FoodGroup: wire.ICBM, SubGroup: wire.ICBMParameterQuery},
- {FoodGroup: wire.ICBM, SubGroup: wire.ICBMParameterReply},
- {FoodGroup: wire.ICBM, SubGroup: wire.ICBMChannelMsgToClient},
- {FoodGroup: wire.ICBM, SubGroup: wire.ICBMEvilRequest},
- {FoodGroup: wire.ICBM, SubGroup: wire.ICBMEvilReply},
- {FoodGroup: wire.ICBM, SubGroup: wire.ICBMMissedCalls},
- {FoodGroup: wire.ICBM, SubGroup: wire.ICBMClientErr},
- {FoodGroup: wire.ICBM, SubGroup: wire.ICBMHostAck},
- {FoodGroup: wire.ICBM, SubGroup: wire.ICBMSinStored},
- {FoodGroup: wire.ICBM, SubGroup: wire.ICBMSinListQuery},
- {FoodGroup: wire.ICBM, SubGroup: wire.ICBMSinListReply},
- {FoodGroup: wire.ICBM, SubGroup: wire.ICBMOfflineRetrieve},
- {FoodGroup: wire.ICBM, SubGroup: wire.ICBMSinDelete},
- {FoodGroup: wire.ICBM, SubGroup: wire.ICBMNotifyRequest},
- {FoodGroup: wire.ICBM, SubGroup: wire.ICBMNotifyReply},
- {FoodGroup: wire.ICBM, SubGroup: wire.ICBMClientEvent},
- {FoodGroup: wire.Advert, SubGroup: wire.AdvertErr},
- {FoodGroup: wire.Advert, SubGroup: wire.AdvertAdsQuery},
- {FoodGroup: wire.Advert, SubGroup: wire.AdvertAdsReply},
- {FoodGroup: wire.Invite, SubGroup: wire.InviteErr},
- {FoodGroup: wire.Invite, SubGroup: wire.InviteRequestQuery},
- {FoodGroup: wire.Invite, SubGroup: wire.InviteRequestReply},
- {FoodGroup: wire.Admin, SubGroup: wire.AdminErr},
- {FoodGroup: wire.Admin, SubGroup: wire.AdminInfoQuery},
- {FoodGroup: wire.Admin, SubGroup: wire.AdminInfoReply},
- {FoodGroup: wire.Admin, SubGroup: wire.AdminInfoChangeRequest},
- {FoodGroup: wire.Admin, SubGroup: wire.AdminInfoChangeReply},
- {FoodGroup: wire.Admin, SubGroup: wire.AdminAcctConfirmRequest},
- {FoodGroup: wire.Admin, SubGroup: wire.AdminAcctConfirmReply},
- {FoodGroup: wire.Admin, SubGroup: wire.AdminAcctDeleteRequest},
- {FoodGroup: wire.Admin, SubGroup: wire.AdminAcctDeleteReply},
- {FoodGroup: wire.Popup, SubGroup: wire.PopupErr},
- {FoodGroup: wire.Popup, SubGroup: wire.PopupDisplay},
- {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenyErr},
- {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenyRightsQuery},
- {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenyRightsReply},
- {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenySetGroupPermitMask},
- {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenyBosErr},
- {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenyAddTempPermitListEntries},
- {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenyDelTempPermitListEntries},
- {FoodGroup: wire.UserLookup, SubGroup: wire.UserLookupErr},
- {FoodGroup: wire.UserLookup, SubGroup: wire.UserLookupFindByEmail},
- {FoodGroup: wire.UserLookup, SubGroup: wire.UserLookupFindReply},
- {FoodGroup: wire.Stats, SubGroup: wire.StatsErr},
- {FoodGroup: wire.Stats, SubGroup: wire.StatsSetMinReportInterval},
- {FoodGroup: wire.Stats, SubGroup: wire.StatsReportEvents},
- {FoodGroup: wire.Stats, SubGroup: wire.StatsReportAck},
- {FoodGroup: wire.Translate, SubGroup: wire.TranslateErr},
- {FoodGroup: wire.Translate, SubGroup: wire.TranslateRequest},
- {FoodGroup: wire.Translate, SubGroup: wire.TranslateReply},
- {FoodGroup: wire.ChatNav, SubGroup: wire.ChatNavErr},
- {FoodGroup: wire.ChatNav, SubGroup: wire.ChatNavRequestChatRights},
- {FoodGroup: wire.ChatNav, SubGroup: wire.ChatNavRequestExchangeInfo},
- {FoodGroup: wire.ChatNav, SubGroup: wire.ChatNavRequestRoomInfo},
- {FoodGroup: wire.ChatNav, SubGroup: wire.ChatNavRequestMoreRoomInfo},
- {FoodGroup: wire.ChatNav, SubGroup: wire.ChatNavRequestOccupantList},
- {FoodGroup: wire.ChatNav, SubGroup: wire.ChatNavSearchForRoom},
- {FoodGroup: wire.ChatNav, SubGroup: wire.ChatNavCreateRoom},
- {FoodGroup: wire.ChatNav, SubGroup: wire.ChatNavNavInfo},
- {FoodGroup: wire.Chat, SubGroup: wire.ChatErr},
- {FoodGroup: wire.Chat, SubGroup: wire.ChatRoomInfoUpdate},
- {FoodGroup: wire.Chat, SubGroup: wire.ChatUsersJoined},
- {FoodGroup: wire.Chat, SubGroup: wire.ChatUsersLeft},
- {FoodGroup: wire.Chat, SubGroup: wire.ChatChannelMsgToClient},
- {FoodGroup: wire.Chat, SubGroup: wire.ChatEvilRequest},
- {FoodGroup: wire.Chat, SubGroup: wire.ChatEvilReply},
- {FoodGroup: wire.Chat, SubGroup: wire.ChatClientErr},
- {FoodGroup: wire.ODir, SubGroup: wire.ODirErr},
- {FoodGroup: wire.ODir, SubGroup: wire.ODirInfoQuery},
- {FoodGroup: wire.ODir, SubGroup: wire.ODirInfoReply},
- {FoodGroup: wire.ODir, SubGroup: wire.ODirKeywordListQuery},
- {FoodGroup: wire.BART, SubGroup: wire.BARTErr},
- {FoodGroup: wire.BART, SubGroup: wire.BARTUploadQuery},
- {FoodGroup: wire.BART, SubGroup: wire.BARTDownloadQuery},
- {FoodGroup: wire.BART, SubGroup: wire.BARTDownload2Query},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagErr},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRightsQuery},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRightsReply},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagQuery},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagQueryIfModified},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagReply},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUse},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertClass},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateClass},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteClass},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagStatus},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagReplyNotModified},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteUser},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagStartCluster},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagEndCluster},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagAuthorizeBuddy},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagPreAuthorizeBuddy},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagPreAuthorizedBuddy},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRemoveMe},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRemoveMe2},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRequestAuthorizeToHost},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRequestAuthorizeToClient},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRespondAuthorizeToHost},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRespondAuthorizeToClient},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagBuddyAdded},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRequestAuthorizeToBadog},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRespondAuthorizeToBadog},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagBuddyAddedToBadog},
- {FoodGroup: wire.Feedbag, SubGroup: 0x0020},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagTestSnac},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagForwardMsg},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagIsAuthRequiredQuery},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagIsAuthRequiredReply},
- {FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagRecentBuddyUpdate},
- {FoodGroup: wire.Feedbag, SubGroup: 0x0026},
- {FoodGroup: wire.Feedbag, SubGroup: 0x0027},
- {FoodGroup: wire.Feedbag, SubGroup: 0x0028},
- {FoodGroup: wire.ICQ, SubGroup: wire.ICQErr},
- {FoodGroup: wire.ICQ, SubGroup: wire.ICQDBQuery},
- {FoodGroup: wire.ICQ, SubGroup: wire.ICQDBReply},
- {FoodGroup: wire.BUCP, SubGroup: wire.BUCPErr},
- {FoodGroup: wire.BUCP, SubGroup: wire.BUCPLoginRequest},
- {FoodGroup: wire.BUCP, SubGroup: wire.BUCPRegisterRequest},
- {FoodGroup: wire.BUCP, SubGroup: wire.BUCPChallengeRequest},
- {FoodGroup: wire.BUCP, SubGroup: wire.BUCPAsasnRequest},
- {FoodGroup: wire.BUCP, SubGroup: wire.BUCPSecuridRequest},
- {FoodGroup: wire.BUCP, SubGroup: wire.BUCPRegistrationImageRequest},
- {FoodGroup: wire.Alert, SubGroup: wire.AlertErr},
- {FoodGroup: wire.Alert, SubGroup: wire.AlertSetAlertRequest},
- {FoodGroup: wire.Alert, SubGroup: wire.AlertGetSubsRequest},
- {FoodGroup: wire.Alert, SubGroup: wire.AlertNotifyCapabilities},
- {FoodGroup: wire.Alert, SubGroup: wire.AlertNotify},
- {FoodGroup: wire.Alert, SubGroup: wire.AlertGetRuleRequest},
- {FoodGroup: wire.Alert, SubGroup: wire.AlertGetFeedRequest},
- {FoodGroup: wire.Alert, SubGroup: wire.AlertRefreshFeed},
- {FoodGroup: wire.Alert, SubGroup: wire.AlertEvent},
- {FoodGroup: wire.Alert, SubGroup: wire.AlertQogSnac},
- {FoodGroup: wire.Alert, SubGroup: wire.AlertRefreshFeedStock},
- {FoodGroup: wire.Alert, SubGroup: wire.AlertNotifyTransport},
- {FoodGroup: wire.Alert, SubGroup: wire.AlertSetAlertRequestV2},
- {FoodGroup: wire.Alert, SubGroup: wire.AlertNotifyAck},
- {FoodGroup: wire.Alert, SubGroup: wire.AlertNotifyDisplayCapabilities},
- {FoodGroup: wire.Alert, SubGroup: wire.AlertUserOnline},
- },
- },
- {
- ID: 2,
- Pairs: []struct {
- FoodGroup uint16
- SubGroup uint16
- }{
- {FoodGroup: wire.Buddy, SubGroup: wire.BuddyAddBuddies},
- {FoodGroup: wire.Buddy, SubGroup: wire.BuddyDelBuddies},
- {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenyAddPermListEntries},
- {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenyDelPermListEntries},
- {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenyAddDenyListEntries},
- {FoodGroup: wire.PermitDeny, SubGroup: wire.PermitDenyDelDenyListEntries},
- {FoodGroup: wire.Chat, SubGroup: wire.ChatChannelMsgToHost},
- },
- },
- {
- ID: 3,
- Pairs: []struct {
- FoodGroup uint16
- SubGroup uint16
- }{
- {FoodGroup: wire.Locate, SubGroup: wire.LocateUserInfoQuery},
- {FoodGroup: wire.ICBM, SubGroup: wire.ICBMChannelMsgToHost},
- },
- },
- {
- ID: 4,
- Pairs: []struct {
- FoodGroup uint16
- SubGroup uint16
- }{
- {FoodGroup: wire.Locate, SubGroup: wire.LocateSetDirInfo},
- {FoodGroup: wire.Locate, SubGroup: wire.LocateGetDirInfo},
- },
- },
- {
- ID: 5,
- Pairs: []struct {
- FoodGroup uint16
- SubGroup uint16
- }{},
- },
- }
- cases := []struct {
- // name is the unit test name
- name string
- // instance is the session of the user requesting the chat service
- // info
- instance *state.SessionInstance
- // inputSNAC is the SNAC sent by the sender client
- inputSNAC wire.SNACMessage
- // expectSNACFrame is the SNAC frame sent from the server to the recipient
- // client
- expectOutput wire.SNACMessage
- // expectErr is the expected error returned by the router
- expectErr error
- // timeNow returns the current time
- timeNow func() time.Time
- }{
- {
- name: "get rate limits for AIM > 1.x clients",
- instance: newTestInstance("me", sessOptSetFoodGroupVersion(wire.OService, 3), sessOptSetRateClasses(rateClasses)),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{RequestID: 1234},
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceRateParamsReply,
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x07_OServiceRateParamsReply{
- RateClasses: []wire.RateParamsSNAC{
- {
- ID: 1,
- WindowSize: 80,
- ClearLevel: 2500,
- AlertLevel: 2000,
- LimitLevel: 1500,
- DisconnectLevel: 800,
- MaxLevel: 6000,
- CurrentLevel: 6000,
- V2Params: &struct {
- LastTime uint32
- DroppingSNACs uint8
- }{
- LastTime: 999,
- DroppingSNACs: 0x00,
- },
- },
- {
- ID: 2,
- WindowSize: 80,
- ClearLevel: 3000,
- AlertLevel: 2000,
- LimitLevel: 1500,
- DisconnectLevel: 1000,
- MaxLevel: 6000,
- CurrentLevel: 6000,
- V2Params: &struct {
- LastTime uint32
- DroppingSNACs uint8
- }{
- LastTime: 999,
- DroppingSNACs: 0x00,
- },
- },
- {
- ID: 3,
- WindowSize: 20,
- ClearLevel: 5100,
- AlertLevel: 5000,
- LimitLevel: 4000,
- DisconnectLevel: 3000,
- MaxLevel: 6000,
- CurrentLevel: 6000,
- V2Params: &struct {
- LastTime uint32
- DroppingSNACs uint8
- }{
- LastTime: 999,
- DroppingSNACs: 0x00,
- },
- },
- {
- ID: 4,
- WindowSize: 20,
- ClearLevel: 5500,
- AlertLevel: 5300,
- LimitLevel: 4200,
- DisconnectLevel: 3000,
- MaxLevel: 8000,
- CurrentLevel: 8000,
- V2Params: &struct {
- LastTime uint32
- DroppingSNACs uint8
- }{
- LastTime: 999,
- DroppingSNACs: 0x00,
- },
- },
- {
- ID: 5,
- WindowSize: 10,
- ClearLevel: 5500,
- AlertLevel: 5300,
- LimitLevel: 4200,
- DisconnectLevel: 3000,
- MaxLevel: 8000,
- CurrentLevel: 8000,
- V2Params: &struct {
- LastTime uint32
- DroppingSNACs uint8
- }{
- LastTime: 999,
- DroppingSNACs: 0x00,
- },
- },
- },
- RateGroups: expectRateGroups,
- },
- },
- timeNow: func() time.Time {
- return time.Unix(1000, 0)
- },
- },
- {
- name: "get rate limits for AIM 1.x client",
- instance: newTestInstance("me", sessClientID("AOL Instant Messenger (TM), version 1."), sessOptSetRateClasses(rateClasses)),
- inputSNAC: wire.SNACMessage{
- Frame: wire.SNACFrame{RequestID: 1234},
- },
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceRateParamsReply,
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x07_OServiceRateParamsReply{
- RateClasses: []wire.RateParamsSNAC{
- {
- ID: 1,
- WindowSize: 80,
- ClearLevel: 2500,
- AlertLevel: 2000,
- LimitLevel: 1500,
- DisconnectLevel: 800,
- MaxLevel: 6000,
- },
- {
- ID: 2,
- WindowSize: 80,
- ClearLevel: 3000,
- AlertLevel: 2000,
- LimitLevel: 1500,
- DisconnectLevel: 1000,
- MaxLevel: 6000,
- },
- {
- ID: 3,
- WindowSize: 20,
- ClearLevel: 5100,
- AlertLevel: 5000,
- LimitLevel: 4000,
- DisconnectLevel: 3000,
- MaxLevel: 6000,
- },
- {
- ID: 4,
- WindowSize: 20,
- ClearLevel: 5500,
- AlertLevel: 5300,
- LimitLevel: 4200,
- DisconnectLevel: 3000,
- MaxLevel: 8000,
- },
- {
- ID: 5,
- WindowSize: 10,
- ClearLevel: 5500,
- AlertLevel: 5300,
- LimitLevel: 4200,
- DisconnectLevel: 3000,
- MaxLevel: 8000,
- },
- },
- RateGroups: expectRateGroups,
- },
- },
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- svc := OServiceService{
- cfg: config.Config{},
- logger: slog.Default(),
- snacRateLimits: wire.DefaultSNACRateLimits(),
- timeNow: tc.timeNow,
- }
- have := svc.RateParamsQuery(context.Background(), tc.instance, tc.inputSNAC.Frame)
- assert.ElementsMatch(t, tc.expectOutput.Body.(wire.SNAC_0x01_0x07_OServiceRateParamsReply).RateGroups[0].Pairs,
- have.Body.(wire.SNAC_0x01_0x07_OServiceRateParamsReply).RateGroups[0].Pairs)
- assert.ElementsMatch(t, tc.expectOutput.Body.(wire.SNAC_0x01_0x07_OServiceRateParamsReply).RateGroups[1].Pairs,
- have.Body.(wire.SNAC_0x01_0x07_OServiceRateParamsReply).RateGroups[1].Pairs)
- assert.ElementsMatch(t, tc.expectOutput.Body.(wire.SNAC_0x01_0x07_OServiceRateParamsReply).RateGroups[2].Pairs,
- have.Body.(wire.SNAC_0x01_0x07_OServiceRateParamsReply).RateGroups[2].Pairs)
- assert.ElementsMatch(t, tc.expectOutput.Body.(wire.SNAC_0x01_0x07_OServiceRateParamsReply).RateGroups[3].Pairs,
- have.Body.(wire.SNAC_0x01_0x07_OServiceRateParamsReply).RateGroups[3].Pairs)
- assert.ElementsMatch(t, tc.expectOutput.Body.(wire.SNAC_0x01_0x07_OServiceRateParamsReply).RateGroups[4].Pairs,
- have.Body.(wire.SNAC_0x01_0x07_OServiceRateParamsReply).RateGroups[4].Pairs)
- })
- }
- }
- func TestOServiceService_HostOnline(t *testing.T) {
- cases := []struct {
- // name is the unit test name
- name string
- // service is the OSCAR service type
- service uint16
- // expectSNACFrame is the SNAC frame sent from the server to the recipient
- // client
- expectOutput wire.SNACMessage
- }{
- {
- name: "Admin service",
- service: wire.Admin,
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceHostOnline,
- RequestID: wire.ReqIDFromServer,
- },
- Body: wire.SNAC_0x01_0x03_OServiceHostOnline{
- FoodGroups: []uint16{
- wire.OService,
- wire.Admin,
- },
- },
- },
- },
- {
- name: "Alert service",
- service: wire.Alert,
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceHostOnline,
- RequestID: wire.ReqIDFromServer,
- },
- Body: wire.SNAC_0x01_0x03_OServiceHostOnline{
- FoodGroups: []uint16{
- wire.Alert,
- wire.OService,
- },
- },
- },
- },
- {
- name: "BART service",
- service: wire.BART,
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceHostOnline,
- RequestID: wire.ReqIDFromServer,
- },
- Body: wire.SNAC_0x01_0x03_OServiceHostOnline{
- FoodGroups: []uint16{
- wire.BART,
- wire.OService,
- },
- },
- },
- },
- {
- name: "BOS service",
- service: wire.BOS,
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceHostOnline,
- RequestID: wire.ReqIDFromServer,
- },
- Body: wire.SNAC_0x01_0x03_OServiceHostOnline{
- FoodGroups: []uint16{
- wire.Alert,
- wire.BART,
- wire.Buddy,
- wire.Feedbag,
- wire.ICBM,
- wire.ICQ,
- wire.Locate,
- wire.OService,
- wire.PermitDeny,
- wire.UserLookup,
- wire.Invite,
- wire.Popup,
- wire.Stats,
- },
- },
- },
- },
- {
- name: "Chat service",
- service: wire.Chat,
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceHostOnline,
- RequestID: wire.ReqIDFromServer,
- },
- Body: wire.SNAC_0x01_0x03_OServiceHostOnline{
- FoodGroups: []uint16{
- wire.OService,
- wire.Chat,
- },
- },
- },
- },
- {
- name: "ChatNav service",
- service: wire.ChatNav,
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceHostOnline,
- RequestID: wire.ReqIDFromServer,
- },
- Body: wire.SNAC_0x01_0x03_OServiceHostOnline{
- FoodGroups: []uint16{
- wire.ChatNav,
- wire.OService,
- },
- },
- },
- },
- {
- name: "ODir service",
- service: wire.ODir,
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceHostOnline,
- RequestID: wire.ReqIDFromServer,
- },
- Body: wire.SNAC_0x01_0x03_OServiceHostOnline{
- FoodGroups: []uint16{
- wire.ODir,
- wire.OService,
- },
- },
- },
- },
- {
- name: "Oops, unsupported service",
- service: wire.Kerberos,
- expectOutput: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceErr,
- },
- },
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- svc := NewOServiceService(config.Config{}, nil, slog.Default(), nil, nil, nil, nil, nil, wire.DefaultSNACRateLimits(), nil, nil, nil, nil)
- have := svc.HostOnline(tc.service)
- assert.Equal(t, tc.expectOutput, have)
- })
- }
- }
- func TestOServiceService_ClientVersions(t *testing.T) {
- svc := OServiceService{
- cfg: config.Config{},
- logger: slog.Default(),
- }
- want := []wire.SNACMessage{
- {
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceHostVersions,
- RequestID: 1234,
- },
- Body: wire.SNAC_0x01_0x18_OServiceHostVersions{
- Versions: []uint16{5, 6, 7, 8},
- },
- },
- {
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceMotd,
- RequestID: wire.ReqIDFromServer,
- },
- Body: wire.SNAC_0x01_0x13_OServiceMOTD{
- MessageType: 0x0004,
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.OServiceTLVTagsMOTDMessage, "Welcome to Open OSCAR Server"),
- },
- },
- },
- },
- }
- instance := newTestInstance("me")
- have := svc.ClientVersions(context.Background(), instance, wire.SNACFrame{
- RequestID: 1234,
- }, wire.SNAC_0x01_0x17_OServiceClientVersions{
- Versions: []uint16{5, 6, 7, 8},
- })
- assert.Equal(t, want, have)
- }
- func TestNewOServiceUserInfoUpdate(t *testing.T) {
- memberSince := time.Unix(1_700_000_000, 0)
- profileUpdated := time.Unix(1_700_100_000, 0)
- signonTime := time.Now().Add(-3 * time.Second)
- t.Run("OService version < 4 without profile", func(t *testing.T) {
- session := newTestInstance("me",
- sessOptMemberSince(memberSince),
- sessOptSignonTime(signonTime))
- signon := session.SignonTime()
- onlineLowerBound := uint32(time.Since(signon).Seconds())
- got := newOServiceUserInfoUpdate(session)
- require.Len(t, got.UserInfo, 1)
- memberVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoMemberSince)
- require.True(t, ok)
- require.Equal(t, uint32(memberSince.Unix()), memberVal)
- signonVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoSignonTOD)
- require.True(t, ok)
- require.Equal(t, uint32(signon.Unix()), signonVal)
- onlineVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoOnlineTime)
- require.True(t, ok)
- require.GreaterOrEqual(t, onlineVal, onlineLowerBound)
- require.LessOrEqual(t, onlineVal-onlineLowerBound, uint32(2))
- hasSigTime := got.UserInfo[0].HasTag(wire.OServiceUserInfoSigTime)
- require.False(t, hasSigTime)
- require.False(t, got.UserInfo[0].HasTag(wire.OServiceUserInfoPrimaryInstance))
- })
- t.Run("includes profile update time when set", func(t *testing.T) {
- session := newTestInstance("me",
- sessOptMemberSince(memberSince),
- sessOptSignonTime(signonTime),
- sessOptProfile(state.UserProfile{UpdateTime: profileUpdated}),
- sessOptSetFoodGroupVersion(wire.OService, 4))
- signon := session.SignonTime()
- onlineLowerBound := uint32(time.Since(signon).Seconds())
- got := newOServiceUserInfoUpdate(session)
- require.Len(t, got.UserInfo, 2)
- memberVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoMemberSince)
- require.True(t, ok)
- require.Equal(t, uint32(memberSince.Unix()), memberVal)
- signonVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoSignonTOD)
- require.True(t, ok)
- require.Equal(t, uint32(signon.Unix()), signonVal)
- onlineVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoOnlineTime)
- require.True(t, ok)
- require.GreaterOrEqual(t, onlineVal, onlineLowerBound)
- require.LessOrEqual(t, onlineVal-onlineLowerBound, uint32(2))
- hasSigTime := got.UserInfo[0].HasTag(wire.OServiceUserInfoSigTime)
- require.False(t, hasSigTime)
- // Signature time is in the instance block, not UserInfo[0]
- hasSigTimeInstance := got.UserInfo[1].HasTag(wire.OServiceUserInfoSigTime)
- require.True(t, hasSigTimeInstance)
- sigVal, ok := got.UserInfo[1].Uint32BE(wire.OServiceUserInfoSigTime)
- require.True(t, ok)
- require.Equal(t, uint32(profileUpdated.Unix()), sigVal)
- require.False(t, got.UserInfo[0].HasTag(wire.OServiceUserInfoPrimaryInstance))
- })
- t.Run("appends additional instance info when food group version >= 4", func(t *testing.T) {
- session := newTestInstance("me",
- sessOptMemberSince(memberSince),
- sessOptSignonTime(signonTime),
- sessOptSetFoodGroupVersion(wire.OService, 4))
- signon := session.SignonTime()
- onlineLowerBound := uint32(time.Since(signon).Seconds())
- got := newOServiceUserInfoUpdate(session)
- require.Len(t, got.UserInfo, 2)
- memberVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoMemberSince)
- require.True(t, ok)
- require.Equal(t, uint32(memberSince.Unix()), memberVal)
- signonVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoSignonTOD)
- require.True(t, ok)
- require.Equal(t, uint32(signon.Unix()), signonVal)
- onlineVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoOnlineTime)
- require.True(t, ok)
- require.GreaterOrEqual(t, onlineVal, onlineLowerBound)
- require.LessOrEqual(t, onlineVal-onlineLowerBound, uint32(2))
- hasSigTime := got.UserInfo[0].HasTag(wire.OServiceUserInfoSigTime)
- require.False(t, hasSigTime)
- instanceBytes, ok := got.UserInfo[0].Bytes(wire.OServiceUserInfoMyInstanceNum)
- require.True(t, ok)
- require.Equal(t, []byte{0x01}, instanceBytes)
- primaryBytes, ok := got.UserInfo[1].Bytes(wire.OServiceUserInfoPrimaryInstance)
- require.True(t, ok)
- require.Equal(t, []byte{0x01}, primaryBytes)
- require.Equal(t, got.UserInfo[0].ScreenName, got.UserInfo[1].ScreenName)
- })
- t.Run("includes info for all instances when session has 2 instances", func(t *testing.T) {
- session := newTestInstance("me",
- sessOptMemberSince(memberSince),
- sessOptSignonTime(signonTime),
- sessOptSetFoodGroupVersion(wire.OService, 4))
- // Add a second instance to the session
- session.Session().AddInstance()
- signon := session.SignonTime()
- onlineLowerBound := uint32(time.Since(signon).Seconds())
- got := newOServiceUserInfoUpdate(session)
- require.Len(t, got.UserInfo, 3)
- memberVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoMemberSince)
- require.True(t, ok)
- require.Equal(t, uint32(memberSince.Unix()), memberVal)
- signonVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoSignonTOD)
- require.True(t, ok)
- require.Equal(t, uint32(signon.Unix()), signonVal)
- onlineVal, ok := got.UserInfo[0].Uint32BE(wire.OServiceUserInfoOnlineTime)
- require.True(t, ok)
- require.GreaterOrEqual(t, onlineVal, onlineLowerBound)
- require.LessOrEqual(t, onlineVal-onlineLowerBound, uint32(2))
- hasSigTime := got.UserInfo[0].HasTag(wire.OServiceUserInfoSigTime)
- require.False(t, hasSigTime)
- instanceBytes, ok := got.UserInfo[0].Bytes(wire.OServiceUserInfoMyInstanceNum)
- require.True(t, ok)
- require.Equal(t, []byte{0x01}, instanceBytes)
- // First instance block
- primary1Bytes, ok := got.UserInfo[1].Bytes(wire.OServiceUserInfoPrimaryInstance)
- require.True(t, ok)
- require.Equal(t, []byte{0x01}, primary1Bytes)
- require.Equal(t, got.UserInfo[0].ScreenName, got.UserInfo[1].ScreenName)
- // Second instance block
- primary2Bytes, ok := got.UserInfo[2].Bytes(wire.OServiceUserInfoPrimaryInstance)
- require.True(t, ok)
- require.Equal(t, []byte{0x02}, primary2Bytes)
- require.Equal(t, got.UserInfo[0].ScreenName, got.UserInfo[2].ScreenName)
- })
- t.Run("marks instance user flags unavailable when session is away", func(t *testing.T) {
- session := newTestInstance("me",
- sessOptUserInfoFlag(wire.OServiceUserFlagUnavailable),
- sessOptSetFoodGroupVersion(wire.OService, 4))
- got := newOServiceUserInfoUpdate(session)
- require.Len(t, got.UserInfo, 2)
- flags, ok := got.UserInfo[1].Uint16BE(wire.OServiceUserInfoUserFlags)
- require.True(t, ok)
- require.Equal(t, wire.OServiceUserFlagOSCARFree|wire.OServiceUserFlagUnavailable, flags)
- })
- t.Run("marks instance status invisible when current instance is invisible", func(t *testing.T) {
- session := newTestInstance("me",
- sessOptInvisible,
- sessOptSetFoodGroupVersion(wire.OService, 4))
- got := newOServiceUserInfoUpdate(session)
- require.Len(t, got.UserInfo, 2)
- status, ok := got.UserInfo[1].Uint32BE(wire.OServiceUserInfoStatus)
- require.True(t, ok)
- require.Equal(t, wire.OServiceUserStatusInvisible, status)
- })
- t.Run("adds buddy icon and profile sig time only for current instance", func(t *testing.T) {
- icon := wire.BARTID{
- Type: 1,
- BARTInfo: wire.BARTInfo{
- Flags: 1,
- Hash: []byte{0xAA, 0xBB, 0xCC},
- },
- }
- session := newTestInstance("me",
- sessOptSetFoodGroupVersion(wire.OService, 4),
- sessOptBuddyIcon(icon),
- sessOptProfile(state.UserProfile{UpdateTime: profileUpdated}))
- session.Session().AddInstance()
- got := newOServiceUserInfoUpdate(session)
- require.Len(t, got.UserInfo, 3)
- require.True(t, got.UserInfo[1].HasTag(wire.OServiceUserInfoBARTInfo))
- require.True(t, got.UserInfo[1].HasTag(wire.OServiceUserInfoSigTime))
- require.False(t, got.UserInfo[2].HasTag(wire.OServiceUserInfoBARTInfo))
- require.False(t, got.UserInfo[2].HasTag(wire.OServiceUserInfoSigTime))
- })
- t.Run("does not add buddy icon when icon type is zero", func(t *testing.T) {
- session := newTestInstance("me",
- sessOptSetFoodGroupVersion(wire.OService, 4),
- sessOptBuddyIcon(wire.BARTID{
- Type: 0,
- BARTInfo: wire.BARTInfo{
- Flags: 1,
- Hash: []byte{0x10, 0x20, 0x30},
- },
- }))
- got := newOServiceUserInfoUpdate(session)
- require.Len(t, got.UserInfo, 2)
- require.False(t, got.UserInfo[1].HasTag(wire.OServiceUserInfoBARTInfo))
- })
- }
- func TestOServiceService_UserInfoQuery(t *testing.T) {
- tests := []struct {
- name string
- instance *state.SessionInstance
- given wire.SNACMessage
- want wire.SNACMessage
- wantErr error
- }{
- {
- name: "happy path windows aim < 6",
- instance: newTestInstance("me"),
- given: wire.SNACMessage{
- Frame: wire.SNACFrame{RequestID: 1234},
- },
- want: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceUserInfoUpdate,
- RequestID: 1234,
- },
- Body: newOServiceUserInfoUpdate(newTestInstance("me")),
- },
- },
- {
- name: "happy path windows aim >= 6",
- instance: newTestInstance("me", sessOptSetFoodGroupVersion(wire.OService, 4)),
- given: wire.SNACMessage{
- Frame: wire.SNACFrame{RequestID: 1234},
- },
- want: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceUserInfoUpdate,
- RequestID: 1234,
- },
- Body: newOServiceUserInfoUpdate(newTestInstance("me", sessOptSetFoodGroupVersion(wire.OService, 4))),
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- svc := OServiceService{
- cfg: config.Config{},
- logger: slog.Default(),
- }
- have := svc.UserInfoQuery(context.Background(), tt.instance, tt.given.Frame)
- assert.Equal(t, tt.want, have)
- })
- }
- }
- func TestOServiceService_IdleNotification(t *testing.T) {
- tests := []struct {
- name string
- instance *state.SessionInstance
- bodyIn wire.SNAC_0x01_0x11_OServiceIdleNotification
- // mockParams is the list of params sent to mocks that satisfy this
- // method's dependencies
- mockParams mockParams
- wantErr error
- }{
- {
- name: "set idle from active",
- instance: newTestInstance("me"),
- bodyIn: wire.SNAC_0x01_0x11_OServiceIdleNotification{
- IdleTime: 90,
- },
- mockParams: mockParams{
- buddyBroadcasterParams: buddyBroadcasterParams{
- broadcastBuddyArrivedParams: broadcastBuddyArrivedParams{
- {
- screenName: state.DisplayScreenName("me"),
- },
- },
- },
- },
- },
- {
- name: "set active from idle",
- instance: newTestInstance("me", sessOptIdle(90*time.Second)),
- bodyIn: wire.SNAC_0x01_0x11_OServiceIdleNotification{
- IdleTime: 0,
- },
- mockParams: mockParams{
- buddyBroadcasterParams: buddyBroadcasterParams{
- broadcastBuddyArrivedParams: broadcastBuddyArrivedParams{
- {
- screenName: state.DisplayScreenName("me"),
- },
- },
- },
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- buddyUpdateBroadcaster := newMockbuddyBroadcaster(t)
- for _, params := range tt.mockParams.broadcastBuddyArrivedParams {
- buddyUpdateBroadcaster.EXPECT().
- BroadcastBuddyArrived(mock.Anything, state.NewIdentScreenName(params.screenName.String()), mock.MatchedBy(func(userInfo wire.TLVUserInfo) bool {
- return userInfo.ScreenName == params.screenName.String()
- })).
- Return(params.err)
- }
- svc := OServiceService{
- cfg: config.Config{},
- logger: slog.Default(),
- buddyBroadcaster: buddyUpdateBroadcaster,
- }
- haveErr := svc.IdleNotification(context.TODO(), tt.instance, tt.bodyIn)
- assert.ErrorIs(t, tt.wantErr, haveErr)
- })
- }
- }
- func TestOServiceService_ClientOnline(t *testing.T) {
- chatRoom := state.NewChatRoom("the-chat-room", state.NewIdentScreenName("creator"), state.PrivateExchange)
- chatter1 := newTestInstance("chatter-1", sessOptChatRoomCookie(chatRoom.Cookie()))
- chatter2 := newTestInstance("chatter-2", sessOptChatRoomCookie(chatRoom.Cookie()))
- tests := []struct {
- // name is the name of the test
- name string
- // joiningChatter is the session of the arriving user
- instance *state.SessionInstance
- // bodyIn is the SNAC body sent from the arriving user's client to the
- // server
- bodyIn wire.SNAC_0x01_0x02_OServiceClientOnline
- // service is the OSCAR service type
- service uint16
- // wantErr is the expected error from the handler
- wantErr error
- // mockParams is the list of params sent to mocks that satisfy this
- // method's dependencies
- mockParams mockParams
- // validateSess verifies the session state after the handler has run
- validateSess func(t *testing.T, instance *state.SessionInstance)
- // cfg is the config to use for this test. Zero value uses default config.Config{}
- cfg config.Config
- }{
- {
- name: "notify that BOS user is online",
- instance: newTestInstance("me", sessOptCannedSignonTime, sessOptContactsInit),
- bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
- service: wire.BOS,
- mockParams: mockParams{
- buddyBroadcasterParams: buddyBroadcasterParams{
- broadcastVisibilityParams: broadcastVisibilityParams{
- {
- from: state.NewIdentScreenName("me"),
- filter: nil,
- doSendDepartures: false,
- },
- },
- },
- messageRelayerParams: messageRelayerParams{
- relayToScreenNameParams: relayToScreenNameParams{
- {
- screenName: state.NewIdentScreenName("me"),
- message: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.Stats,
- SubGroup: wire.StatsSetMinReportInterval,
- RequestID: wire.ReqIDFromServer,
- },
- Body: wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{
- MinReportInterval: 1,
- },
- },
- },
- },
- },
- },
- validateSess: func(t *testing.T, instance *state.SessionInstance) {
- assert.True(t, instance.SignonComplete())
- },
- },
- {
- name: "ICQ Lite order: ClientOnline before feedbag use",
- instance: newTestInstance("me", sessOptCannedSignonTime),
- bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
- service: wire.BOS,
- mockParams: mockParams{
- messageRelayerParams: messageRelayerParams{
- relayToScreenNameParams: relayToScreenNameParams{
- {
- screenName: state.NewIdentScreenName("me"),
- message: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.Stats,
- SubGroup: wire.StatsSetMinReportInterval,
- RequestID: wire.ReqIDFromServer,
- },
- Body: wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{
- MinReportInterval: 1,
- },
- },
- },
- },
- },
- },
- validateSess: func(t *testing.T, instance *state.SessionInstance) {
- assert.True(t, instance.SignonComplete())
- assert.False(t, instance.ContactsInit())
- },
- },
- {
- name: "notify that BOS user is online via Kerberos auth, does not have stored profile",
- instance: newTestInstance("me", sessOptCannedSignonTime, sessOptKerberosAuth, sessOptContactsInit),
- bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
- service: wire.BOS,
- mockParams: mockParams{
- buddyBroadcasterParams: buddyBroadcasterParams{
- broadcastVisibilityParams: broadcastVisibilityParams{
- {
- from: state.NewIdentScreenName("me"),
- filter: nil,
- doSendDepartures: false,
- },
- },
- },
- messageRelayerParams: messageRelayerParams{
- relayToScreenNameParams: relayToScreenNameParams{
- {
- screenName: state.NewIdentScreenName("me"),
- message: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.Stats,
- SubGroup: wire.StatsSetMinReportInterval,
- RequestID: wire.ReqIDFromServer,
- },
- Body: wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{
- MinReportInterval: 1,
- },
- },
- },
- },
- },
- profileManagerParams: profileManagerParams{
- retrieveProfileParams: retrieveProfileParams{
- {
- screenName: state.NewIdentScreenName("me"),
- result: state.UserProfile{},
- },
- },
- },
- },
- validateSess: func(t *testing.T, instance *state.SessionInstance) {
- assert.True(t, instance.SignonComplete())
- assert.True(t, instance.Session().Profile().IsZero())
- },
- },
- {
- name: "notify that BOS user is online via Kerberos auth, has stored profile",
- instance: newTestInstance("me", sessOptCannedSignonTime, sessOptKerberosAuth, sessOptContactsInit),
- bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
- service: wire.BOS,
- mockParams: mockParams{
- buddyBroadcasterParams: buddyBroadcasterParams{
- broadcastVisibilityParams: broadcastVisibilityParams{
- {
- from: state.NewIdentScreenName("me"),
- filter: nil,
- doSendDepartures: false,
- },
- },
- },
- messageRelayerParams: messageRelayerParams{
- relayToScreenNameParams: relayToScreenNameParams{
- {
- screenName: state.NewIdentScreenName("me"),
- message: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.Stats,
- SubGroup: wire.StatsSetMinReportInterval,
- RequestID: wire.ReqIDFromServer,
- },
- Body: wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{
- MinReportInterval: 1,
- },
- },
- },
- },
- relayToSelfParams: relayToSelfParams{
- {
- screenName: state.NewIdentScreenName("me"),
- message: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.OService,
- SubGroup: wire.OServiceUserInfoUpdate,
- },
- Body: newOServiceUserInfoUpdate(newTestInstance("me", sessOptCannedSignonTime, sessOptProfile(
- state.UserProfile{
- ProfileText: "profile-result",
- MIMEType: `text/aolrtf; charset="us-ascii"`,
- UpdateTime: time.Unix(100000, 0),
- },
- ))),
- },
- },
- },
- },
- profileManagerParams: profileManagerParams{
- retrieveProfileParams: retrieveProfileParams{
- {
- screenName: state.NewIdentScreenName("me"),
- result: state.UserProfile{
- ProfileText: "profile-result",
- MIMEType: `text/aolrtf; charset="us-ascii"`,
- UpdateTime: time.Unix(100000, 0),
- },
- },
- },
- },
- },
- validateSess: func(t *testing.T, instance *state.SessionInstance) {
- assert.True(t, instance.SignonComplete())
- assert.False(t, instance.Session().Profile().IsZero(), "profile update time is non-zero")
- assert.Equal(t, "profile-result", instance.Session().Profile().ProfileText, "profile text matches")
- assert.Equal(t, `text/aolrtf; charset="us-ascii"`, instance.Session().Profile().MIMEType, "profile mimetype matches")
- },
- },
- {
- name: "notify that BOS user is online with 0 offline messages, no notification sent",
- instance: newTestInstance("me", sessOptCannedSignonTime, sessOptOfflineMsgCount(0), sessOptContactsInit),
- bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
- service: wire.BOS,
- mockParams: mockParams{
- buddyBroadcasterParams: buddyBroadcasterParams{
- broadcastVisibilityParams: broadcastVisibilityParams{
- {
- from: state.NewIdentScreenName("me"),
- filter: nil,
- doSendDepartures: false,
- },
- },
- },
- messageRelayerParams: messageRelayerParams{
- relayToScreenNameParams: relayToScreenNameParams{
- {
- screenName: state.NewIdentScreenName("me"),
- message: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.Stats,
- SubGroup: wire.StatsSetMinReportInterval,
- RequestID: wire.ReqIDFromServer,
- },
- Body: wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{
- MinReportInterval: 1,
- },
- },
- },
- },
- },
- },
- validateSess: func(t *testing.T, instance *state.SessionInstance) {
- assert.True(t, instance.SignonComplete())
- assert.Equal(t, 0, instance.OfflineMsgCount())
- },
- },
- {
- name: "notify that BOS user is online with offline messages, send notification and reset count",
- instance: newTestInstance("me", sessOptCannedSignonTime, sessOptOfflineMsgCount(3), sessOptContactsInit),
- bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
- service: wire.BOS,
- mockParams: mockParams{
- buddyBroadcasterParams: buddyBroadcasterParams{
- broadcastVisibilityParams: broadcastVisibilityParams{
- {
- from: state.NewIdentScreenName("me"),
- filter: nil,
- doSendDepartures: false,
- },
- },
- },
- messageRelayerParams: messageRelayerParams{
- relayToScreenNameParams: relayToScreenNameParams{
- {
- screenName: state.NewIdentScreenName("me"),
- message: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.Stats,
- SubGroup: wire.StatsSetMinReportInterval,
- RequestID: wire.ReqIDFromServer,
- },
- Body: wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{
- MinReportInterval: 1,
- },
- },
- },
- {
- screenName: state.NewIdentScreenName("me"),
- message: func() wire.SNACMessage {
- msg, err := systemMessage("You just received 3 IM(s) while you were offline. If you " +
- "do not wish to receive offline messages, please go to " +
- "<a href=\"https://www.youtube.com/watch?v=dQw4w9WgXcQ&list=RDdQw4w9WgXcQ&start_radio=1&pp=ygUJcmljayByb2xsoAcB\">IM Settings</a>.")
- require.NoError(t, err)
- return msg
- }(),
- },
- },
- },
- offlineMessageManagerParams: offlineMessageManagerParams{
- setOfflineMsgCountParams: setOfflineMsgCountParams{
- {
- screenName: state.NewIdentScreenName("me"),
- count: 0,
- err: nil,
- },
- },
- },
- },
- validateSess: func(t *testing.T, instance *state.SessionInstance) {
- assert.True(t, instance.SignonComplete())
- assert.Equal(t, 0, instance.OfflineMsgCount())
- },
- },
- {
- name: "ICQ user with offline messages, no offline message notification sent",
- instance: newTestInstance("100001", sessOptCannedSignonTime, sessOptOfflineMsgCount(3), sessOptContactsInit, sessOptUIN(100001)),
- bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
- service: wire.BOS,
- mockParams: mockParams{
- buddyBroadcasterParams: buddyBroadcasterParams{
- broadcastVisibilityParams: broadcastVisibilityParams{
- {
- from: state.NewIdentScreenName("100001"),
- filter: nil,
- doSendDepartures: false,
- },
- },
- },
- messageRelayerParams: messageRelayerParams{
- relayToScreenNameParams: relayToScreenNameParams{
- {
- screenName: state.NewIdentScreenName("100001"),
- message: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.Stats,
- SubGroup: wire.StatsSetMinReportInterval,
- RequestID: wire.ReqIDFromServer,
- },
- Body: wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{
- MinReportInterval: 1,
- },
- },
- },
- },
- },
- },
- validateSess: func(t *testing.T, instance *state.SessionInstance) {
- assert.True(t, instance.SignonComplete())
- assert.Equal(t, uint32(100001), instance.UIN())
- assert.Equal(t, 3, instance.OfflineMsgCount())
- },
- },
- {
- name: "notify that BOS user is logged in to multiple locations",
- instance: func() *state.SessionInstance {
- instance1 := newTestInstance("me")
- instance1.SetSignonComplete()
- instance2 := instance1.Session().AddInstance()
- instance2.SetSignonComplete()
- instance3 := instance1.Session().AddInstance()
- instance3.SetContactsInit()
- // instance 3 is not yet signed on
- return instance3
- }(),
- bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
- service: wire.BOS,
- mockParams: mockParams{
- buddyBroadcasterParams: buddyBroadcasterParams{
- broadcastVisibilityParams: broadcastVisibilityParams{
- {
- from: state.NewIdentScreenName("me"),
- filter: nil,
- doSendDepartures: false,
- },
- },
- },
- messageRelayerParams: messageRelayerParams{
- relayToScreenNameParams: relayToScreenNameParams{
- {
- screenName: state.NewIdentScreenName("me"),
- message: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.Stats,
- SubGroup: wire.StatsSetMinReportInterval,
- RequestID: wire.ReqIDFromServer,
- },
- Body: wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{
- MinReportInterval: 1,
- },
- },
- },
- },
- relayToOtherInstancesParams: relayToOtherInstancesParams{
- {
- screenName: state.NewIdentScreenName("me"),
- message: func() wire.SNACMessage {
- msg, err := systemMessage("Your screen name (me) is now signed into Open OSCAR Server in 3 locations. Click " +
- "<a href=\"https://www.youtube.com/watch?v=dQw4w9WgXcQ&list=RDdQw4w9WgXcQ&start_radio=1&pp=ygUJcmljayByb2xsoAcB\">here</a> " +
- "for more information.")
- require.NoError(t, err)
- return msg
- }(),
- },
- },
- },
- },
- validateSess: func(t *testing.T, instance *state.SessionInstance) {
- assert.True(t, instance.Session().Instance(3).SignonComplete())
- },
- },
- {
- name: "BOS user is logged in to multiple locations, DisableMultiLoginNotif is true, no notification sent",
- instance: func() *state.SessionInstance {
- instance1 := newTestInstance("me")
- instance1.SetSignonComplete()
- instance2 := instance1.Session().AddInstance()
- instance2.SetSignonComplete()
- instance3 := instance1.Session().AddInstance()
- instance3.SetContactsInit()
- // instance 3 is not yet signed on
- return instance3
- }(),
- bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
- service: wire.BOS,
- cfg: config.Config{
- DisableMultiLoginNotif: true,
- },
- mockParams: mockParams{
- buddyBroadcasterParams: buddyBroadcasterParams{
- broadcastVisibilityParams: broadcastVisibilityParams{
- {
- from: state.NewIdentScreenName("me"),
- filter: nil,
- doSendDepartures: false,
- },
- },
- },
- messageRelayerParams: messageRelayerParams{
- relayToScreenNameParams: relayToScreenNameParams{
- {
- screenName: state.NewIdentScreenName("me"),
- message: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.Stats,
- SubGroup: wire.StatsSetMinReportInterval,
- RequestID: wire.ReqIDFromServer,
- },
- Body: wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{
- MinReportInterval: 1,
- },
- },
- },
- },
- // relayToOtherInstancesParams is intentionally omitted - notification should not be sent
- },
- },
- validateSess: func(t *testing.T, instance *state.SessionInstance) {
- assert.True(t, instance.Session().Instance(3).SignonComplete())
- },
- },
- {
- name: "notify that BOS user is online with offline messages, SetOfflineMsgCount fails",
- instance: newTestInstance("me", sessOptCannedSignonTime, sessOptOfflineMsgCount(2), sessOptContactsInit),
- bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
- service: wire.BOS,
- wantErr: assert.AnError,
- mockParams: mockParams{
- buddyBroadcasterParams: buddyBroadcasterParams{
- broadcastVisibilityParams: broadcastVisibilityParams{
- {
- from: state.NewIdentScreenName("me"),
- filter: nil,
- doSendDepartures: false,
- },
- },
- },
- messageRelayerParams: messageRelayerParams{
- relayToScreenNameParams: relayToScreenNameParams{
- {
- screenName: state.NewIdentScreenName("me"),
- message: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.Stats,
- SubGroup: wire.StatsSetMinReportInterval,
- RequestID: wire.ReqIDFromServer,
- },
- Body: wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{
- MinReportInterval: 1,
- },
- },
- },
- },
- },
- offlineMessageManagerParams: offlineMessageManagerParams{
- setOfflineMsgCountParams: setOfflineMsgCountParams{
- {
- screenName: state.NewIdentScreenName("me"),
- count: 0,
- err: assert.AnError,
- },
- },
- },
- },
- validateSess: func(t *testing.T, instance *state.SessionInstance) {
- assert.True(t, instance.SignonComplete())
- assert.Equal(t, 2, instance.OfflineMsgCount())
- },
- },
- {
- name: "upon joining, send chat room metadata and participant list to joining user; alert arrival to existing participants",
- instance: chatter1,
- bodyIn: wire.SNAC_0x01_0x02_OServiceClientOnline{},
- service: wire.Chat,
- mockParams: mockParams{
- chatMessageRelayerParams: chatMessageRelayerParams{
- chatRelayToAllExceptParams: chatRelayToAllExceptParams{
- {
- screenName: state.NewIdentScreenName("chatter-1"),
- cookie: chatRoom.Cookie(),
- message: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.Chat,
- SubGroup: wire.ChatUsersJoined,
- },
- Body: wire.SNAC_0x0E_0x03_ChatUsersJoined{
- Users: []wire.TLVUserInfo{
- chatter1.Session().TLVUserInfo(),
- },
- },
- },
- },
- },
- chatAllSessionsParams: chatAllSessionsParams{
- {
- cookie: chatRoom.Cookie(),
- sessions: []*state.Session{
- chatter1.Session(),
- chatter2.Session(),
- },
- },
- },
- chatRelayToScreenNameParams: chatRelayToScreenNameParams{
- {
- cookie: chatRoom.Cookie(),
- screenName: chatter1.IdentScreenName(),
- message: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.Chat,
- SubGroup: wire.ChatRoomInfoUpdate,
- },
- Body: wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
- Exchange: chatRoom.Exchange(),
- Cookie: chatRoom.Cookie(),
- InstanceNumber: chatRoom.InstanceNumber(),
- DetailLevel: chatRoom.DetailLevel(),
- TLVBlock: wire.TLVBlock{
- TLVList: chatRoom.TLVList(),
- },
- },
- },
- },
- {
- cookie: chatRoom.Cookie(),
- screenName: chatter1.IdentScreenName(),
- message: wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.Chat,
- SubGroup: wire.ChatUsersJoined,
- },
- Body: wire.SNAC_0x0E_0x03_ChatUsersJoined{
- Users: []wire.TLVUserInfo{
- chatter1.Session().TLVUserInfo(),
- chatter2.Session().TLVUserInfo(),
- },
- },
- },
- },
- },
- },
- chatRoomRegistryParams: chatRoomRegistryParams{
- chatRoomByCookieParams: chatRoomByCookieParams{
- {
- cookie: chatRoom.Cookie(),
- room: chatRoom,
- },
- },
- },
- },
- validateSess: func(t *testing.T, instance *state.SessionInstance) {
- assert.True(t, instance.SignonComplete())
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- buddyUpdateBroadcaster := newMockbuddyBroadcaster(t)
- for _, params := range tt.mockParams.broadcastVisibilityParams {
- buddyUpdateBroadcaster.EXPECT().
- BroadcastVisibility(matchContext(), matchSession(params.from), params.filter, params.doSendDepartures).
- Return(params.err)
- }
- messageRelayer := newMockMessageRelayer(t)
- for _, params := range tt.mockParams.relayToScreenNameParams {
- messageRelayer.EXPECT().
- RelayToScreenName(matchContext(), params.screenName, params.message)
- }
- for _, params := range tt.mockParams.relayToSelfParams {
- messageRelayer.EXPECT().
- RelayToSelf(matchContext(), mock.Anything, mock.MatchedBy(func(msg wire.SNACMessage) bool {
- return msg.Frame.FoodGroup == params.message.Frame.FoodGroup &&
- msg.Frame.SubGroup == params.message.Frame.SubGroup
- }))
- }
- for _, params := range tt.mockParams.relayToOtherInstancesParams {
- messageRelayer.EXPECT().
- RelayToOtherInstances(matchContext(), matchSession(params.screenName), params.message)
- }
- chatRoomManager := newMockChatRoomRegistry(t)
- for _, params := range tt.mockParams.chatRoomByCookieParams {
- chatRoomManager.EXPECT().
- ChatRoomByCookie(context.Background(), params.cookie).
- Return(params.room, params.err)
- }
- chatMessageRelayer := newMockChatMessageRelayer(t)
- for _, params := range tt.mockParams.chatRelayToAllExceptParams {
- chatMessageRelayer.EXPECT().
- RelayToAllExcept(matchContext(), params.cookie, params.screenName, params.message)
- }
- for _, params := range tt.mockParams.chatAllSessionsParams {
- chatMessageRelayer.EXPECT().
- AllSessions(params.cookie).
- Return(params.sessions)
- }
- for _, params := range tt.mockParams.chatRelayToScreenNameParams {
- chatMessageRelayer.EXPECT().
- RelayToScreenName(matchContext(), params.cookie, params.screenName, params.message)
- }
- profileManager := newMockProfileManager(t)
- for _, params := range tt.mockParams.retrieveProfileParams {
- profileManager.EXPECT().
- Profile(matchContext(), params.screenName).
- Return(params.result, params.err)
- }
- offlineMessageManager := newMockOfflineMessageManager(t)
- for _, params := range tt.mockParams.setOfflineMsgCountParams {
- offlineMessageManager.EXPECT().
- SetOfflineMsgCount(matchContext(), params.screenName, params.count).
- Return(params.err)
- }
- svc := NewOServiceService(tt.cfg, messageRelayer, slog.Default(), nil, chatRoomManager, nil, nil, nil, wire.DefaultSNACRateLimits(), chatMessageRelayer, profileManager, offlineMessageManager, nil)
- svc.buddyBroadcaster = buddyUpdateBroadcaster
- haveErr := svc.ClientOnline(context.Background(), tt.service, tt.bodyIn, tt.instance)
- assert.ErrorIs(t, haveErr, tt.wantErr)
- tt.validateSess(t, tt.instance)
- })
- }
- }
- func TestOServiceService_SetPrivacyFlags(t *testing.T) {
- svc := OServiceService{
- cfg: config.Config{},
- logger: slog.Default(),
- }
- body := wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags{
- PrivacyFlags: wire.OServicePrivacyFlagMember | wire.OServicePrivacyFlagIdle,
- }
- svc.SetPrivacyFlags(context.Background(), body)
- }
- func TestOServiceService_MonitorRateLimits(t *testing.T) {
- now := time.Now()
- sess := state.NewSession()
- sess.SetRateClasses(now, wire.DefaultRateLimitClasses())
- inst1 := sess.AddInstance()
- inst2 := sess.AddInstance()
- classID := wire.RateLimitClassID(3)
- sess.SubscribeRateLimits([]wire.RateLimitClassID{classID})
- // A clock shared with the monitor goroutine so the test controls when
- // ObserveRateChanges sees recovery.
- var clockMu sync.Mutex
- clockNow := now
- setClock := func(v time.Time) {
- clockMu.Lock()
- defer clockMu.Unlock()
- clockNow = v
- }
- svc := OServiceService{
- logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
- rateLimitMonitorInterval: time.Millisecond,
- timeNow: func() time.Time {
- clockMu.Lock()
- defer clockMu.Unlock()
- return clockNow
- },
- }
- recv := func(inst *state.SessionInstance) wire.SNAC_0x01_0x0A_OServiceRateParamsChange {
- t.Helper()
- select {
- case msg := <-inst.ReceiveMessage():
- assert.Equal(t, wire.OServiceRateParamChange, msg.Frame.SubGroup)
- body, ok := msg.Body.(wire.SNAC_0x01_0x0A_OServiceRateParamsChange)
- if !ok {
- t.Fatalf("unexpected body type %T", msg.Body)
- }
- return body
- case <-time.After(2 * time.Second):
- t.Fatal("timed out waiting for a relayed rate limit SNAC")
- return wire.SNAC_0x01_0x0A_OServiceRateParamsChange{}
- }
- }
- // Drive the IM class into the limited state.
- driveTime := now
- var status wire.RateLimitStatus
- for i := 0; status != wire.RateLimitStatusLimited; i++ {
- if i > 100 {
- t.Fatal("class never reached the limited state")
- }
- driveTime = driveTime.Add(time.Millisecond)
- status = sess.EvaluateRateLimit(driveTime, classID)
- }
- setClock(driveTime)
- // Stop the monitor at test end by closing the account's session.
- t.Cleanup(func() {
- inst1.CloseInstance()
- inst2.CloseInstance()
- })
- go svc.MonitorRateLimits(context.Background(), sess)
- // The transition is observed once for the account and broadcast to every
- // instance — the multi-connection bug the monitor fixes.
- for _, inst := range []*state.SessionInstance{inst1, inst2} {
- body := recv(inst)
- assert.Equal(t, uint16(3), body.Code) // limited
- assert.Equal(t, uint16(classID), body.Rate.ID)
- }
- // After a long idle gap the moving average clears; both instances are told.
- setClock(driveTime.Add(time.Minute))
- for _, inst := range []*state.SessionInstance{inst1, inst2} {
- body := recv(inst)
- assert.Equal(t, uint16(4), body.Code) // clear
- }
- }
- // The monitor is started from a RunOnce block owned by whichever connection
- // signed on first. That connection departing must not take rate limit eventing
- // down with it.
- func TestOServiceService_MonitorRateLimits_outlivesTheInstanceThatStartedIt(t *testing.T) {
- now := time.Now()
- sess := state.NewSession()
- sess.SetRateClasses(now, wire.DefaultRateLimitClasses())
- inst1 := sess.AddInstance()
- classID := wire.RateLimitClassID(3)
- sess.SubscribeRateLimits([]wire.RateLimitClassID{classID})
- var clockMu sync.Mutex
- clockNow := now
- setClock := func(v time.Time) {
- clockMu.Lock()
- defer clockMu.Unlock()
- clockNow = v
- }
- svc := OServiceService{
- logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
- rateLimitMonitorInterval: time.Millisecond,
- timeNow: func() time.Time {
- clockMu.Lock()
- defer clockMu.Unlock()
- return clockNow
- },
- }
- // The first instance on the account starts the monitor, as RunOnce does.
- go svc.MonitorRateLimits(context.Background(), sess)
- // A second connection joins, then the one that started the monitor departs.
- inst2 := sess.AddInstance()
- t.Cleanup(inst2.CloseInstance)
- inst1.CloseInstance()
- require.False(t, sess.IsClosed(), "the account is still online via inst2")
- recv := func() wire.SNAC_0x01_0x0A_OServiceRateParamsChange {
- t.Helper()
- select {
- case msg := <-inst2.ReceiveMessage():
- assert.Equal(t, wire.OServiceRateParamChange, msg.Frame.SubGroup)
- body, ok := msg.Body.(wire.SNAC_0x01_0x0A_OServiceRateParamsChange)
- if !ok {
- t.Fatalf("unexpected body type %T", msg.Body)
- }
- return body
- case <-time.After(2 * time.Second):
- t.Fatal("surviving instance got no rate limit update after the starting instance left")
- return wire.SNAC_0x01_0x0A_OServiceRateParamsChange{}
- }
- }
- driveTime := now
- var status wire.RateLimitStatus
- for i := 0; status != wire.RateLimitStatusLimited; i++ {
- if i > 100 {
- t.Fatal("class never reached the limited state")
- }
- driveTime = driveTime.Add(time.Millisecond)
- status = sess.EvaluateRateLimit(driveTime, classID)
- }
- setClock(driveTime)
- body := recv()
- assert.Equal(t, uint16(3), body.Code) // limited
- assert.Equal(t, uint16(classID), body.Rate.ID)
- // Recovery reaches the surviving instance too.
- setClock(driveTime.Add(time.Minute))
- assert.Equal(t, uint16(4), recv().Code) // clear
- }
- // The monitor's lifetime tracks the account, not the connection that started it:
- // the WebAPI server runs it with a server-lifetime context, so closing the
- // session is the only thing that stops it once the account has signed off.
- func TestOServiceService_MonitorRateLimits_exitsWhenAccountEmpty(t *testing.T) {
- sess := state.NewSession()
- sess.SetRateClasses(time.Now(), wire.DefaultRateLimitClasses())
- inst := sess.AddInstance()
- svc := OServiceService{
- logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
- rateLimitMonitorInterval: time.Millisecond,
- timeNow: time.Now,
- }
- done := make(chan struct{})
- go func() {
- // A context that is never cancelled — like the WebAPI server's
- // shutdownCtx before shutdown — so only Session.Closed() can stop it.
- svc.MonitorRateLimits(context.Background(), sess)
- close(done)
- }()
- // While an instance is present, the monitor keeps running.
- select {
- case <-done:
- t.Fatal("monitor exited while an instance was still present")
- case <-time.After(20 * time.Millisecond):
- }
- // Closing the account's last instance closes the session; the monitor exits.
- inst.CloseInstance()
- select {
- case <-done:
- case <-time.After(2 * time.Second):
- t.Fatal("monitor did not exit after the account's last instance left")
- }
- }
- func TestOServiceService_RateParamsSubAdd(t *testing.T) {
- svc := OServiceService{
- logger: slog.New(slog.NewTextHandler(io.Discard, nil)), // silence logs
- }
- classes := [5]wire.RateClass{
- {
- ID: 1,
- WindowSize: 80,
- ClearLevel: 2500,
- AlertLevel: 2000,
- LimitLevel: 1500,
- DisconnectLevel: 800,
- MaxLevel: 6000,
- },
- {
- ID: 2,
- WindowSize: 80,
- ClearLevel: 3000,
- AlertLevel: 2000,
- LimitLevel: 1500,
- DisconnectLevel: 1000,
- MaxLevel: 6000,
- },
- {
- ID: 3,
- WindowSize: 20,
- ClearLevel: 5100,
- AlertLevel: 5000,
- LimitLevel: 4000,
- DisconnectLevel: 3000,
- MaxLevel: 6000,
- },
- {
- ID: 4,
- WindowSize: 20,
- ClearLevel: 5500,
- AlertLevel: 5300,
- LimitLevel: 4200,
- DisconnectLevel: 3000,
- MaxLevel: 8000,
- },
- {
- ID: 5,
- WindowSize: 10,
- ClearLevel: 5500,
- AlertLevel: 5300,
- LimitLevel: 4200,
- DisconnectLevel: 3000,
- MaxLevel: 8000,
- },
- }
- t.Run("happy path", func(t *testing.T) {
- classes := classes
- instance := newTestInstance("me")
- instance.Session().SetRateClasses(time.Now(), wire.NewRateLimitClasses(classes))
- deltas, _ := instance.Session().ObserveRateChanges(time.Now())
- assert.Len(t, deltas, 0)
- // expect 3 rate limit class changes
- classes[0].MaxLevel = 8888
- classes[1].MaxLevel = 8888
- classes[2].MaxLevel = 8888
- classes[3].MaxLevel = 8888
- classes[4].MaxLevel = 8888
- instance.Session().SetRateClasses(time.Now(), wire.NewRateLimitClasses(classes))
- snac := wire.SNAC_0x01_0x08_OServiceRateParamsSubAdd{
- ClassIDs: []uint16{2, 5},
- }
- svc.RateParamsSubAdd(context.Background(), instance, snac)
- deltas, _ = instance.Session().ObserveRateChanges(time.Now())
- assert.Len(t, deltas, 2)
- // expect 5 rate limit class changes
- classes[0].MaxLevel = 9999
- classes[1].MaxLevel = 9999
- classes[2].MaxLevel = 9999
- classes[3].MaxLevel = 9999
- classes[4].MaxLevel = 9999
- instance.Session().SetRateClasses(time.Now(), wire.NewRateLimitClasses(classes))
- snac = wire.SNAC_0x01_0x08_OServiceRateParamsSubAdd{
- ClassIDs: []uint16{1, 3, 4},
- }
- svc.RateParamsSubAdd(context.Background(), instance, snac)
- deltas, _ = instance.Session().ObserveRateChanges(time.Now())
- assert.Len(t, deltas, 5)
- })
- t.Run("empty subscribe list", func(t *testing.T) {
- classes := classes
- instance := newTestInstance("me")
- instance.Session().SetRateClasses(time.Now(), wire.NewRateLimitClasses(classes))
- deltas, _ := instance.Session().ObserveRateChanges(time.Now())
- assert.Len(t, deltas, 0)
- // expect 3 rate limit class changes
- classes[0].MaxLevel = 8888
- classes[1].MaxLevel = 8888
- classes[2].MaxLevel = 8888
- classes[3].MaxLevel = 8888
- classes[4].MaxLevel = 8888
- instance.Session().SetRateClasses(time.Now(), wire.NewRateLimitClasses(classes))
- snac := wire.SNAC_0x01_0x08_OServiceRateParamsSubAdd{
- ClassIDs: []uint16{},
- }
- svc.RateParamsSubAdd(context.Background(), instance, snac)
- deltas, _ = instance.Session().ObserveRateChanges(time.Now())
- assert.Empty(t, deltas)
- })
- t.Run("class IDs out of range", func(t *testing.T) {
- classes := classes
- instance := newTestInstance("me")
- instance.Session().SetRateClasses(time.Now(), wire.NewRateLimitClasses(classes))
- deltas, _ := instance.Session().ObserveRateChanges(time.Now())
- assert.Len(t, deltas, 0)
- // expect 3 rate limit class changes
- classes[0].MaxLevel = 8888
- classes[1].MaxLevel = 8888
- classes[2].MaxLevel = 8888
- classes[3].MaxLevel = 8888
- classes[4].MaxLevel = 8888
- instance.Session().SetRateClasses(time.Now(), wire.NewRateLimitClasses(classes))
- snac := wire.SNAC_0x01_0x08_OServiceRateParamsSubAdd{
- ClassIDs: []uint16{0, 6},
- }
- svc.RateParamsSubAdd(context.Background(), instance, snac)
- deltas, _ = instance.Session().ObserveRateChanges(time.Now())
- assert.Empty(t, deltas)
- })
- }
|