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

     1  package ws
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"math/rand"
     7  )
     8  
     9  // Constants defined by specification.
    10  const (
    11  	// All control frames MUST have a payload length of 125 bytes or less and MUST NOT be fragmented.
    12  	MaxControlFramePayloadSize = 125
    13  )
    14  
    15  // OpCode represents operation code.
    16  type OpCode byte
    17  
    18  // Operation codes defined by specification.
    19  // See https://tools.ietf.org/html/rfc6455#section-5.2
    20  const (
    21  	OpContinuation OpCode = 0x0
    22  	OpText         OpCode = 0x1
    23  	OpBinary       OpCode = 0x2
    24  	OpClose        OpCode = 0x8
    25  	OpPing         OpCode = 0x9
    26  	OpPong         OpCode = 0xa
    27  )
    28  
    29  // IsControl checks whether the c is control operation code.
    30  // See https://tools.ietf.org/html/rfc6455#section-5.5
    31  func (c OpCode) IsControl() bool {
    32  	// RFC6455: Control frames are identified by opcodes where
    33  	// the most significant bit of the opcode is 1.
    34  	//
    35  	// Note that OpCode is only 4 bit length.
    36  	return c&0x8 != 0
    37  }
    38  
    39  // IsData checks whether the c is data operation code.
    40  // See https://tools.ietf.org/html/rfc6455#section-5.6
    41  func (c OpCode) IsData() bool {
    42  	// RFC6455: Data frames (e.g., non-control frames) are identified by opcodes
    43  	// where the most significant bit of the opcode is 0.
    44  	//
    45  	// Note that OpCode is only 4 bit length.
    46  	return c&0x8 == 0
    47  }
    48  
    49  // IsReserved checks whether the c is reserved operation code.
    50  // See https://tools.ietf.org/html/rfc6455#section-5.2
    51  func (c OpCode) IsReserved() bool {
    52  	// RFC6455:
    53  	// %x3-7 are reserved for further non-control frames
    54  	// %xB-F are reserved for further control frames
    55  	return (0x3 <= c && c <= 0x7) || (0xb <= c && c <= 0xf)
    56  }
    57  
    58  // StatusCode represents the encoded reason for closure of websocket connection.
    59  //
    60  // There are few helper methods on StatusCode that helps to define a range in
    61  // which given code is lay in. accordingly to ranges defined in specification.
    62  //
    63  // See https://tools.ietf.org/html/rfc6455#section-7.4
    64  type StatusCode uint16
    65  
    66  // StatusCodeRange describes range of StatusCode values.
    67  type StatusCodeRange struct {
    68  	Min, Max StatusCode
    69  }
    70  
    71  // Status code ranges defined by specification.
    72  // See https://tools.ietf.org/html/rfc6455#section-7.4.2
    73  var (
    74  	StatusRangeNotInUse    = StatusCodeRange{0, 999}
    75  	StatusRangeProtocol    = StatusCodeRange{1000, 2999}
    76  	StatusRangeApplication = StatusCodeRange{3000, 3999}
    77  	StatusRangePrivate     = StatusCodeRange{4000, 4999}
    78  )
    79  
    80  // Status codes defined by specification.
    81  // See https://tools.ietf.org/html/rfc6455#section-7.4.1
    82  const (
    83  	StatusNormalClosure           StatusCode = 1000
    84  	StatusGoingAway               StatusCode = 1001
    85  	StatusProtocolError           StatusCode = 1002
    86  	StatusUnsupportedData         StatusCode = 1003
    87  	StatusNoMeaningYet            StatusCode = 1004
    88  	StatusInvalidFramePayloadData StatusCode = 1007
    89  	StatusPolicyViolation         StatusCode = 1008
    90  	StatusMessageTooBig           StatusCode = 1009
    91  	StatusMandatoryExt            StatusCode = 1010
    92  	StatusInternalServerError     StatusCode = 1011
    93  	StatusTLSHandshake            StatusCode = 1015
    94  
    95  	// StatusAbnormalClosure is a special code designated for use in
    96  	// applications.
    97  	StatusAbnormalClosure StatusCode = 1006
    98  
    99  	// StatusNoStatusRcvd is a special code designated for use in applications.
   100  	StatusNoStatusRcvd StatusCode = 1005
   101  )
   102  
   103  // In reports whether the code is defined in given range.
   104  func (s StatusCode) In(r StatusCodeRange) bool {
   105  	return r.Min <= s && s <= r.Max
   106  }
   107  
   108  // Empty reports whether the code is empty.
   109  // Empty code has no any meaning neither app level codes nor other.
   110  // This method is useful just to check that code is golang default value 0.
   111  func (s StatusCode) Empty() bool {
   112  	return s == 0
   113  }
   114  
   115  // IsNotUsed reports whether the code is predefined in not used range.
   116  func (s StatusCode) IsNotUsed() bool {
   117  	return s.In(StatusRangeNotInUse)
   118  }
   119  
   120  // IsApplicationSpec reports whether the code should be defined by
   121  // application, framework or libraries specification.
   122  func (s StatusCode) IsApplicationSpec() bool {
   123  	return s.In(StatusRangeApplication)
   124  }
   125  
   126  // IsPrivateSpec reports whether the code should be defined privately.
   127  func (s StatusCode) IsPrivateSpec() bool {
   128  	return s.In(StatusRangePrivate)
   129  }
   130  
   131  // IsProtocolSpec reports whether the code should be defined by protocol specification.
   132  func (s StatusCode) IsProtocolSpec() bool {
   133  	return s.In(StatusRangeProtocol)
   134  }
   135  
   136  // IsProtocolDefined reports whether the code is already defined by protocol specification.
   137  func (s StatusCode) IsProtocolDefined() bool {
   138  	switch s {
   139  	case StatusNormalClosure,
   140  		StatusGoingAway,
   141  		StatusProtocolError,
   142  		StatusUnsupportedData,
   143  		StatusInvalidFramePayloadData,
   144  		StatusPolicyViolation,
   145  		StatusMessageTooBig,
   146  		StatusMandatoryExt,
   147  		StatusInternalServerError,
   148  		StatusNoStatusRcvd,
   149  		StatusAbnormalClosure,
   150  		StatusTLSHandshake:
   151  		return true
   152  	}
   153  	return false
   154  }
   155  
   156  // IsProtocolReserved reports whether the code is defined by protocol specification
   157  // to be reserved only for application usage purpose.
   158  func (s StatusCode) IsProtocolReserved() bool {
   159  	switch s {
   160  	// [RFC6455]: {1005,1006,1015} is a reserved value and MUST NOT be set as a status code in a
   161  	// Close control frame by an endpoint.
   162  	case StatusNoStatusRcvd, StatusAbnormalClosure, StatusTLSHandshake:
   163  		return true
   164  	default:
   165  		return false
   166  	}
   167  }
   168  
   169  // Compiled control frames for common use cases.
   170  // For construct-serialize optimizations.
   171  var (
   172  	CompiledPing  = MustCompileFrame(NewPingFrame(nil))
   173  	CompiledPong  = MustCompileFrame(NewPongFrame(nil))
   174  	CompiledClose = MustCompileFrame(NewCloseFrame(nil))
   175  
   176  	CompiledCloseNormalClosure           = MustCompileFrame(closeFrameNormalClosure)
   177  	CompiledCloseGoingAway               = MustCompileFrame(closeFrameGoingAway)
   178  	CompiledCloseProtocolError           = MustCompileFrame(closeFrameProtocolError)
   179  	CompiledCloseUnsupportedData         = MustCompileFrame(closeFrameUnsupportedData)
   180  	CompiledCloseNoMeaningYet            = MustCompileFrame(closeFrameNoMeaningYet)
   181  	CompiledCloseInvalidFramePayloadData = MustCompileFrame(closeFrameInvalidFramePayloadData)
   182  	CompiledClosePolicyViolation         = MustCompileFrame(closeFramePolicyViolation)
   183  	CompiledCloseMessageTooBig           = MustCompileFrame(closeFrameMessageTooBig)
   184  	CompiledCloseMandatoryExt            = MustCompileFrame(closeFrameMandatoryExt)
   185  	CompiledCloseInternalServerError     = MustCompileFrame(closeFrameInternalServerError)
   186  	CompiledCloseTLSHandshake            = MustCompileFrame(closeFrameTLSHandshake)
   187  )
   188  
   189  // Header represents websocket frame header.
   190  // See https://tools.ietf.org/html/rfc6455#section-5.2
   191  type Header struct {
   192  	Fin    bool
   193  	Rsv    byte
   194  	OpCode OpCode
   195  	Masked bool
   196  	Mask   [4]byte
   197  	Length int64
   198  }
   199  
   200  // Rsv1 reports whether the header has first rsv bit set.
   201  func (h Header) Rsv1() bool { return h.Rsv&bit5 != 0 }
   202  
   203  // Rsv2 reports whether the header has second rsv bit set.
   204  func (h Header) Rsv2() bool { return h.Rsv&bit6 != 0 }
   205  
   206  // Rsv3 reports whether the header has third rsv bit set.
   207  func (h Header) Rsv3() bool { return h.Rsv&bit7 != 0 }
   208  
   209  // Frame represents websocket frame.
   210  // See https://tools.ietf.org/html/rfc6455#section-5.2
   211  type Frame struct {
   212  	Header  Header
   213  	Payload []byte
   214  }
   215  
   216  // NewFrame creates frame with given operation code,
   217  // flag of completeness and payload bytes.
   218  func NewFrame(op OpCode, fin bool, p []byte) Frame {
   219  	return Frame{
   220  		Header: Header{
   221  			Fin:    fin,
   222  			OpCode: op,
   223  			Length: int64(len(p)),
   224  		},
   225  		Payload: p,
   226  	}
   227  }
   228  
   229  // NewTextFrame creates text frame with p as payload.
   230  // Note that p is not copied.
   231  func NewTextFrame(p []byte) Frame {
   232  	return NewFrame(OpText, true, p)
   233  }
   234  
   235  // NewBinaryFrame creates binary frame with p as payload.
   236  // Note that p is not copied.
   237  func NewBinaryFrame(p []byte) Frame {
   238  	return NewFrame(OpBinary, true, p)
   239  }
   240  
   241  // NewPingFrame creates ping frame with p as payload.
   242  // Note that p is not copied.
   243  // Note that p must have length of MaxControlFramePayloadSize bytes or less due
   244  // to RFC.
   245  func NewPingFrame(p []byte) Frame {
   246  	return NewFrame(OpPing, true, p)
   247  }
   248  
   249  // NewPongFrame creates pong frame with p as payload.
   250  // Note that p is not copied.
   251  // Note that p must have length of MaxControlFramePayloadSize bytes or less due
   252  // to RFC.
   253  func NewPongFrame(p []byte) Frame {
   254  	return NewFrame(OpPong, true, p)
   255  }
   256  
   257  // NewCloseFrame creates close frame with given close body.
   258  // Note that p is not copied.
   259  // Note that p must have length of MaxControlFramePayloadSize bytes or less due
   260  // to RFC.
   261  func NewCloseFrame(p []byte) Frame {
   262  	return NewFrame(OpClose, true, p)
   263  }
   264  
   265  // NewCloseFrameBody encodes a closure code and a reason into a binary
   266  // representation.
   267  //
   268  // It returns slice which is at most MaxControlFramePayloadSize bytes length.
   269  // If the reason is too big it will be cropped to fit the limit defined by the
   270  // spec.
   271  //
   272  // See https://tools.ietf.org/html/rfc6455#section-5.5
   273  func NewCloseFrameBody(code StatusCode, reason string) []byte {
   274  	n := min(2+len(reason), MaxControlFramePayloadSize)
   275  	p := make([]byte, n)
   276  
   277  	crop := min(MaxControlFramePayloadSize-2, len(reason))
   278  	PutCloseFrameBody(p, code, reason[:crop])
   279  
   280  	return p
   281  }
   282  
   283  // PutCloseFrameBody encodes code and reason into buf.
   284  //
   285  // It will panic if the buffer is too small to accommodate a code or a reason.
   286  //
   287  // PutCloseFrameBody does not check buffer to be RFC compliant, but note that
   288  // by RFC it must be at most MaxControlFramePayloadSize.
   289  func PutCloseFrameBody(p []byte, code StatusCode, reason string) {
   290  	_ = p[1+len(reason)]
   291  	binary.BigEndian.PutUint16(p, uint16(code))
   292  	copy(p[2:], reason)
   293  }
   294  
   295  // MaskFrame masks frame and returns frame with masked payload and Mask header's field set.
   296  // Note that it copies f payload to prevent collisions.
   297  // For less allocations you could use MaskFrameInPlace or construct frame manually.
   298  func MaskFrame(f Frame) Frame {
   299  	return MaskFrameWith(f, NewMask())
   300  }
   301  
   302  // MaskFrameWith masks frame with given mask and returns frame
   303  // with masked payload and Mask header's field set.
   304  // Note that it copies f payload to prevent collisions.
   305  // For less allocations you could use MaskFrameInPlaceWith or construct frame manually.
   306  func MaskFrameWith(f Frame, mask [4]byte) Frame {
   307  	// TODO(ezoic): check CopyCipher ws copy() Cipher().
   308  	p := make([]byte, len(f.Payload))
   309  	copy(p, f.Payload)
   310  	f.Payload = p
   311  	return MaskFrameInPlaceWith(f, mask)
   312  }
   313  
   314  // MaskFrameInPlace masks frame and returns frame with masked payload and Mask
   315  // header's field set.
   316  // Note that it applies xor cipher to f.Payload without copying, that is, it
   317  // modifies f.Payload inplace.
   318  func MaskFrameInPlace(f Frame) Frame {
   319  	return MaskFrameInPlaceWith(f, NewMask())
   320  }
   321  
   322  // MaskFrameInPlaceWith masks frame with given mask and returns frame
   323  // with masked payload and Mask header's field set.
   324  // Note that it applies xor cipher to f.Payload without copying, that is, it
   325  // modifies f.Payload inplace.
   326  func MaskFrameInPlaceWith(f Frame, m [4]byte) Frame {
   327  	f.Header.Masked = true
   328  	f.Header.Mask = m
   329  	Cipher(f.Payload, m, 0)
   330  	return f
   331  }
   332  
   333  // NewMask creates new random mask.
   334  func NewMask() (ret [4]byte) {
   335  	binary.BigEndian.PutUint32(ret[:], rand.Uint32())
   336  	return
   337  }
   338  
   339  // CompileFrame returns byte representation of given frame.
   340  // In terms of memory consumption it is useful to precompile static frames
   341  // which are often used.
   342  func CompileFrame(f Frame) (bts []byte, err error) {
   343  	buf := bytes.NewBuffer(make([]byte, 0, 16))
   344  	err = WriteFrame(buf, f)
   345  	bts = buf.Bytes()
   346  	return
   347  }
   348  
   349  // MustCompileFrame is like CompileFrame but panics if frame can not be
   350  // encoded.
   351  func MustCompileFrame(f Frame) []byte {
   352  	bts, err := CompileFrame(f)
   353  	if err != nil {
   354  		panic(err)
   355  	}
   356  	return bts
   357  }
   358  
   359  // Rsv creates rsv byte representation.
   360  func Rsv(r1, r2, r3 bool) (rsv byte) {
   361  	if r1 {
   362  		rsv |= bit5
   363  	}
   364  	if r2 {
   365  		rsv |= bit6
   366  	}
   367  	if r3 {
   368  		rsv |= bit7
   369  	}
   370  	return rsv
   371  }
   372  
   373  func makeCloseFrame(code StatusCode) Frame {
   374  	return NewCloseFrame(NewCloseFrameBody(code, ""))
   375  }
   376  
   377  var (
   378  	closeFrameNormalClosure           = makeCloseFrame(StatusNormalClosure)
   379  	closeFrameGoingAway               = makeCloseFrame(StatusGoingAway)
   380  	closeFrameProtocolError           = makeCloseFrame(StatusProtocolError)
   381  	closeFrameUnsupportedData         = makeCloseFrame(StatusUnsupportedData)
   382  	closeFrameNoMeaningYet            = makeCloseFrame(StatusNoMeaningYet)
   383  	closeFrameInvalidFramePayloadData = makeCloseFrame(StatusInvalidFramePayloadData)
   384  	closeFramePolicyViolation         = makeCloseFrame(StatusPolicyViolation)
   385  	closeFrameMessageTooBig           = makeCloseFrame(StatusMessageTooBig)
   386  	closeFrameMandatoryExt            = makeCloseFrame(StatusMandatoryExt)
   387  	closeFrameInternalServerError     = makeCloseFrame(StatusInternalServerError)
   388  	closeFrameTLSHandshake            = makeCloseFrame(StatusTLSHandshake)
   389  )