connection.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. package server
  2. import (
  3. "bytes"
  4. "context"
  5. "errors"
  6. "io"
  7. "log/slog"
  8. "github.com/mk6i/retro-aim-server/oscar"
  9. "github.com/mk6i/retro-aim-server/state"
  10. )
  11. var (
  12. ErrUnsupportedSubGroup = errors.New("unimplemented subgroup, your client version may be unsupported")
  13. )
  14. type (
  15. incomingMessage struct {
  16. flap oscar.FLAPFrame
  17. payload *bytes.Buffer
  18. }
  19. alertHandler func(ctx context.Context, msg oscar.SNACMessage, w io.Writer, u *uint32) error
  20. clientReqHandler func(ctx context.Context, r io.Reader, w io.Writer, u *uint32) error
  21. )
  22. func sendSNAC(frame oscar.SNACFrame, body any, sequence *uint32, w io.Writer) error {
  23. snacBuf := &bytes.Buffer{}
  24. if err := oscar.Marshal(frame, snacBuf); err != nil {
  25. return err
  26. }
  27. if err := oscar.Marshal(body, snacBuf); err != nil {
  28. return err
  29. }
  30. flap := oscar.FLAPFrame{
  31. StartMarker: 42,
  32. FrameType: oscar.FLAPFrameData,
  33. Sequence: uint16(*sequence),
  34. PayloadLength: uint16(snacBuf.Len()),
  35. }
  36. if err := oscar.Marshal(flap, w); err != nil {
  37. return err
  38. }
  39. expectLen := snacBuf.Len()
  40. c, err := w.Write(snacBuf.Bytes())
  41. if err != nil {
  42. return err
  43. }
  44. if c != expectLen {
  45. panic("did not write the expected # of bytes")
  46. }
  47. *sequence++
  48. return nil
  49. }
  50. func receiveSNAC(frame *oscar.SNACFrame, body any, r io.Reader) error {
  51. flap := oscar.FLAPFrame{}
  52. if err := oscar.Unmarshal(&flap, r); err != nil {
  53. return err
  54. }
  55. buf, err := flap.ReadBody(r)
  56. if err != nil {
  57. return err
  58. }
  59. if err := oscar.Unmarshal(frame, buf); err != nil {
  60. return err
  61. }
  62. return oscar.Unmarshal(body, buf)
  63. }
  64. func sendInvalidSNACErr(frameIn oscar.SNACFrame, w io.Writer, sequence *uint32) error {
  65. frameOut := oscar.SNACFrame{
  66. FoodGroup: frameIn.FoodGroup,
  67. SubGroup: 0x01, // error subgroup for all SNACs
  68. RequestID: frameIn.RequestID,
  69. }
  70. bodyOut := oscar.SNACError{
  71. Code: oscar.ErrorCodeInvalidSnac,
  72. }
  73. return sendSNAC(frameOut, bodyOut, sequence, w)
  74. }
  75. func consumeFLAPFrames(r io.Reader, msgCh chan incomingMessage, errCh chan error) {
  76. defer close(msgCh)
  77. defer close(errCh)
  78. for {
  79. in := incomingMessage{}
  80. if err := oscar.Unmarshal(&in.flap, r); err != nil {
  81. errCh <- err
  82. return
  83. }
  84. if in.flap.FrameType == oscar.FLAPFrameData {
  85. buf := make([]byte, in.flap.PayloadLength)
  86. if _, err := r.Read(buf); err != nil {
  87. errCh <- err
  88. return
  89. }
  90. in.payload = bytes.NewBuffer(buf)
  91. }
  92. msgCh <- in
  93. }
  94. }
  95. func dispatchIncomingMessages(ctx context.Context, sess *state.Session, seq uint32, rw io.ReadWriter, logger *slog.Logger, fn clientReqHandler, alertHandler alertHandler) {
  96. // buffered so that the go routine has room to exit
  97. msgCh := make(chan incomingMessage, 1)
  98. readErrCh := make(chan error, 1)
  99. go consumeFLAPFrames(rw, msgCh, readErrCh)
  100. defer func() {
  101. logger.InfoContext(ctx, "user disconnected")
  102. }()
  103. for {
  104. select {
  105. case m := <-msgCh:
  106. switch m.flap.FrameType {
  107. case oscar.FLAPFrameData:
  108. // route a client request to the appropriate service handler. the
  109. // handler may write a response to the client connection.
  110. if err := fn(ctx, m.payload, rw, &seq); err != nil {
  111. return
  112. }
  113. case oscar.FLAPFrameSignon:
  114. logger.ErrorContext(ctx, "shouldn't get FLAPFrameSignon", "flap", m.flap)
  115. case oscar.FLAPFrameError:
  116. logger.ErrorContext(ctx, "got FLAPFrameError", "flap", m.flap)
  117. return
  118. case oscar.FLAPFrameSignoff:
  119. logger.InfoContext(ctx, "got FLAPFrameSignoff", "flap", m.flap)
  120. return
  121. case oscar.FLAPFrameKeepAlive:
  122. logger.DebugContext(ctx, "keepalive heartbeat")
  123. default:
  124. logger.ErrorContext(ctx, "got unknown FLAP frame type", "flap", m.flap)
  125. return
  126. }
  127. case m := <-sess.ReceiveMessage():
  128. // forward a notification sent from another client to this client
  129. if err := alertHandler(ctx, m, rw, &seq); err != nil {
  130. logRequestError(ctx, logger, m.Frame, err)
  131. return
  132. }
  133. logRequest(ctx, logger, m.Frame, m.Body)
  134. case <-sess.Closed():
  135. // gracefully disconnect so that the client does not try to
  136. // reconnect when the connection closes.
  137. flap := oscar.FLAPFrame{
  138. StartMarker: 42,
  139. FrameType: oscar.FLAPFrameSignoff,
  140. Sequence: uint16(seq),
  141. PayloadLength: uint16(0),
  142. }
  143. if err := oscar.Marshal(flap, rw); err != nil {
  144. logger.ErrorContext(ctx, "unable to gracefully disconnect user", "err", err)
  145. }
  146. return
  147. case err := <-readErrCh:
  148. if !errors.Is(io.EOF, err) {
  149. logger.ErrorContext(ctx, "client disconnected with error", "err", err)
  150. }
  151. return
  152. }
  153. }
  154. }