server_test.go 28 KB

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