4
0

server_test.go 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308
  1. package oscar
  2. import (
  3. "bufio"
  4. "bytes"
  5. "context"
  6. "io"
  7. "log/slog"
  8. "net"
  9. "strings"
  10. "sync"
  11. "testing"
  12. "time"
  13. "github.com/stretchr/testify/assert"
  14. "github.com/stretchr/testify/mock"
  15. "golang.org/x/time/rate"
  16. "github.com/mk6i/open-oscar-server/config"
  17. "github.com/mk6i/open-oscar-server/state"
  18. "github.com/mk6i/open-oscar-server/wire"
  19. )
  20. // noopRateLimitUpdater satisfies RateLimitUpdater without running the monitor.
  21. type noopRateLimitUpdater struct{}
  22. func (noopRateLimitUpdater) MonitorRateLimits(context.Context, *state.Session) {}
  23. // recordingRateLimitUpdater reports which sessions the monitor was started for.
  24. type recordingRateLimitUpdater struct {
  25. monitored chan *state.Session
  26. }
  27. func newRecordingRateLimitUpdater() recordingRateLimitUpdater {
  28. return recordingRateLimitUpdater{monitored: make(chan *state.Session, 1)}
  29. }
  30. func (r recordingRateLimitUpdater) MonitorRateLimits(_ context.Context, session *state.Session) {
  31. select {
  32. case r.monitored <- session:
  33. default:
  34. }
  35. }
  36. func TestServer_ListenAndServeAndShutdown(t *testing.T) {
  37. var mu sync.Mutex
  38. var received []string
  39. var msgWg sync.WaitGroup
  40. // the second group terminates SSL, so it binds :15001 and :15002
  41. groups := []config.ListenerGroup{
  42. {
  43. BOSListenAddress: ":15000",
  44. BOSAdvertisedHostPlain: "localhost",
  45. },
  46. {
  47. BOSListenAddress: ":15001",
  48. BOSListenAddressSSL: ":15002",
  49. BOSAdvertisedHostPlain: "localhost",
  50. BOSAdvertisedHostSSL: "localhost",
  51. },
  52. }
  53. var endpoints []config.Endpoint
  54. for _, g := range groups {
  55. endpoints = append(endpoints, g.Endpoints()...)
  56. }
  57. responses := []string{"hello1", "hello2", "hello3"}
  58. server := NewServer(
  59. nil,
  60. nil,
  61. nil,
  62. nil,
  63. slog.Default(),
  64. nil,
  65. nil,
  66. nil,
  67. wire.DefaultSNACRateLimits(),
  68. nil,
  69. groups,
  70. func(ctx context.Context, instance *state.SessionInstance) error { return nil },
  71. func(ctx context.Context, instance *state.SessionInstance) {},
  72. )
  73. server.handler = func(ctx context.Context, conn net.Conn, endpointCfg config.Endpoint) error {
  74. go func() {
  75. <-ctx.Done()
  76. _ = conn.Close()
  77. }()
  78. for {
  79. r := bufio.NewReader(conn)
  80. line, err := r.ReadString('\n')
  81. if err != nil {
  82. break
  83. }
  84. mu.Lock()
  85. received = append(received, strings.TrimSpace(line))
  86. mu.Unlock()
  87. msgWg.Done()
  88. }
  89. return nil
  90. }
  91. server.shutdownCtx, server.shutdownCancel = context.WithCancel(context.Background())
  92. shutdownCh := make(chan struct{})
  93. go func() {
  94. defer close(shutdownCh)
  95. assert.NoError(t, server.ListenAndServe())
  96. }()
  97. // Wait for server to be ready by checking if ports are listening
  98. for i := 0; i < len(endpoints); i++ {
  99. maxRetries := 10
  100. backoff := 5 * time.Millisecond
  101. for attempt := 0; attempt < maxRetries; attempt++ {
  102. conn, err := net.Dial("tcp", "localhost"+endpoints[i].ListenAddress)
  103. if err == nil {
  104. _ = conn.Close()
  105. break
  106. }
  107. if attempt == maxRetries-1 {
  108. t.Fatalf("Server not ready after %d attempts: %v", maxRetries, err)
  109. }
  110. time.Sleep(backoff)
  111. backoff *= 2
  112. }
  113. }
  114. for i := 0; i < len(endpoints); i++ {
  115. msgWg.Add(1)
  116. // Connect and send message
  117. conn, err := net.Dial("tcp", "localhost"+endpoints[i].ListenAddress)
  118. assert.NoError(t, err)
  119. _, err = conn.Write([]byte(responses[i] + "\n"))
  120. assert.NoError(t, err)
  121. }
  122. msgWg.Wait()
  123. // Shutdown
  124. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  125. defer cancel()
  126. err := server.Shutdown(ctx)
  127. assert.NoError(t, err)
  128. <-shutdownCh
  129. // Check what was received
  130. mu.Lock()
  131. defer mu.Unlock()
  132. assert.ElementsMatch(t, received, responses)
  133. }
  134. type fakeConn struct {
  135. net.Conn // embed the real connection
  136. local net.Addr
  137. remote net.Addr
  138. }
  139. func (f fakeConn) RemoteAddr() net.Addr { return f.remote }
  140. func TestOscarServer_RouteConnection_Auth_BUCP(t *testing.T) {
  141. serverConn, clientConn := net.Pipe()
  142. addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080")
  143. assert.NoError(t, err)
  144. clientFake := fakeConn{
  145. Conn: serverConn,
  146. local: addr,
  147. remote: addr,
  148. }
  149. go func() {
  150. defer func() {
  151. _ = clientConn.Close()
  152. }()
  153. // < receive FLAPSignonFrame
  154. flap := wire.FLAPFrame{}
  155. assert.NoError(t, wire.UnmarshalBE(&flap, clientConn))
  156. flapSignonFrame := wire.FLAPSignonFrame{}
  157. assert.NoError(t, wire.UnmarshalBE(&flapSignonFrame, bytes.NewBuffer(flap.Payload)))
  158. // > send FLAPSignonFrame
  159. flapSignonFrame = wire.FLAPSignonFrame{
  160. FLAPVersion: 1,
  161. }
  162. buf := &bytes.Buffer{}
  163. assert.NoError(t, wire.MarshalBE(flapSignonFrame, buf))
  164. flap = wire.FLAPFrame{
  165. StartMarker: 42,
  166. FrameType: wire.FLAPFrameSignon,
  167. Payload: buf.Bytes(),
  168. }
  169. assert.NoError(t, wire.MarshalBE(flap, clientConn))
  170. // > send SNAC_0x17_0x06_BUCPChallengeRequest
  171. flapc := wire.NewFlapClient(0, clientConn, clientConn)
  172. frame := wire.SNACFrame{
  173. FoodGroup: wire.BUCP,
  174. SubGroup: wire.BUCPChallengeRequest,
  175. RequestID: 4,
  176. }
  177. bodyIn := wire.SNAC_0x17_0x06_BUCPChallengeRequest{}
  178. assert.NoError(t, flapc.SendSNAC(frame, bodyIn))
  179. // < receive SNAC_0x17_0x07_BUCPChallengeResponse
  180. frame = wire.SNACFrame{}
  181. assert.NoError(t, flapc.ReceiveSNAC(&frame, &wire.SNAC_0x17_0x07_BUCPChallengeResponse{}))
  182. assert.Equal(t, wire.SNACFrame{FoodGroup: wire.BUCP, SubGroup: wire.BUCPChallengeResponse, RequestID: 4}, frame)
  183. // > send keep alive frame (like BSFlite does mid-login)
  184. assert.NoError(t, flapc.SendKeepAliveFrame())
  185. // > send SNAC_0x17_0x02_BUCPLoginRequest
  186. frame = wire.SNACFrame{
  187. FoodGroup: wire.BUCP,
  188. SubGroup: wire.BUCPLoginRequest,
  189. RequestID: 5,
  190. }
  191. assert.NoError(t, flapc.SendSNAC(frame, wire.SNAC_0x17_0x02_BUCPLoginRequest{}))
  192. // < receive SNAC_0x17_0x03_BUCPLoginResponse
  193. frame = wire.SNACFrame{}
  194. assert.NoError(t, flapc.ReceiveSNAC(&frame, &wire.SNAC_0x17_0x03_BUCPLoginResponse{}))
  195. assert.Equal(t, wire.SNACFrame{FoodGroup: wire.BUCP, SubGroup: wire.BUCPLoginResponse, RequestID: 5}, frame)
  196. // < receive FLAPSignoffFrame (server sends this after SNAC for Kopete compatibility)
  197. flap = wire.FLAPFrame{}
  198. assert.NoError(t, wire.UnmarshalBE(&flap, clientConn))
  199. assert.Equal(t, wire.FLAPFrameSignoff, flap.FrameType)
  200. }()
  201. wg := &sync.WaitGroup{}
  202. authService := newMockAuthService(t)
  203. authService.EXPECT().
  204. BUCPChallenge(matchContext(), mock.Anything, mock.Anything).
  205. Return(wire.SNACMessage{
  206. Frame: wire.SNACFrame{
  207. FoodGroup: wire.BUCP,
  208. SubGroup: wire.BUCPChallengeResponse,
  209. },
  210. Body: wire.SNAC_0x17_0x07_BUCPChallengeResponse{},
  211. }, nil)
  212. authService.EXPECT().
  213. BUCPLogin(matchContext(), mock.Anything, config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "localhost:5190"}}).
  214. Return(wire.SNACMessage{
  215. Frame: wire.SNACFrame{
  216. FoodGroup: wire.BUCP,
  217. SubGroup: wire.BUCPLoginResponse,
  218. },
  219. Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{},
  220. }, nil)
  221. rt := oscarServer{
  222. rateLimitUpdater: noopRateLimitUpdater{},
  223. authService: authService,
  224. logger: slog.Default(),
  225. ipRateLimiter: NewIPRateLimiter(rate.Every(1*time.Minute), 10, 1*time.Minute),
  226. }
  227. assert.NoError(t, rt.routeConnection(context.Background(), clientFake, config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "localhost:5190"}}))
  228. wg.Wait()
  229. }
  230. func TestOscarServer_RouteConnection_Auth_FLAP(t *testing.T) {
  231. serverConn, clientConn := net.Pipe()
  232. addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080")
  233. assert.NoError(t, err)
  234. clientFake := fakeConn{
  235. Conn: serverConn,
  236. local: addr,
  237. remote: addr,
  238. }
  239. go func() {
  240. defer func() {
  241. _ = clientConn.Close()
  242. }()
  243. // < receive FLAPSignonFrame
  244. flap := wire.FLAPFrame{}
  245. assert.NoError(t, wire.UnmarshalBE(&flap, clientConn))
  246. flapSignonFrame := wire.FLAPSignonFrame{}
  247. assert.NoError(t, wire.UnmarshalBE(&flapSignonFrame, bytes.NewBuffer(flap.Payload)))
  248. // > send FLAPSignonFrame with screen name TLV (indicates FLAP auth)
  249. flapSignonFrame = wire.FLAPSignonFrame{
  250. FLAPVersion: 1,
  251. }
  252. // Add screen name TLV to indicate FLAP authentication
  253. flapSignonFrame.Append(wire.NewTLVBE(wire.LoginTLVTagsScreenName, "testuser"))
  254. // Add password hash TLV for authentication
  255. flapSignonFrame.Append(wire.NewTLVBE(wire.LoginTLVTagsPasswordHash, []byte("password_hash")))
  256. // Add client identity TLV
  257. flapSignonFrame.Append(wire.NewTLVBE(wire.LoginTLVTagsClientIdentity, "ICQ 2000b"))
  258. buf := &bytes.Buffer{}
  259. assert.NoError(t, wire.MarshalBE(flapSignonFrame, buf))
  260. flap = wire.FLAPFrame{
  261. StartMarker: 42,
  262. FrameType: wire.FLAPFrameSignon,
  263. Payload: buf.Bytes(),
  264. }
  265. assert.NoError(t, wire.MarshalBE(flap, clientConn))
  266. // < receive FLAPSignoffFrame with authentication result
  267. flap = wire.FLAPFrame{}
  268. assert.NoError(t, wire.UnmarshalBE(&flap, clientConn))
  269. assert.Equal(t, wire.FLAPFrameSignoff, flap.FrameType)
  270. // Parse the signoff frame payload to verify authentication response
  271. signoffTLVs := wire.TLVRestBlock{}
  272. assert.NoError(t, wire.UnmarshalBE(&signoffTLVs, bytes.NewBuffer(flap.Payload)))
  273. }()
  274. wg := &sync.WaitGroup{}
  275. authService := newMockAuthService(t)
  276. authService.EXPECT().
  277. FLAPLogin(matchContext(), mock.Anything, config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "localhost:5190"}}).
  278. Return(wire.TLVRestBlock{
  279. TLVList: []wire.TLV{
  280. wire.NewTLVBE(wire.LoginTLVTagsScreenName, "testuser"),
  281. wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "localhost:5190"),
  282. wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("auth-cookie")),
  283. },
  284. }, nil)
  285. rt := oscarServer{
  286. rateLimitUpdater: noopRateLimitUpdater{},
  287. authService: authService,
  288. logger: slog.Default(),
  289. ipRateLimiter: NewIPRateLimiter(rate.Every(1*time.Minute), 10, 1*time.Minute),
  290. }
  291. assert.NoError(t, rt.routeConnection(context.Background(), clientFake, config.Endpoint{Group: config.ListenerGroup{BOSAdvertisedHostPlain: "localhost:5190"}}))
  292. wg.Wait()
  293. }
  294. func TestOscarServer_RouteConnection_BOS(t *testing.T) {
  295. instance := state.NewSession().AddInstance()
  296. clientConn, serverConn := net.Pipe()
  297. addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080")
  298. assert.NoError(t, err)
  299. clientFake := fakeConn{
  300. Conn: serverConn,
  301. local: addr,
  302. remote: addr,
  303. }
  304. go func() {
  305. // < receive FLAPSignonFrame
  306. flap := wire.FLAPFrame{}
  307. assert.NoError(t, wire.UnmarshalBE(&flap, clientConn))
  308. flapSignonFrame := wire.FLAPSignonFrame{}
  309. assert.NoError(t, wire.UnmarshalBE(&flapSignonFrame, bytes.NewBuffer(flap.Payload)))
  310. // > send FLAPSignonFrame
  311. flapSignonFrame = wire.FLAPSignonFrame{
  312. FLAPVersion: 1,
  313. }
  314. flapSignonFrame.Append(wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")))
  315. buf := &bytes.Buffer{}
  316. assert.NoError(t, wire.MarshalBE(flapSignonFrame, buf))
  317. flap = wire.FLAPFrame{
  318. StartMarker: 42,
  319. FrameType: wire.FLAPFrameSignon,
  320. Payload: buf.Bytes(),
  321. }
  322. assert.NoError(t, wire.MarshalBE(flap, clientConn))
  323. flapc := wire.NewFlapClient(0, clientConn, clientConn)
  324. // < receive SNAC_0x01_0x03_OServiceHostOnline
  325. frame := wire.SNACFrame{}
  326. body := wire.SNAC_0x01_0x03_OServiceHostOnline{}
  327. assert.NoError(t, flapc.ReceiveSNAC(&frame, &body))
  328. // send the first request that should get relayed to BOSRouter.Handle
  329. frame = wire.SNACFrame{
  330. FoodGroup: wire.OService,
  331. SubGroup: wire.OServiceClientOnline,
  332. }
  333. assert.NoError(t, flapc.SendSNAC(frame, struct{}{}))
  334. }()
  335. wg := &sync.WaitGroup{}
  336. authService := newMockAuthService(t)
  337. authService.EXPECT().
  338. RegisterBOSSession(mock.Anything, state.ServerCookie{Service: wire.BOS}, mock.Anything).
  339. Run(func(ctx context.Context, cookie state.ServerCookie, conf func(*state.Session)) {
  340. if conf != nil {
  341. conf(instance.Session())
  342. }
  343. }).
  344. Return(instance, nil)
  345. wg.Add(1)
  346. authService.EXPECT().
  347. Signout(mock.Anything, instance.Session()).
  348. Run(func(ctx context.Context, s *state.Session) {
  349. defer wg.Done()
  350. })
  351. authService.EXPECT().
  352. CrackCookie(mock.Anything).
  353. Return(state.ServerCookie{Service: wire.BOS}, time.Now().Add(time.Minute), nil)
  354. onlineNotifier := newMockOnlineNotifier(t)
  355. onlineNotifier.EXPECT().
  356. HostOnline(mock.Anything).
  357. Return(wire.SNACMessage{
  358. Frame: wire.SNACFrame{
  359. FoodGroup: wire.OService,
  360. SubGroup: wire.OServiceHostOnline,
  361. },
  362. Body: wire.SNAC_0x01_0x03_OServiceHostOnline{},
  363. })
  364. buddyListRegistry := newMockBuddyListRegistry(t)
  365. buddyListRegistry.EXPECT().
  366. RegisterBuddyList(mock.Anything, mock.Anything).
  367. Return(nil)
  368. buddyListRegistry.EXPECT().
  369. UnregisterBuddyList(mock.Anything, mock.Anything).
  370. Return(nil)
  371. departureNotifier := newMockDepartureNotifier(t)
  372. departureNotifier.EXPECT().
  373. BroadcastBuddyDeparted(mock.Anything, mock.Anything).
  374. Return(nil)
  375. chatSessionManager := newMockChatSessionManager(t)
  376. chatSessionManager.EXPECT().
  377. RemoveUserFromAllChats(mock.Anything)
  378. wg.Add(2)
  379. handler := func(ctx context.Context, serverType uint16, instance *state.SessionInstance, inFrame wire.SNACFrame, r io.Reader, rw ResponseWriter, endpointCfg config.Endpoint) error {
  380. defer wg.Done()
  381. assert.NoError(t, clientConn.Close())
  382. return nil
  383. }
  384. rt := oscarServer{
  385. rateLimitUpdater: noopRateLimitUpdater{},
  386. authService: authService,
  387. snacHandler: handler,
  388. logger: slog.Default(),
  389. onlineNotifier: onlineNotifier,
  390. buddyListRegistry: buddyListRegistry,
  391. chatSessionManager: chatSessionManager,
  392. departureNotifier: departureNotifier,
  393. recalcWarning: func(ctx context.Context, instance *state.SessionInstance) error {
  394. return nil
  395. },
  396. lowerWarnLevel: func(ctx context.Context, instance *state.SessionInstance) {
  397. defer wg.Done()
  398. },
  399. }
  400. assert.NoError(t, rt.routeConnection(context.Background(), clientFake, config.Endpoint{}))
  401. wg.Wait()
  402. }
  403. // Set up a multi-instance session and connect as the second instance.
  404. // - Ensure that disconnecting second instance does not sign out the session.
  405. func TestOscarServer_RouteConnection_BOS_MultiSessionSignoff(t *testing.T) {
  406. instance := state.NewSession().AddInstance()
  407. instance.Session().AddInstance()
  408. clientConn, serverConn := net.Pipe()
  409. addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080")
  410. assert.NoError(t, err)
  411. clientFake := fakeConn{
  412. Conn: serverConn,
  413. local: addr,
  414. remote: addr,
  415. }
  416. go func() {
  417. // < receive FLAPSignonFrame
  418. flap := wire.FLAPFrame{}
  419. assert.NoError(t, wire.UnmarshalBE(&flap, clientConn))
  420. flapSignonFrame := wire.FLAPSignonFrame{}
  421. assert.NoError(t, wire.UnmarshalBE(&flapSignonFrame, bytes.NewBuffer(flap.Payload)))
  422. // > send FLAPSignonFrame
  423. flapSignonFrame = wire.FLAPSignonFrame{
  424. FLAPVersion: 1,
  425. }
  426. flapSignonFrame.Append(wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")))
  427. buf := &bytes.Buffer{}
  428. assert.NoError(t, wire.MarshalBE(flapSignonFrame, buf))
  429. flap = wire.FLAPFrame{
  430. StartMarker: 42,
  431. FrameType: wire.FLAPFrameSignon,
  432. Payload: buf.Bytes(),
  433. }
  434. assert.NoError(t, wire.MarshalBE(flap, clientConn))
  435. flapc := wire.NewFlapClient(0, clientConn, clientConn)
  436. // < receive SNAC_0x01_0x03_OServiceHostOnline
  437. frame := wire.SNACFrame{}
  438. body := wire.SNAC_0x01_0x03_OServiceHostOnline{}
  439. assert.NoError(t, flapc.ReceiveSNAC(&frame, &body))
  440. // send the first request that should get relayed to BOSRouter.Handle
  441. frame = wire.SNACFrame{
  442. FoodGroup: wire.OService,
  443. SubGroup: wire.OServiceClientOnline,
  444. }
  445. assert.NoError(t, flapc.SendSNAC(frame, struct{}{}))
  446. }()
  447. wg := &sync.WaitGroup{}
  448. authService := newMockAuthService(t)
  449. authService.EXPECT().
  450. RegisterBOSSession(mock.Anything, state.ServerCookie{Service: wire.BOS}, mock.Anything).
  451. Return(instance, nil)
  452. authService.EXPECT().
  453. CrackCookie(mock.Anything).
  454. Return(state.ServerCookie{Service: wire.BOS}, time.Now().Add(time.Minute), nil)
  455. onlineNotifier := newMockOnlineNotifier(t)
  456. onlineNotifier.EXPECT().
  457. HostOnline(mock.Anything).
  458. Return(wire.SNACMessage{
  459. Frame: wire.SNACFrame{
  460. FoodGroup: wire.OService,
  461. SubGroup: wire.OServiceHostOnline,
  462. },
  463. Body: wire.SNAC_0x01_0x03_OServiceHostOnline{},
  464. })
  465. buddyListRegistry := newMockBuddyListRegistry(t)
  466. buddyListRegistry.EXPECT().
  467. RegisterBuddyList(mock.Anything, mock.Anything).
  468. Return(nil)
  469. departureNotifier := newMockDepartureNotifier(t)
  470. departureNotifier.EXPECT().
  471. BroadcastBuddyArrived(mock.Anything, mock.Anything, mock.Anything).
  472. Return(nil)
  473. chatSessionManager := newMockChatSessionManager(t)
  474. wg.Add(2)
  475. handler := func(ctx context.Context, serverType uint16, instance *state.SessionInstance, inFrame wire.SNACFrame, r io.Reader, rw ResponseWriter, endpointCfg config.Endpoint) error {
  476. defer wg.Done()
  477. assert.NoError(t, clientConn.Close())
  478. return nil
  479. }
  480. rt := oscarServer{
  481. rateLimitUpdater: noopRateLimitUpdater{},
  482. authService: authService,
  483. snacHandler: handler,
  484. logger: slog.Default(),
  485. onlineNotifier: onlineNotifier,
  486. buddyListRegistry: buddyListRegistry,
  487. chatSessionManager: chatSessionManager,
  488. departureNotifier: departureNotifier,
  489. recalcWarning: func(ctx context.Context, instance *state.SessionInstance) error {
  490. return nil
  491. },
  492. lowerWarnLevel: func(ctx context.Context, instance *state.SessionInstance) {
  493. defer wg.Done()
  494. },
  495. }
  496. assert.NoError(t, rt.routeConnection(context.Background(), clientFake, config.Endpoint{}))
  497. wg.Wait()
  498. }
  499. // Ensure client disconnection if session hits max concurrent sessions limit.
  500. func TestOscarServer_RouteConnection_BOS_MaxConcurrentSessionsReached(t *testing.T) {
  501. clientConn, serverConn := net.Pipe()
  502. addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080")
  503. assert.NoError(t, err)
  504. clientFake := fakeConn{
  505. Conn: serverConn,
  506. local: addr,
  507. remote: addr,
  508. }
  509. go func() {
  510. // < receive FLAPSignonFrame
  511. flap := wire.FLAPFrame{}
  512. assert.NoError(t, wire.UnmarshalBE(&flap, clientConn))
  513. flapSignonFrame := wire.FLAPSignonFrame{}
  514. assert.NoError(t, wire.UnmarshalBE(&flapSignonFrame, bytes.NewBuffer(flap.Payload)))
  515. // > send FLAPSignonFrame
  516. flapSignonFrame = wire.FLAPSignonFrame{
  517. FLAPVersion: 1,
  518. }
  519. flapSignonFrame.Append(wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")))
  520. buf := &bytes.Buffer{}
  521. assert.NoError(t, wire.MarshalBE(flapSignonFrame, buf))
  522. flap = wire.FLAPFrame{
  523. StartMarker: 42,
  524. FrameType: wire.FLAPFrameSignon,
  525. Payload: buf.Bytes(),
  526. }
  527. assert.NoError(t, wire.MarshalBE(flap, clientConn))
  528. flapc := wire.NewFlapClient(0, clientConn, clientConn)
  529. // < receive SNAC_0x01_0x03_OServiceHostOnline
  530. flap, err = flapc.ReceiveFLAP()
  531. assert.NoError(t, err)
  532. assert.NoError(t, clientConn.Close())
  533. }()
  534. wg := &sync.WaitGroup{}
  535. authService := newMockAuthService(t)
  536. authService.EXPECT().
  537. RegisterBOSSession(mock.Anything, state.ServerCookie{Service: wire.BOS}, mock.Anything).
  538. Return(nil, state.ErrMaxConcurrentSessionsReached)
  539. authService.EXPECT().
  540. CrackCookie(mock.Anything).
  541. Return(state.ServerCookie{Service: wire.BOS}, time.Now().Add(time.Minute), nil)
  542. rt := oscarServer{
  543. rateLimitUpdater: noopRateLimitUpdater{},
  544. authService: authService,
  545. logger: slog.Default(),
  546. }
  547. assert.NoError(t, rt.routeConnection(context.Background(), clientFake, config.Endpoint{}))
  548. wg.Wait()
  549. }
  550. func TestOscarServer_RouteConnection_Chat(t *testing.T) {
  551. instance := state.NewSession().AddInstance()
  552. // Without a monitor of its own, a chat client that subscribes over this
  553. // connection is never told its rate limit status changed.
  554. rateLimitUpdater := newRecordingRateLimitUpdater()
  555. clientConn, serverConn := net.Pipe()
  556. addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080")
  557. assert.NoError(t, err)
  558. clientFake := fakeConn{
  559. Conn: serverConn,
  560. local: addr,
  561. remote: addr,
  562. }
  563. go func() {
  564. // < receive FLAPSignonFrame
  565. flap := wire.FLAPFrame{}
  566. assert.NoError(t, wire.UnmarshalBE(&flap, clientConn))
  567. flapSignonFrame := wire.FLAPSignonFrame{}
  568. assert.NoError(t, wire.UnmarshalBE(&flapSignonFrame, bytes.NewBuffer(flap.Payload)))
  569. // > send FLAPSignonFrame
  570. flapSignonFrame = wire.FLAPSignonFrame{
  571. FLAPVersion: 1,
  572. }
  573. flapSignonFrame.Append(wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")))
  574. buf := &bytes.Buffer{}
  575. assert.NoError(t, wire.MarshalBE(flapSignonFrame, buf))
  576. flap = wire.FLAPFrame{
  577. StartMarker: 42,
  578. FrameType: wire.FLAPFrameSignon,
  579. Payload: buf.Bytes(),
  580. }
  581. assert.NoError(t, wire.MarshalBE(flap, clientConn))
  582. flapc := wire.NewFlapClient(0, clientConn, clientConn)
  583. // < receive SNAC_0x01_0x03_OServiceHostOnline
  584. frame := wire.SNACFrame{}
  585. body := wire.SNAC_0x01_0x03_OServiceHostOnline{}
  586. assert.NoError(t, flapc.ReceiveSNAC(&frame, &body))
  587. // send the first request that should get relayed to BOSRouter.Handle
  588. frame = wire.SNACFrame{
  589. FoodGroup: wire.OService,
  590. SubGroup: wire.OServiceClientOnline,
  591. }
  592. assert.NoError(t, flapc.SendSNAC(frame, struct{}{}))
  593. }()
  594. wg := &sync.WaitGroup{}
  595. authService := newMockAuthService(t)
  596. authService.EXPECT().
  597. RegisterChatSession(mock.Anything, state.ServerCookie{Service: wire.Chat}, mock.Anything).
  598. Run(func(_ context.Context, _ state.ServerCookie, cfg func(*state.Session)) {
  599. if cfg != nil {
  600. cfg(instance.Session())
  601. }
  602. }).
  603. Return(instance, nil)
  604. wg.Add(1)
  605. authService.EXPECT().
  606. SignoutChat(mock.Anything, instance.Session()).
  607. Run(func(ctx context.Context, s *state.Session) {
  608. defer wg.Done()
  609. })
  610. authService.EXPECT().
  611. CrackCookie(mock.Anything).
  612. Return(state.ServerCookie{Service: wire.Chat}, time.Now().Add(time.Minute), nil)
  613. onlineNotifier := newMockOnlineNotifier(t)
  614. onlineNotifier.EXPECT().
  615. HostOnline(mock.Anything).
  616. Return(wire.SNACMessage{
  617. Frame: wire.SNACFrame{
  618. FoodGroup: wire.OService,
  619. SubGroup: wire.OServiceHostOnline,
  620. },
  621. Body: wire.SNAC_0x01_0x03_OServiceHostOnline{},
  622. })
  623. buddyListRegistry := newMockBuddyListRegistry(t)
  624. departureNotifier := newMockDepartureNotifier(t)
  625. chatSessionManager := newMockChatSessionManager(t)
  626. wg.Add(1)
  627. handler := func(ctx context.Context, serverType uint16, instance *state.SessionInstance, inFrame wire.SNACFrame, r io.Reader, rw ResponseWriter, endpointCfg config.Endpoint) error {
  628. defer wg.Done()
  629. assert.NoError(t, clientConn.Close())
  630. return nil
  631. }
  632. rt := oscarServer{
  633. rateLimitUpdater: rateLimitUpdater,
  634. authService: authService,
  635. snacHandler: handler,
  636. logger: slog.Default(),
  637. onlineNotifier: onlineNotifier,
  638. buddyListRegistry: buddyListRegistry,
  639. chatSessionManager: chatSessionManager,
  640. departureNotifier: departureNotifier,
  641. }
  642. assert.NoError(t, rt.routeConnection(context.Background(), clientFake, config.Endpoint{}))
  643. wg.Wait()
  644. select {
  645. case monitored := <-rateLimitUpdater.monitored:
  646. assert.Same(t, instance.Session(), monitored, "the monitor must run against the chat session")
  647. case <-time.After(2 * time.Second):
  648. t.Fatal("no rate limit monitor was started for the chat session")
  649. }
  650. }
  651. func TestOscarServer_RouteConnection_Admin(t *testing.T) {
  652. instance := state.NewSession().AddInstance()
  653. clientConn, serverConn := net.Pipe()
  654. addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080")
  655. assert.NoError(t, err)
  656. clientFake := fakeConn{
  657. Conn: serverConn,
  658. local: addr,
  659. remote: addr,
  660. }
  661. go func() {
  662. // < receive FLAPSignonFrame
  663. flap := wire.FLAPFrame{}
  664. assert.NoError(t, wire.UnmarshalBE(&flap, clientConn))
  665. flapSignonFrame := wire.FLAPSignonFrame{}
  666. assert.NoError(t, wire.UnmarshalBE(&flapSignonFrame, bytes.NewBuffer(flap.Payload)))
  667. // > send FLAPSignonFrame
  668. flapSignonFrame = wire.FLAPSignonFrame{
  669. FLAPVersion: 1,
  670. }
  671. flapSignonFrame.Append(wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")))
  672. buf := &bytes.Buffer{}
  673. assert.NoError(t, wire.MarshalBE(flapSignonFrame, buf))
  674. flap = wire.FLAPFrame{
  675. StartMarker: 42,
  676. FrameType: wire.FLAPFrameSignon,
  677. Payload: buf.Bytes(),
  678. }
  679. assert.NoError(t, wire.MarshalBE(flap, clientConn))
  680. flapc := wire.NewFlapClient(0, clientConn, clientConn)
  681. // < receive SNAC_0x01_0x03_OServiceHostOnline
  682. frame := wire.SNACFrame{}
  683. body := wire.SNAC_0x01_0x03_OServiceHostOnline{}
  684. assert.NoError(t, flapc.ReceiveSNAC(&frame, &body))
  685. // send the first request that should get relayed to BOSRouter.Handle
  686. frame = wire.SNACFrame{
  687. FoodGroup: wire.OService,
  688. SubGroup: wire.OServiceClientOnline,
  689. }
  690. assert.NoError(t, flapc.SendSNAC(frame, struct{}{}))
  691. }()
  692. wg := &sync.WaitGroup{}
  693. authService := newMockAuthService(t)
  694. authService.EXPECT().
  695. CrackCookie(mock.Anything).
  696. Return(state.ServerCookie{Service: wire.Admin}, time.Now().Add(time.Minute), nil)
  697. authService.EXPECT().
  698. RetrieveBOSSession(mock.Anything, state.ServerCookie{Service: wire.Admin}).
  699. Return(instance, nil)
  700. onlineNotifier := newMockOnlineNotifier(t)
  701. onlineNotifier.EXPECT().
  702. HostOnline(mock.Anything).
  703. Return(wire.SNACMessage{
  704. Frame: wire.SNACFrame{
  705. FoodGroup: wire.OService,
  706. SubGroup: wire.OServiceHostOnline,
  707. },
  708. Body: wire.SNAC_0x01_0x03_OServiceHostOnline{},
  709. })
  710. buddyListRegistry := newMockBuddyListRegistry(t)
  711. departureNotifier := newMockDepartureNotifier(t)
  712. chatSessionManager := newMockChatSessionManager(t)
  713. wg.Add(1)
  714. handler := func(ctx context.Context, serverType uint16, instance *state.SessionInstance, inFrame wire.SNACFrame, r io.Reader, rw ResponseWriter, endpointCfg config.Endpoint) error {
  715. defer wg.Done()
  716. assert.NoError(t, clientConn.Close())
  717. return nil
  718. }
  719. rt := oscarServer{
  720. rateLimitUpdater: noopRateLimitUpdater{},
  721. authService: authService,
  722. snacHandler: handler,
  723. logger: slog.Default(),
  724. onlineNotifier: onlineNotifier,
  725. buddyListRegistry: buddyListRegistry,
  726. chatSessionManager: chatSessionManager,
  727. departureNotifier: departureNotifier,
  728. }
  729. assert.NoError(t, rt.routeConnection(context.Background(), clientFake, config.Endpoint{}))
  730. wg.Wait()
  731. }
  732. // Make sure the client receives signoff FLAP when the server shuts down via
  733. // context cancellation.
  734. func Test_oscarServer_dispatchIncomingMessages_shutdownSignoff(t *testing.T) {
  735. clientConn, serverConn := net.Pipe()
  736. ctx, cancel := context.WithCancel(context.Background())
  737. var wg sync.WaitGroup
  738. wg.Add(1)
  739. go func() {
  740. defer wg.Done()
  741. srv := oscarServer{
  742. rateLimitUpdater: noopRateLimitUpdater{},
  743. logger: slog.Default(),
  744. }
  745. instance := state.NewSession().AddInstance()
  746. instance.SetMultiConnFlag(wire.MultiConnFlagsRecentClient)
  747. flapc := wire.NewFlapClient(0, serverConn, serverConn)
  748. err := srv.dispatchIncomingMessages(ctx, wire.BOS, instance, flapc, serverConn, config.Endpoint{})
  749. assert.NoError(t, err)
  750. }()
  751. cancel()
  752. flapc := wire.NewFlapClient(0, clientConn, clientConn)
  753. frame, err := flapc.ReceiveFLAP()
  754. assert.NoError(t, err)
  755. assert.Equal(t, wire.FLAPFrameSignoff, frame.FrameType)
  756. wg.Wait()
  757. }
  758. // Make sure the client (which doesn't support multi-conn) receives
  759. // disconnection signoff FLAP when the session gets logged off by a new session.
  760. func Test_oscarServer_dispatchIncomingMessages_disconnect_old_client(t *testing.T) {
  761. clientConn, serverConn := net.Pipe()
  762. ctx := context.Background()
  763. instance := state.NewSession().AddInstance()
  764. instance.SetMultiConnFlag(wire.MultiConnFlagsOldClient)
  765. var wg sync.WaitGroup
  766. wg.Add(1)
  767. go func() {
  768. defer wg.Done()
  769. srv := oscarServer{
  770. rateLimitUpdater: noopRateLimitUpdater{},
  771. logger: slog.Default(),
  772. }
  773. flapc := wire.NewFlapClient(0, serverConn, serverConn)
  774. err := srv.dispatchIncomingMessages(ctx, wire.BOS, instance, flapc, serverConn, config.Endpoint{})
  775. assert.NoError(t, err)
  776. }()
  777. instance.CloseInstance()
  778. frame := wire.FLAPFrameDisconnect{}
  779. assert.NoError(t, wire.UnmarshalBE(&frame, clientConn))
  780. assert.Equal(t, wire.FLAPFrameSignoff, frame.FrameType)
  781. wg.Wait()
  782. }
  783. // Make sure the client (which supports multi-conn) receives disconnection
  784. // signoff FLAP when the session gets logged off by a new session.
  785. func Test_oscarServer_dispatchIncomingMessages_disconnect_new_client(t *testing.T) {
  786. clientConn, serverConn := net.Pipe()
  787. ctx := context.Background()
  788. instance := state.NewSession().AddInstance()
  789. instance.SetMultiConnFlag(wire.MultiConnFlagsRecentClient)
  790. var wg sync.WaitGroup
  791. wg.Add(1)
  792. go func() {
  793. defer wg.Done()
  794. srv := oscarServer{
  795. rateLimitUpdater: noopRateLimitUpdater{},
  796. logger: slog.Default(),
  797. }
  798. flapc := wire.NewFlapClient(0, serverConn, serverConn)
  799. err := srv.dispatchIncomingMessages(ctx, wire.BOS, instance, flapc, serverConn, config.Endpoint{})
  800. assert.NoError(t, err)
  801. }()
  802. instance.CloseInstance()
  803. flapc := wire.NewFlapClient(0, clientConn, clientConn)
  804. frame, err := flapc.ReceiveFLAP()
  805. assert.NoError(t, err)
  806. assert.Equal(t, wire.FLAPFrameSignoff, frame.FrameType)
  807. wg.Wait()
  808. }
  809. func Test_oscarServer_receiveSessMessages_BOS_integration(t *testing.T) {
  810. serverConn, clientConn := net.Pipe()
  811. defer func() { _ = serverConn.Close() }()
  812. defer func() { _ = clientConn.Close() }()
  813. // Prepare session and mocks so we can exercise through routeConnection
  814. instance := state.NewSession().AddInstance()
  815. instance.SetSignonComplete()
  816. authService := newMockAuthService(t)
  817. authService.EXPECT().
  818. CrackCookie(mock.Anything).
  819. Return(state.ServerCookie{Service: wire.BOS}, time.Now().Add(time.Minute), nil)
  820. authService.EXPECT().
  821. RegisterBOSSession(mock.Anything, state.ServerCookie{Service: wire.BOS}, mock.Anything).
  822. Run(func(ctx context.Context, cookie state.ServerCookie, conf func(*state.Session)) {
  823. if conf != nil {
  824. conf(instance.Session())
  825. }
  826. }).
  827. Return(instance, nil)
  828. var signoutWG sync.WaitGroup
  829. signoutWG.Add(1)
  830. authService.EXPECT().
  831. Signout(mock.Anything, instance.Session()).
  832. Run(func(ctx context.Context, s *state.Session) { signoutWG.Done() })
  833. onlineNotifier := newMockOnlineNotifier(t)
  834. onlineNotifier.EXPECT().
  835. HostOnline(mock.Anything).
  836. Return(wire.SNACMessage{
  837. Frame: wire.SNACFrame{FoodGroup: wire.OService, SubGroup: wire.OServiceHostOnline},
  838. Body: wire.SNAC_0x01_0x03_OServiceHostOnline{},
  839. })
  840. buddyListRegistry := newMockBuddyListRegistry(t)
  841. buddyListRegistry.EXPECT().RegisterBuddyList(mock.Anything, mock.Anything).Return(nil)
  842. buddyListRegistry.EXPECT().UnregisterBuddyList(mock.Anything, mock.Anything).Return(nil)
  843. departureNotifier := newMockDepartureNotifier(t)
  844. departureNotifier.EXPECT().BroadcastBuddyDeparted(mock.Anything, mock.Anything).Return(nil)
  845. chatSessionManager := newMockChatSessionManager(t)
  846. chatSessionManager.EXPECT().RemoveUserFromAllChats(mock.Anything)
  847. server := oscarServer{
  848. rateLimitUpdater: noopRateLimitUpdater{},
  849. authService: authService,
  850. buddyListRegistry: buddyListRegistry,
  851. chatSessionManager: chatSessionManager,
  852. departureNotifier: departureNotifier,
  853. onlineNotifier: onlineNotifier,
  854. logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
  855. recalcWarning: func(ctx context.Context, instance *state.SessionInstance) error { return nil },
  856. lowerWarnLevel: func(ctx context.Context, instance *state.SessionInstance) {},
  857. }
  858. // Fake client connection with address
  859. addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080")
  860. assert.NoError(t, err)
  861. clientFake := fakeConn{Conn: serverConn, local: addr, remote: addr}
  862. // Coordinate when the server has finished login and sent HostOnline
  863. ready := make(chan struct{})
  864. // Client goroutine: perform handshake and then read forwarded messages
  865. go func() {
  866. // < receive FLAPSignonFrame
  867. flap := wire.FLAPFrame{}
  868. _ = wire.UnmarshalBE(&flap, clientConn)
  869. flapSignon := wire.FLAPSignonFrame{}
  870. _ = wire.UnmarshalBE(&flapSignon, bytes.NewBuffer(flap.Payload))
  871. // > send FLAPSignonFrame with login cookie
  872. flapSignon = wire.FLAPSignonFrame{FLAPVersion: 1}
  873. flapSignon.Append(wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")))
  874. buf := &bytes.Buffer{}
  875. _ = wire.MarshalBE(flapSignon, buf)
  876. _ = wire.MarshalBE(wire.FLAPFrame{StartMarker: 42, FrameType: wire.FLAPFrameSignon, Payload: buf.Bytes()}, clientConn)
  877. // Expect HostOnline
  878. flapcClient := wire.NewFlapClient(0, clientConn, clientConn)
  879. fr := wire.SNACFrame{}
  880. body := wire.SNAC_0x01_0x03_OServiceHostOnline{}
  881. _ = flapcClient.ReceiveSNAC(&fr, &body)
  882. close(ready)
  883. }()
  884. // Run the server handler in background so we can drive the session
  885. doneServer := make(chan error, 1)
  886. go func() { doneServer <- server.routeConnection(context.Background(), clientFake, config.Endpoint{}) }()
  887. // Wait for HostOnline to be received so session is ready
  888. select {
  889. case <-ready:
  890. case <-time.After(5 * time.Second):
  891. t.Fatal("server did not complete login in time")
  892. }
  893. // Now send messages via the session and verify client receives them
  894. messages := []wire.SNACMessage{
  895. {
  896. Frame: wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyArrived},
  897. Body: wire.SNAC_0x03_0x0B_BuddyArrived{TLVUserInfo: wire.TLVUserInfo{ScreenName: "user1"}},
  898. },
  899. {
  900. Frame: wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyDeparted},
  901. Body: wire.SNAC_0x03_0x0C_BuddyDeparted{TLVUserInfo: wire.TLVUserInfo{ScreenName: "user2"}},
  902. },
  903. {
  904. Frame: wire.SNACFrame{FoodGroup: wire.ICBM, SubGroup: 0x07},
  905. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{Cookie: 12345, ChannelID: 1, TLVUserInfo: wire.TLVUserInfo{ScreenName: "sender"}},
  906. },
  907. }
  908. for i, msg := range messages {
  909. status := instance.RelayMessageToInstance(msg)
  910. assert.Equal(t, state.SessSendOK, status, "Message %d should be sent successfully", i)
  911. }
  912. // Read and verify all messages from client side
  913. for i, expected := range messages {
  914. flapFrame := wire.FLAPFrame{}
  915. err := wire.UnmarshalBE(&flapFrame, clientConn)
  916. assert.NoError(t, err, "read FLAP frame %d", i)
  917. assert.Equal(t, uint8(42), flapFrame.StartMarker)
  918. assert.Equal(t, wire.FLAPFrameData, flapFrame.FrameType)
  919. snac := wire.SNACFrame{}
  920. buf := bytes.NewBuffer(flapFrame.Payload)
  921. err = wire.UnmarshalBE(&snac, buf)
  922. assert.NoError(t, err, "unmarshal SNAC %d", i)
  923. assert.Equal(t, expected.Frame.FoodGroup, snac.FoodGroup)
  924. assert.Equal(t, expected.Frame.SubGroup, snac.SubGroup)
  925. }
  926. // CloseSession client to let server exit cleanly
  927. _ = clientConn.Close()
  928. // Wait for server handler to return
  929. select {
  930. case err := <-doneServer:
  931. assert.NoError(t, err)
  932. case <-time.After(5 * time.Second):
  933. t.Fatal("routeConnection did not exit in time")
  934. }
  935. // Ensure signout ran
  936. signoutWG.Wait()
  937. }
  938. func Test_oscarServer_receiveSessMessages_Chat_integration(t *testing.T) {
  939. serverConn, clientConn := net.Pipe()
  940. defer func() { _ = serverConn.Close() }()
  941. defer func() { _ = clientConn.Close() }()
  942. // Prepare session and mocks so we can exercise through routeConnection
  943. instance := state.NewSession().AddInstance()
  944. instance.SetSignonComplete()
  945. authService := newMockAuthService(t)
  946. authService.EXPECT().
  947. CrackCookie(mock.Anything).
  948. Return(state.ServerCookie{Service: wire.Chat}, time.Now().Add(time.Minute), nil)
  949. authService.EXPECT().
  950. RegisterChatSession(mock.Anything, state.ServerCookie{Service: wire.Chat}, mock.Anything).
  951. Run(func(_ context.Context, _ state.ServerCookie, cfg func(*state.Session)) {
  952. if cfg != nil {
  953. cfg(instance.Session())
  954. }
  955. }).
  956. Return(instance, nil)
  957. var signoutWG sync.WaitGroup
  958. signoutWG.Add(1)
  959. authService.EXPECT().
  960. SignoutChat(mock.Anything, instance.Session()).
  961. Run(func(ctx context.Context, s *state.Session) { signoutWG.Done() })
  962. onlineNotifier := newMockOnlineNotifier(t)
  963. onlineNotifier.EXPECT().
  964. HostOnline(mock.Anything).
  965. Return(wire.SNACMessage{
  966. Frame: wire.SNACFrame{FoodGroup: wire.OService, SubGroup: wire.OServiceHostOnline},
  967. Body: wire.SNAC_0x01_0x03_OServiceHostOnline{},
  968. })
  969. server := oscarServer{
  970. rateLimitUpdater: noopRateLimitUpdater{},
  971. authService: authService,
  972. onlineNotifier: onlineNotifier,
  973. logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
  974. }
  975. // Fake client connection with address
  976. addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080")
  977. assert.NoError(t, err)
  978. clientFake := fakeConn{Conn: serverConn, local: addr, remote: addr}
  979. ready := make(chan struct{})
  980. // Client goroutine: perform handshake and then read forwarded messages
  981. go func() {
  982. // < receive FLAPSignonFrame
  983. flap := wire.FLAPFrame{}
  984. _ = wire.UnmarshalBE(&flap, clientConn)
  985. flapSignon := wire.FLAPSignonFrame{}
  986. _ = wire.UnmarshalBE(&flapSignon, bytes.NewBuffer(flap.Payload))
  987. // > send FLAPSignonFrame with login cookie
  988. flapSignon = wire.FLAPSignonFrame{FLAPVersion: 1}
  989. flapSignon.Append(wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")))
  990. buf := &bytes.Buffer{}
  991. _ = wire.MarshalBE(flapSignon, buf)
  992. _ = wire.MarshalBE(wire.FLAPFrame{StartMarker: 42, FrameType: wire.FLAPFrameSignon, Payload: buf.Bytes()}, clientConn)
  993. // Expect HostOnline
  994. flapcClient := wire.NewFlapClient(0, clientConn, clientConn)
  995. fr := wire.SNACFrame{}
  996. body := wire.SNAC_0x01_0x03_OServiceHostOnline{}
  997. _ = flapcClient.ReceiveSNAC(&fr, &body)
  998. close(ready)
  999. }()
  1000. // Run the server handler in background so we can drive the session
  1001. doneServer := make(chan error, 1)
  1002. go func() { doneServer <- server.routeConnection(context.Background(), clientFake, config.Endpoint{}) }()
  1003. // Wait for HostOnline to be received so session is ready
  1004. select {
  1005. case <-ready:
  1006. case <-time.After(5 * time.Second):
  1007. t.Fatal("server did not complete login in time")
  1008. }
  1009. messages := []wire.SNACMessage{
  1010. {
  1011. Frame: wire.SNACFrame{FoodGroup: wire.Chat, SubGroup: wire.ChatUsersJoined},
  1012. Body: wire.SNAC_0x0E_0x03_ChatUsersJoined{Users: []wire.TLVUserInfo{{ScreenName: "user1"}}},
  1013. },
  1014. {
  1015. Frame: wire.SNACFrame{FoodGroup: wire.Chat, SubGroup: wire.ChatUsersLeft},
  1016. Body: wire.SNAC_0x0E_0x04_ChatUsersLeft{Users: []wire.TLVUserInfo{{ScreenName: "user2"}}},
  1017. },
  1018. {
  1019. Frame: wire.SNACFrame{FoodGroup: wire.Chat, SubGroup: wire.ChatChannelMsgToClient},
  1020. Body: wire.SNAC_0x0E_0x06_ChatChannelMsgToClient{
  1021. Cookie: 12345, Channel: 1,
  1022. TLVRestBlock: wire.TLVRestBlock{TLVList: wire.TLVList{
  1023. wire.NewTLVBE(wire.ChatTLVSenderInformation, wire.TLVUserInfo{ScreenName: "sender"}),
  1024. wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{TLVList: wire.TLVList{
  1025. wire.NewTLVBE(wire.ChatTLVMessageInfoText, "Hello chat!"),
  1026. }}),
  1027. }},
  1028. },
  1029. },
  1030. }
  1031. for i, msg := range messages {
  1032. status := instance.RelayMessageToInstance(msg)
  1033. assert.Equal(t, state.SessSendOK, status, "Message %d should be sent successfully", i)
  1034. }
  1035. for i, expected := range messages {
  1036. flapFrame := wire.FLAPFrame{}
  1037. err := wire.UnmarshalBE(&flapFrame, clientConn)
  1038. assert.NoError(t, err, "read FLAP frame %d", i)
  1039. assert.Equal(t, uint8(42), flapFrame.StartMarker)
  1040. assert.Equal(t, wire.FLAPFrameData, flapFrame.FrameType)
  1041. snac := wire.SNACFrame{}
  1042. buf := bytes.NewBuffer(flapFrame.Payload)
  1043. err = wire.UnmarshalBE(&snac, buf)
  1044. assert.NoError(t, err, "unmarshal SNAC %d", i)
  1045. assert.Equal(t, expected.Frame.FoodGroup, snac.FoodGroup)
  1046. assert.Equal(t, expected.Frame.SubGroup, snac.SubGroup)
  1047. }
  1048. _ = clientConn.Close()
  1049. select {
  1050. case err := <-doneServer:
  1051. assert.NoError(t, err)
  1052. case <-time.After(5 * time.Second):
  1053. t.Fatal("routeConnection did not exit in time")
  1054. }
  1055. signoutWG.Wait()
  1056. }
  1057. func newServeConnTestServer() *Server {
  1058. return NewServer(
  1059. nil,
  1060. nil,
  1061. nil,
  1062. nil,
  1063. slog.Default(),
  1064. nil,
  1065. nil,
  1066. nil,
  1067. wire.DefaultSNACRateLimits(),
  1068. nil,
  1069. nil,
  1070. func(ctx context.Context, instance *state.SessionInstance) error { return nil },
  1071. func(ctx context.Context, instance *state.SessionInstance) {},
  1072. )
  1073. }
  1074. func TestServer_ServeConn(t *testing.T) {
  1075. server := newServeConnTestServer()
  1076. var gotEndpoint config.Endpoint
  1077. var gotRemote net.Addr
  1078. server.handler = func(ctx context.Context, conn net.Conn, endpointCfg config.Endpoint) error {
  1079. gotEndpoint = endpointCfg
  1080. gotRemote = conn.RemoteAddr()
  1081. return nil
  1082. }
  1083. serverEnd, clientEnd := net.Pipe()
  1084. clientAddr := &net.TCPAddr{IP: net.IPv4(10, 0, 0, 5), Port: 4321}
  1085. endpoint := config.Endpoint{ListenAddress: ":5190", Group: config.ListenerGroup{Name: "LOCAL"}}
  1086. server.ServeConn(fakeConn{Conn: serverEnd, remote: clientAddr}, endpoint)
  1087. assert.Equal(t, endpoint, gotEndpoint)
  1088. assert.Equal(t, clientAddr, gotRemote)
  1089. // the connection is closed once served
  1090. _, err := clientEnd.Read(make([]byte, 1))
  1091. assert.Error(t, err)
  1092. assert.Empty(t, server.conns)
  1093. }
  1094. func TestServer_ServeConn_afterShutdown(t *testing.T) {
  1095. server := newServeConnTestServer()
  1096. called := false
  1097. server.handler = func(ctx context.Context, conn net.Conn, endpointCfg config.Endpoint) error {
  1098. called = true
  1099. return nil
  1100. }
  1101. assert.NoError(t, server.Shutdown(context.Background()))
  1102. serverEnd, clientEnd := net.Pipe()
  1103. server.ServeConn(serverEnd, config.Endpoint{})
  1104. assert.False(t, called)
  1105. _, err := clientEnd.Read(make([]byte, 1))
  1106. assert.Error(t, err)
  1107. }