4
0

server_test.go 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246
  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}, 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}, 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}, 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}, 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}, 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}, 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}, 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. }