connection.go 4.0 KB

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