server_test.go 35 KB

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