server_test.go 38 KB

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