connection.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. package oscar
  2. import (
  3. "bytes"
  4. "context"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "log/slog"
  9. "github.com/mk6i/retro-aim-server/server/oscar/middleware"
  10. "github.com/mk6i/retro-aim-server/state"
  11. "github.com/mk6i/retro-aim-server/wire"
  12. )
  13. func sendInvalidSNACErr(frameIn wire.SNACFrame, rw ResponseWriter) error {
  14. frameOut := wire.SNACFrame{
  15. FoodGroup: frameIn.FoodGroup,
  16. SubGroup: 0x01, // error subgroup for all SNACs
  17. RequestID: frameIn.RequestID,
  18. }
  19. bodyOut := wire.SNACError{
  20. Code: wire.ErrorCodeInvalidSnac,
  21. }
  22. return rw.SendSNAC(frameOut, bodyOut)
  23. }
  24. // dispatchIncomingMessages receives incoming messages and sends them to the
  25. // appropriate message handler. Messages from the client are sent to the
  26. // router. Messages relayed from the user session are forwarded to the client.
  27. // This function ensures that the same sequence number is incremented for both
  28. // types of messages. The function terminates upon receiving a connection error
  29. // or when the session closes.
  30. //
  31. // todo: this method has too many params and should be folded into a new type
  32. func dispatchIncomingMessages(ctx context.Context, sess *state.Session, flapc *wire.FlapClient, r io.Reader, logger *slog.Logger, router Handler) error {
  33. defer func() {
  34. logger.InfoContext(ctx, "user disconnected")
  35. }()
  36. // buffered so that the go routine has room to exit
  37. msgCh := make(chan wire.FLAPFrame, 1)
  38. errCh := make(chan error, 1)
  39. // consume flap frames
  40. go func() {
  41. defer close(msgCh)
  42. defer close(errCh)
  43. for {
  44. frame := wire.FLAPFrame{}
  45. if err := wire.UnmarshalBE(&frame, r); err != nil {
  46. errCh <- err
  47. return
  48. }
  49. msgCh <- frame
  50. }
  51. }()
  52. for {
  53. select {
  54. case flap, ok := <-msgCh:
  55. if !ok {
  56. return nil
  57. }
  58. switch flap.FrameType {
  59. case wire.FLAPFrameData:
  60. flapBuf := bytes.NewBuffer(flap.Payload)
  61. inFrame := wire.SNACFrame{}
  62. if err := wire.UnmarshalBE(&inFrame, flapBuf); err != nil {
  63. return err
  64. }
  65. // route a client request to the appropriate service handler. the
  66. // handler may write a response to the client connection.
  67. if err := router.Handle(ctx, sess, inFrame, flapBuf, flapc); err != nil {
  68. middleware.LogRequestError(ctx, logger, inFrame, err)
  69. if errors.Is(err, ErrRouteNotFound) {
  70. if err1 := sendInvalidSNACErr(inFrame, flapc); err1 != nil {
  71. return errors.Join(err1, err)
  72. }
  73. break
  74. }
  75. return err
  76. }
  77. case wire.FLAPFrameSignon:
  78. return fmt.Errorf("shouldn't get FLAPFrameSignon. flap: %v", flap)
  79. case wire.FLAPFrameError:
  80. return fmt.Errorf("got FLAPFrameError. flap: %v", flap)
  81. case wire.FLAPFrameSignoff:
  82. logger.InfoContext(ctx, "got FLAPFrameSignoff", "flap", flap)
  83. return nil
  84. case wire.FLAPFrameKeepAlive:
  85. logger.DebugContext(ctx, "keepalive heartbeat")
  86. default:
  87. return fmt.Errorf("got unknown FLAP frame type. flap: %v", flap)
  88. }
  89. case m := <-sess.ReceiveMessage():
  90. // forward a notification sent from another client to this client
  91. if err := flapc.SendSNAC(m.Frame, m.Body); err != nil {
  92. middleware.LogRequestError(ctx, logger, m.Frame, err)
  93. return err
  94. }
  95. middleware.LogRequest(ctx, logger, m.Frame, m.Body)
  96. case <-sess.Closed():
  97. block := wire.TLVRestBlock{}
  98. // error code indicating user signed in a different location
  99. block.Append(wire.NewTLVBE(0x0009, wire.OServiceDiscErrNewLogin))
  100. // "more info" button
  101. block.Append(wire.NewTLVBE(0x000b, "https://github.com/mk6i/retro-aim-server"))
  102. if err := flapc.SendSignoffFrame(block); err != nil {
  103. return fmt.Errorf("unable to gracefully disconnect user. %w", err)
  104. }
  105. return nil
  106. case <-ctx.Done():
  107. // application is shutting down
  108. if err := flapc.Disconnect(); err != nil {
  109. return fmt.Errorf("unable to gracefully disconnect user. %w", err)
  110. }
  111. return nil
  112. case err := <-errCh:
  113. if !errors.Is(io.EOF, err) {
  114. logger.ErrorContext(ctx, "client disconnected with error", "err", err)
  115. }
  116. return nil
  117. }
  118. }
  119. }