github.com/ezoic/ws@v1.0.4-0.20220713205711-5c1d69e074c5/wsutil/handler.go (about)

     1  package wsutil
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"io/ioutil"
     7  	"strconv"
     8  
     9  	"github.com/ezoic/pool/pbytes"
    10  	"github.com/ezoic/ws"
    11  )
    12  
    13  // ClosedError returned when peer has closed the connection with appropriate
    14  // code and a textual reason.
    15  type ClosedError struct {
    16  	Code   ws.StatusCode
    17  	Reason string
    18  }
    19  
    20  // Error implements error interface.
    21  func (err ClosedError) Error() string {
    22  	return "ws closed: " + strconv.FormatUint(uint64(err.Code), 10) + " " + err.Reason
    23  }
    24  
    25  // ControlHandler contains logic of handling control frames.
    26  //
    27  // The intentional way to use it is to read the next frame header from the
    28  // connection, optionally check its validity via ws.CheckHeader() and if it is
    29  // not a ws.OpText of ws.OpBinary (or ws.OpContinuation) – pass it to Handle()
    30  // method.
    31  //
    32  // That is, passed header should be checked to get rid of unexpected errors.
    33  //
    34  // The Handle() method will read out all control frame payload (if any) and
    35  // write necessary bytes as a rfc compatible response.
    36  type ControlHandler struct {
    37  	Src   io.Reader
    38  	Dst   io.Writer
    39  	State ws.State
    40  
    41  	// DisableSrcCiphering disables unmasking payload data read from Src.
    42  	// It is useful when wsutil.Reader is used or when frame payload already
    43  	// pulled and ciphered out from the connection (and introduced by
    44  	// bytes.Reader, for example).
    45  	DisableSrcCiphering bool
    46  }
    47  
    48  // ErrNotControlFrame is returned by ControlHandler to indicate that given
    49  // header could not be handled.
    50  var ErrNotControlFrame = errors.New("not a control frame")
    51  
    52  // Handle handles control frames regarding to the c.State and writes responses
    53  // to the c.Dst when needed.
    54  //
    55  // It returns ErrNotControlFrame when given header is not of ws.OpClose,
    56  // ws.OpPing or ws.OpPong operation code.
    57  func (c ControlHandler) Handle(h ws.Header) error {
    58  	switch h.OpCode {
    59  	case ws.OpPing:
    60  		return c.HandlePing(h)
    61  	case ws.OpPong:
    62  		return c.HandlePong(h)
    63  	case ws.OpClose:
    64  		return c.HandleClose(h)
    65  	}
    66  	return ErrNotControlFrame
    67  }
    68  
    69  // HandlePing handles ping frame and writes specification compatible response
    70  // to the c.Dst.
    71  func (c ControlHandler) HandlePing(h ws.Header) error {
    72  	if h.Length == 0 {
    73  		// The most common case when ping is empty.
    74  		// Note that when sending masked frame the mask for empty payload is
    75  		// just four zero bytes.
    76  		return ws.WriteHeader(c.Dst, ws.Header{
    77  			Fin:    true,
    78  			OpCode: ws.OpPong,
    79  			Masked: c.State.ClientSide(),
    80  		})
    81  	}
    82  
    83  	// In other way reply with Pong frame with copied payload.
    84  	p := pbytes.GetLen(int(h.Length) + ws.HeaderSize(ws.Header{
    85  		Length: h.Length,
    86  		Masked: c.State.ClientSide(),
    87  	}))
    88  	defer pbytes.Put(p)
    89  
    90  	// Deal with ciphering i/o:
    91  	// Masking key is used to mask the "Payload data" defined in the same
    92  	// section as frame-payload-data, which includes "Extension data" and
    93  	// "Application data".
    94  	//
    95  	// See https://tools.ietf.org/html/rfc6455#section-5.3
    96  	//
    97  	// NOTE: We prefer ControlWriter with preallocated buffer to
    98  	// ws.WriteHeader because it performs one syscall instead of two.
    99  	w := NewControlWriterBuffer(c.Dst, c.State, ws.OpPong, p)
   100  	r := c.Src
   101  	if c.State.ServerSide() && !c.DisableSrcCiphering {
   102  		r = NewCipherReader(r, h.Mask)
   103  	}
   104  
   105  	_, err := io.Copy(w, r)
   106  	if err == nil {
   107  		err = w.Flush()
   108  	}
   109  
   110  	return err
   111  }
   112  
   113  // HandlePong handles pong frame by discarding it.
   114  func (c ControlHandler) HandlePong(h ws.Header) error {
   115  	if h.Length == 0 {
   116  		return nil
   117  	}
   118  
   119  	buf := pbytes.GetLen(int(h.Length))
   120  	defer pbytes.Put(buf)
   121  
   122  	// Discard pong message according to the RFC6455:
   123  	// A Pong frame MAY be sent unsolicited. This serves as a
   124  	// unidirectional heartbeat. A response to an unsolicited Pong frame
   125  	// is not expected.
   126  	_, err := io.CopyBuffer(ioutil.Discard, c.Src, buf)
   127  
   128  	return err
   129  }
   130  
   131  // HandleClose handles close frame, makes protocol validity checks and writes
   132  // specification compatible response to the c.Dst.
   133  func (c ControlHandler) HandleClose(h ws.Header) error {
   134  	if h.Length == 0 {
   135  		err := ws.WriteHeader(c.Dst, ws.Header{
   136  			Fin:    true,
   137  			OpCode: ws.OpClose,
   138  			Masked: c.State.ClientSide(),
   139  		})
   140  		if err != nil {
   141  			return err
   142  		}
   143  
   144  		// Due to RFC, we should interpret the code as no status code
   145  		// received:
   146  		//   If this Close control frame contains no status code, _The WebSocket
   147  		//   Connection Close Code_ is considered to be 1005.
   148  		//
   149  		// See https://tools.ietf.org/html/rfc6455#section-7.1.5
   150  		return ClosedError{
   151  			Code: ws.StatusNoStatusRcvd,
   152  		}
   153  	}
   154  
   155  	// Prepare bytes both for reading reason and sending response.
   156  	p := pbytes.GetLen(int(h.Length) + ws.HeaderSize(ws.Header{
   157  		Length: h.Length,
   158  		Masked: c.State.ClientSide(),
   159  	}))
   160  	defer pbytes.Put(p)
   161  
   162  	// Get the subslice to read the frame payload out.
   163  	subp := p[:h.Length]
   164  
   165  	r := c.Src
   166  	if c.State.ServerSide() && !c.DisableSrcCiphering {
   167  		r = NewCipherReader(r, h.Mask)
   168  	}
   169  	if _, err := io.ReadFull(r, subp); err != nil {
   170  		return err
   171  	}
   172  
   173  	code, reason := ws.ParseCloseFrameData(subp)
   174  	if err := ws.CheckCloseFrameData(code, reason); err != nil {
   175  		// Here we could not use the prepared bytes because there is no
   176  		// guarantee that it may fit our protocol error closure code and a
   177  		// reason.
   178  		c.closeWithProtocolError(err)
   179  		return err
   180  	}
   181  
   182  	// Deal with ciphering i/o:
   183  	// Masking key is used to mask the "Payload data" defined in the same
   184  	// section as frame-payload-data, which includes "Extension data" and
   185  	// "Application data".
   186  	//
   187  	// See https://tools.ietf.org/html/rfc6455#section-5.3
   188  	//
   189  	// NOTE: We prefer ControlWriter with preallocated buffer to
   190  	// ws.WriteHeader because it performs one syscall instead of two.
   191  	w := NewControlWriterBuffer(c.Dst, c.State, ws.OpClose, p)
   192  
   193  	// RFC6455#5.5.1:
   194  	// If an endpoint receives a Close frame and did not previously
   195  	// send a Close frame, the endpoint MUST send a Close frame in
   196  	// response. (When sending a Close frame in response, the endpoint
   197  	// typically echoes the status code it received.)
   198  	_, err := w.Write(p[:2])
   199  	if err != nil {
   200  		return err
   201  	}
   202  	if err = w.Flush(); err != nil {
   203  		return err
   204  	}
   205  	return ClosedError{
   206  		Code:   code,
   207  		Reason: reason,
   208  	}
   209  }
   210  
   211  func (c ControlHandler) closeWithProtocolError(reason error) error {
   212  	f := ws.NewCloseFrame(ws.NewCloseFrameBody(
   213  		ws.StatusProtocolError, reason.Error(),
   214  	))
   215  	if c.State.ClientSide() {
   216  		ws.MaskFrameInPlace(f)
   217  	}
   218  	return ws.WriteFrame(c.Dst, f)
   219  }