chat.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. package oscar
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "log/slog"
  8. "net"
  9. "sync"
  10. "time"
  11. "github.com/mk6i/retro-aim-server/config"
  12. "github.com/mk6i/retro-aim-server/wire"
  13. )
  14. // ChatServer represents a service that implements a chat room session.
  15. // Clients connect to this service upon creating a chat room or being invited
  16. // to a chat room.
  17. type ChatServer struct {
  18. AuthService
  19. Handler
  20. Logger *slog.Logger
  21. OnlineNotifier
  22. config.Config
  23. RateLimitUpdater
  24. wire.SNACRateLimits
  25. }
  26. // Start creates a TCP server that implements that chat flow.
  27. func (rt ChatServer) Start(ctx context.Context) error {
  28. addr := net.JoinHostPort("", rt.Config.ChatPort)
  29. listener, err := net.Listen("tcp", addr)
  30. if err != nil {
  31. return fmt.Errorf("unable to start chat sever: %w", err)
  32. }
  33. go func() {
  34. <-ctx.Done()
  35. listener.Close()
  36. }()
  37. rt.Logger.Info("starting server", "listen_host", addr, "oscar_host", rt.Config.OSCARHost)
  38. wg := sync.WaitGroup{}
  39. for {
  40. conn, err := listener.Accept()
  41. if err != nil {
  42. if errors.Is(err, net.ErrClosed) {
  43. break
  44. }
  45. rt.Logger.Error("accept failed", "err", err.Error())
  46. continue
  47. }
  48. wg.Add(1)
  49. go func() {
  50. defer wg.Done()
  51. connCtx := context.WithValue(ctx, "ip", conn.RemoteAddr().String())
  52. rt.Logger.DebugContext(connCtx, "accepted connection")
  53. if err := rt.handleNewConnection(connCtx, conn); err != nil {
  54. rt.Logger.Info("user session failed", "err", err.Error())
  55. }
  56. }()
  57. }
  58. if !waitForShutdown(&wg) {
  59. rt.Logger.Error("shutdown complete, but connections didn't close cleanly")
  60. } else {
  61. rt.Logger.Info("shutdown complete")
  62. }
  63. return nil
  64. }
  65. func (rt ChatServer) handleNewConnection(ctx context.Context, rwc io.ReadWriteCloser) error {
  66. defer func() {
  67. rwc.Close()
  68. }()
  69. flapc := wire.NewFlapClient(100, rwc, rwc)
  70. if err := flapc.SendSignonFrame(nil); err != nil {
  71. return err
  72. }
  73. flap, err := flapc.ReceiveSignonFrame()
  74. if err != nil {
  75. return err
  76. }
  77. authCookie, ok := flap.Bytes(wire.OServiceTLVTagsLoginCookie)
  78. if !ok {
  79. return errors.New("unable to get login cookie from payload")
  80. }
  81. chatSess, err := rt.RegisterChatSession(ctx, authCookie)
  82. if err != nil {
  83. return err
  84. }
  85. if chatSess == nil {
  86. return errors.New("session not found")
  87. }
  88. defer func() {
  89. ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
  90. defer cancel()
  91. chatSess.Close()
  92. rt.SignoutChat(ctx, chatSess)
  93. }()
  94. msg := rt.HostOnline()
  95. if err := flapc.SendSNAC(msg.Frame, msg.Body); err != nil {
  96. return err
  97. }
  98. ctx = context.WithValue(ctx, "screenName", chatSess.IdentScreenName())
  99. return dispatchIncomingMessages(ctx, chatSess, flapc, rwc, rt.Logger, rt.Handler, rt.RateLimitUpdater, rt.SNACRateLimits)
  100. }