chat.go 2.5 KB

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