server_test.go 35 KB

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