server_test.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984
  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. sess.SetMultiConnFlag(wire.MultiConnFlagsRecentClient)
  542. flapc := wire.NewFlapClient(0, serverConn, serverConn)
  543. err := srv.dispatchIncomingMessages(ctx, wire.BOS, sess, flapc, serverConn, config.Listener{})
  544. assert.NoError(t, err)
  545. }()
  546. cancel()
  547. flapc := wire.NewFlapClient(0, clientConn, clientConn)
  548. frame, err := flapc.ReceiveFLAP()
  549. assert.NoError(t, err)
  550. assert.Equal(t, wire.FLAPFrameSignoff, frame.FrameType)
  551. wg.Wait()
  552. }
  553. // Make sure the client (which doesn't support multi-conn) receives
  554. // disconnection signoff FLAP when the session gets logged off by a new session.
  555. func Test_oscarServer_dispatchIncomingMessages_disconnect_old_client(t *testing.T) {
  556. clientConn, serverConn := net.Pipe()
  557. ctx := context.Background()
  558. sess := state.NewSession()
  559. sess.SetMultiConnFlag(wire.MultiConnFlagsOldClient)
  560. var wg sync.WaitGroup
  561. wg.Add(1)
  562. go func() {
  563. defer wg.Done()
  564. srv := oscarServer{
  565. Logger: slog.Default(),
  566. }
  567. flapc := wire.NewFlapClient(0, serverConn, serverConn)
  568. err := srv.dispatchIncomingMessages(ctx, wire.BOS, sess, flapc, serverConn, config.Listener{})
  569. assert.NoError(t, err)
  570. }()
  571. sess.Close()
  572. frame := wire.FLAPFrameDisconnect{}
  573. assert.NoError(t, wire.UnmarshalBE(&frame, clientConn))
  574. assert.Equal(t, wire.FLAPFrameSignoff, frame.FrameType)
  575. wg.Wait()
  576. }
  577. // Make sure the client (which supports multi-conn) receives disconnection
  578. // signoff FLAP when the session gets logged off by a new session.
  579. func Test_oscarServer_dispatchIncomingMessages_disconnect_new_client(t *testing.T) {
  580. clientConn, serverConn := net.Pipe()
  581. ctx := context.Background()
  582. sess := state.NewSession()
  583. sess.SetMultiConnFlag(wire.MultiConnFlagsRecentClient)
  584. var wg sync.WaitGroup
  585. wg.Add(1)
  586. go func() {
  587. defer wg.Done()
  588. srv := oscarServer{
  589. Logger: slog.Default(),
  590. }
  591. flapc := wire.NewFlapClient(0, serverConn, serverConn)
  592. err := srv.dispatchIncomingMessages(ctx, wire.BOS, sess, flapc, serverConn, config.Listener{})
  593. assert.NoError(t, err)
  594. }()
  595. sess.Close()
  596. flapc := wire.NewFlapClient(0, clientConn, clientConn)
  597. frame, err := flapc.ReceiveFLAP()
  598. assert.NoError(t, err)
  599. assert.Equal(t, wire.FLAPFrameSignoff, frame.FrameType)
  600. wg.Wait()
  601. }
  602. func Test_oscarServer_receiveSessMessages_BOS_integration(t *testing.T) {
  603. serverConn, clientConn := net.Pipe()
  604. defer serverConn.Close()
  605. defer clientConn.Close()
  606. // Prepare session and mocks so we can exercise through routeConnection
  607. sess := state.NewSession()
  608. authService := newMockAuthService(t)
  609. authService.EXPECT().
  610. CrackCookie(mock.Anything).
  611. Return(state.ServerCookie{Service: wire.BOS}, nil)
  612. authService.EXPECT().
  613. RegisterBOSSession(mock.Anything, state.ServerCookie{Service: wire.BOS}).
  614. Return(sess, nil)
  615. var signoutWG sync.WaitGroup
  616. signoutWG.Add(1)
  617. authService.EXPECT().
  618. Signout(mock.Anything, sess).
  619. Run(func(ctx context.Context, s *state.Session) { signoutWG.Done() })
  620. onlineNotifier := newMockOnlineNotifier(t)
  621. onlineNotifier.EXPECT().
  622. HostOnline(mock.Anything).
  623. Return(wire.SNACMessage{
  624. Frame: wire.SNACFrame{FoodGroup: wire.OService, SubGroup: wire.OServiceHostOnline},
  625. Body: wire.SNAC_0x01_0x03_OServiceHostOnline{},
  626. })
  627. buddyListRegistry := newMockBuddyListRegistry(t)
  628. buddyListRegistry.EXPECT().RegisterBuddyList(mock.Anything, mock.Anything).Return(nil)
  629. buddyListRegistry.EXPECT().UnregisterBuddyList(mock.Anything, mock.Anything).Return(nil)
  630. departureNotifier := newMockDepartureNotifier(t)
  631. departureNotifier.EXPECT().BroadcastBuddyDeparted(mock.Anything, mock.Anything).Return(nil)
  632. chatSessionManager := newMockChatSessionManager(t)
  633. chatSessionManager.EXPECT().RemoveUserFromAllChats(mock.Anything)
  634. server := oscarServer{
  635. AuthService: authService,
  636. BuddyListRegistry: buddyListRegistry,
  637. ChatSessionManager: chatSessionManager,
  638. DepartureNotifier: departureNotifier,
  639. OnlineNotifier: onlineNotifier,
  640. Logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
  641. }
  642. // Fake client connection with address
  643. addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080")
  644. assert.NoError(t, err)
  645. clientFake := fakeConn{Conn: serverConn, local: addr, remote: addr}
  646. // Coordinate when the server has finished login and sent HostOnline
  647. ready := make(chan struct{})
  648. // Client goroutine: perform handshake and then read forwarded messages
  649. go func() {
  650. // < receive FLAPSignonFrame
  651. flap := wire.FLAPFrame{}
  652. _ = wire.UnmarshalBE(&flap, clientConn)
  653. flapSignon := wire.FLAPSignonFrame{}
  654. _ = wire.UnmarshalBE(&flapSignon, bytes.NewBuffer(flap.Payload))
  655. // > send FLAPSignonFrame with login cookie
  656. flapSignon = wire.FLAPSignonFrame{FLAPVersion: 1}
  657. flapSignon.Append(wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")))
  658. buf := &bytes.Buffer{}
  659. _ = wire.MarshalBE(flapSignon, buf)
  660. _ = wire.MarshalBE(wire.FLAPFrame{StartMarker: 42, FrameType: wire.FLAPFrameSignon, Payload: buf.Bytes()}, clientConn)
  661. // Expect HostOnline
  662. flapcClient := wire.NewFlapClient(0, clientConn, clientConn)
  663. fr := wire.SNACFrame{}
  664. body := wire.SNAC_0x01_0x03_OServiceHostOnline{}
  665. _ = flapcClient.ReceiveSNAC(&fr, &body)
  666. close(ready)
  667. }()
  668. // Run the server handler in background so we can drive the session
  669. doneServer := make(chan error, 1)
  670. go func() { doneServer <- server.routeConnection(context.Background(), clientFake, config.Listener{}) }()
  671. // Wait for HostOnline to be received so session is ready
  672. select {
  673. case <-ready:
  674. case <-time.After(5 * time.Second):
  675. t.Fatal("server did not complete login in time")
  676. }
  677. // Now send messages via the session and verify client receives them
  678. messages := []wire.SNACMessage{
  679. {
  680. Frame: wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyArrived},
  681. Body: wire.SNAC_0x03_0x0B_BuddyArrived{TLVUserInfo: wire.TLVUserInfo{ScreenName: "user1"}},
  682. },
  683. {
  684. Frame: wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyDeparted},
  685. Body: wire.SNAC_0x03_0x0C_BuddyDeparted{TLVUserInfo: wire.TLVUserInfo{ScreenName: "user2"}},
  686. },
  687. {
  688. Frame: wire.SNACFrame{FoodGroup: wire.ICBM, SubGroup: 0x07},
  689. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{Cookie: 12345, ChannelID: 1, TLVUserInfo: wire.TLVUserInfo{ScreenName: "sender"}},
  690. },
  691. }
  692. for i, msg := range messages {
  693. status := sess.RelayMessage(msg)
  694. assert.Equal(t, state.SessSendOK, status, "Message %d should be sent successfully", i)
  695. }
  696. // Read and verify all messages from client side
  697. for i, expected := range messages {
  698. flapFrame := wire.FLAPFrame{}
  699. err := wire.UnmarshalBE(&flapFrame, clientConn)
  700. assert.NoError(t, err, "read FLAP frame %d", i)
  701. assert.Equal(t, uint8(42), flapFrame.StartMarker)
  702. assert.Equal(t, wire.FLAPFrameData, flapFrame.FrameType)
  703. snac := wire.SNACFrame{}
  704. buf := bytes.NewBuffer(flapFrame.Payload)
  705. err = wire.UnmarshalBE(&snac, buf)
  706. assert.NoError(t, err, "unmarshal SNAC %d", i)
  707. assert.Equal(t, expected.Frame.FoodGroup, snac.FoodGroup)
  708. assert.Equal(t, expected.Frame.SubGroup, snac.SubGroup)
  709. }
  710. // Close client to let server exit cleanly
  711. _ = clientConn.Close()
  712. // Wait for server handler to return
  713. select {
  714. case err := <-doneServer:
  715. assert.NoError(t, err)
  716. case <-time.After(5 * time.Second):
  717. t.Fatal("routeConnection did not exit in time")
  718. }
  719. // Ensure signout ran
  720. signoutWG.Wait()
  721. }
  722. func Test_oscarServer_receiveSessMessages_Chat_integration(t *testing.T) {
  723. serverConn, clientConn := net.Pipe()
  724. defer serverConn.Close()
  725. defer clientConn.Close()
  726. // Prepare session and mocks so we can exercise through routeConnection
  727. sess := state.NewSession()
  728. authService := newMockAuthService(t)
  729. authService.EXPECT().
  730. CrackCookie(mock.Anything).
  731. Return(state.ServerCookie{Service: wire.Chat}, nil)
  732. authService.EXPECT().
  733. RegisterChatSession(mock.Anything, state.ServerCookie{Service: wire.Chat}).
  734. Return(sess, nil)
  735. var signoutWG sync.WaitGroup
  736. signoutWG.Add(1)
  737. authService.EXPECT().
  738. SignoutChat(mock.Anything, sess).
  739. Run(func(ctx context.Context, s *state.Session) { signoutWG.Done() })
  740. onlineNotifier := newMockOnlineNotifier(t)
  741. onlineNotifier.EXPECT().
  742. HostOnline(mock.Anything).
  743. Return(wire.SNACMessage{
  744. Frame: wire.SNACFrame{FoodGroup: wire.OService, SubGroup: wire.OServiceHostOnline},
  745. Body: wire.SNAC_0x01_0x03_OServiceHostOnline{},
  746. })
  747. server := oscarServer{
  748. AuthService: authService,
  749. OnlineNotifier: onlineNotifier,
  750. Logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
  751. }
  752. // Fake client connection with address
  753. addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080")
  754. assert.NoError(t, err)
  755. clientFake := fakeConn{Conn: serverConn, local: addr, remote: addr}
  756. ready := make(chan struct{})
  757. // Client goroutine: perform handshake and then read forwarded messages
  758. go func() {
  759. // < receive FLAPSignonFrame
  760. flap := wire.FLAPFrame{}
  761. _ = wire.UnmarshalBE(&flap, clientConn)
  762. flapSignon := wire.FLAPSignonFrame{}
  763. _ = wire.UnmarshalBE(&flapSignon, bytes.NewBuffer(flap.Payload))
  764. // > send FLAPSignonFrame with login cookie
  765. flapSignon = wire.FLAPSignonFrame{FLAPVersion: 1}
  766. flapSignon.Append(wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("the-cookie")))
  767. buf := &bytes.Buffer{}
  768. _ = wire.MarshalBE(flapSignon, buf)
  769. _ = wire.MarshalBE(wire.FLAPFrame{StartMarker: 42, FrameType: wire.FLAPFrameSignon, Payload: buf.Bytes()}, clientConn)
  770. // Expect HostOnline
  771. flapcClient := wire.NewFlapClient(0, clientConn, clientConn)
  772. fr := wire.SNACFrame{}
  773. body := wire.SNAC_0x01_0x03_OServiceHostOnline{}
  774. _ = flapcClient.ReceiveSNAC(&fr, &body)
  775. close(ready)
  776. }()
  777. // Run the server handler in background so we can drive the session
  778. doneServer := make(chan error, 1)
  779. go func() { doneServer <- server.routeConnection(context.Background(), clientFake, config.Listener{}) }()
  780. // Wait for HostOnline to be received so session is ready
  781. select {
  782. case <-ready:
  783. case <-time.After(5 * time.Second):
  784. t.Fatal("server did not complete login in time")
  785. }
  786. messages := []wire.SNACMessage{
  787. {
  788. Frame: wire.SNACFrame{FoodGroup: wire.Chat, SubGroup: wire.ChatUsersJoined},
  789. Body: wire.SNAC_0x0E_0x03_ChatUsersJoined{Users: []wire.TLVUserInfo{{ScreenName: "user1"}}},
  790. },
  791. {
  792. Frame: wire.SNACFrame{FoodGroup: wire.Chat, SubGroup: wire.ChatUsersLeft},
  793. Body: wire.SNAC_0x0E_0x04_ChatUsersLeft{Users: []wire.TLVUserInfo{{ScreenName: "user2"}}},
  794. },
  795. {
  796. Frame: wire.SNACFrame{FoodGroup: wire.Chat, SubGroup: wire.ChatChannelMsgToClient},
  797. Body: wire.SNAC_0x0E_0x06_ChatChannelMsgToClient{
  798. Cookie: 12345, Channel: 1,
  799. TLVRestBlock: wire.TLVRestBlock{TLVList: wire.TLVList{
  800. wire.NewTLVBE(wire.ChatTLVSenderInformation, wire.TLVUserInfo{ScreenName: "sender"}),
  801. wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{TLVList: wire.TLVList{
  802. wire.NewTLVBE(wire.ChatTLVMessageInfoText, "Hello chat!"),
  803. }}),
  804. }},
  805. },
  806. },
  807. }
  808. for i, msg := range messages {
  809. status := sess.RelayMessage(msg)
  810. assert.Equal(t, state.SessSendOK, status, "Message %d should be sent successfully", i)
  811. }
  812. for i, expected := range messages {
  813. flapFrame := wire.FLAPFrame{}
  814. err := wire.UnmarshalBE(&flapFrame, clientConn)
  815. assert.NoError(t, err, "read FLAP frame %d", i)
  816. assert.Equal(t, uint8(42), flapFrame.StartMarker)
  817. assert.Equal(t, wire.FLAPFrameData, flapFrame.FrameType)
  818. snac := wire.SNACFrame{}
  819. buf := bytes.NewBuffer(flapFrame.Payload)
  820. err = wire.UnmarshalBE(&snac, buf)
  821. assert.NoError(t, err, "unmarshal SNAC %d", i)
  822. assert.Equal(t, expected.Frame.FoodGroup, snac.FoodGroup)
  823. assert.Equal(t, expected.Frame.SubGroup, snac.SubGroup)
  824. }
  825. _ = clientConn.Close()
  826. select {
  827. case err := <-doneServer:
  828. assert.NoError(t, err)
  829. case <-time.After(5 * time.Second):
  830. t.Fatal("routeConnection did not exit in time")
  831. }
  832. signoutWG.Wait()
  833. }