github.com/simonmittag/ws@v1.1.0-rc.5.0.20210419231947-82b846128245/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  // Rsv creates rsv byte representation from bits.
   210  func Rsv(r1, r2, r3 bool) (rsv byte) {
   211  	if r1 {
   212  		rsv |= bit5
   213  	}
   214  	if r2 {
   215  		rsv |= bit6
   216  	}
   217  	if r3 {
   218  		rsv |= bit7
   219  	}
   220  	return rsv
   221  }
   222  
   223  // RsvBits returns rsv bits from bytes representation.
   224  func RsvBits(rsv byte) (r1, r2, r3 bool) {
   225  	r1 = rsv&bit5 != 0
   226  	r2 = rsv&bit6 != 0
   227  	r3 = rsv&bit7 != 0
   228  	return
   229  }
   230  
   231  // Frame represents websocket frame.
   232  // See https://tools.ietf.org/html/rfc6455#section-5.2
   233  type Frame struct {
   234  	Header  Header
   235  	Payload []byte
   236  }
   237  
   238  // NewFrame creates frame with given operation code,
   239  // flag of completeness and payload bytes.
   240  func NewFrame(op OpCode, fin bool, p []byte) Frame {
   241  	return Frame{
   242  		Header: Header{
   243  			Fin:    fin,
   244  			OpCode: op,
   245  			Length: int64(len(p)),
   246  		},
   247  		Payload: p,
   248  	}
   249  }
   250  
   251  // NewTextFrame creates text frame with p as payload.
   252  // Note that p is not copied.
   253  func NewTextFrame(p []byte) Frame {
   254  	return NewFrame(OpText, true, p)
   255  }
   256  
   257  // NewBinaryFrame creates binary frame with p as payload.
   258  // Note that p is not copied.
   259  func NewBinaryFrame(p []byte) Frame {
   260  	return NewFrame(OpBinary, true, p)
   261  }
   262  
   263  // NewPingFrame creates ping frame with p as payload.
   264  // Note that p is not copied.
   265  // Note that p must have length of MaxControlFramePayloadSize bytes or less due
   266  // to RFC.
   267  func NewPingFrame(p []byte) Frame {
   268  	return NewFrame(OpPing, true, p)
   269  }
   270  
   271  // NewPongFrame creates pong frame with p as payload.
   272  // Note that p is not copied.
   273  // Note that p must have length of MaxControlFramePayloadSize bytes or less due
   274  // to RFC.
   275  func NewPongFrame(p []byte) Frame {
   276  	return NewFrame(OpPong, true, p)
   277  }
   278  
   279  // NewCloseFrame creates close frame with given close body.
   280  // Note that p is not copied.
   281  // Note that p must have length of MaxControlFramePayloadSize bytes or less due
   282  // to RFC.
   283  func NewCloseFrame(p []byte) Frame {
   284  	return NewFrame(OpClose, true, p)
   285  }
   286  
   287  // NewCloseFrameBody encodes a closure code and a reason into a binary
   288  // representation.
   289  //
   290  // It returns slice which is at most MaxControlFramePayloadSize bytes length.
   291  // If the reason is too big it will be cropped to fit the limit defined by the
   292  // spec.
   293  //
   294  // See https://tools.ietf.org/html/rfc6455#section-5.5
   295  func NewCloseFrameBody(code StatusCode, reason string) []byte {
   296  	n := min(2+len(reason), MaxControlFramePayloadSize)
   297  	p := make([]byte, n)
   298  
   299  	crop := min(MaxControlFramePayloadSize-2, len(reason))
   300  	PutCloseFrameBody(p, code, reason[:crop])
   301  
   302  	return p
   303  }
   304  
   305  // PutCloseFrameBody encodes code and reason into buf.
   306  //
   307  // It will panic if the buffer is too small to accommodate a code or a reason.
   308  //
   309  // PutCloseFrameBody does not check buffer to be RFC compliant, but note that
   310  // by RFC it must be at most MaxControlFramePayloadSize.
   311  func PutCloseFrameBody(p []byte, code StatusCode, reason string) {
   312  	_ = p[1+len(reason)]
   313  	binary.BigEndian.PutUint16(p, uint16(code))
   314  	copy(p[2:], reason)
   315  }
   316  
   317  // MaskFrame masks frame and returns frame with masked payload and Mask header's field set.
   318  // Note that it copies f payload to prevent collisions.
   319  // For less allocations you could use MaskFrameInPlace or construct frame manually.
   320  func MaskFrame(f Frame) Frame {
   321  	return MaskFrameWith(f, NewMask())
   322  }
   323  
   324  // MaskFrameWith masks frame with given mask and returns frame
   325  // with masked payload and Mask header's field set.
   326  // Note that it copies f payload to prevent collisions.
   327  // For less allocations you could use MaskFrameInPlaceWith or construct frame manually.
   328  func MaskFrameWith(f Frame, mask [4]byte) Frame {
   329  	// TODO(gobwas): check CopyCipher ws copy() Cipher().
   330  	p := make([]byte, len(f.Payload))
   331  	copy(p, f.Payload)
   332  	f.Payload = p
   333  	return MaskFrameInPlaceWith(f, mask)
   334  }
   335  
   336  // MaskFrameInPlace masks frame and returns frame with masked payload and Mask
   337  // header's field set.
   338  // Note that it applies xor cipher to f.Payload without copying, that is, it
   339  // modifies f.Payload inplace.
   340  func MaskFrameInPlace(f Frame) Frame {
   341  	return MaskFrameInPlaceWith(f, NewMask())
   342  }
   343  
   344  var zeroMask [4]byte
   345  
   346  // UnmaskFrame unmasks frame and returns frame with unmasked payload and Mask
   347  // header's field cleared.
   348  // Note that it copies f payload.
   349  func UnmaskFrame(f Frame) Frame {
   350  	p := make([]byte, len(f.Payload))
   351  	copy(p, f.Payload)
   352  	f.Payload = p
   353  	return UnmaskFrameInPlace(f)
   354  }
   355  
   356  // UnmaskFrameInPlace unmasks frame and returns frame with unmasked payload and
   357  // Mask header's field cleared.
   358  // Note that it applies xor cipher to f.Payload without copying, that is, it
   359  // modifies f.Payload inplace.
   360  func UnmaskFrameInPlace(f Frame) Frame {
   361  	Cipher(f.Payload, f.Header.Mask, 0)
   362  	f.Header.Masked = false
   363  	f.Header.Mask = zeroMask
   364  	return f
   365  }
   366  
   367  // MaskFrameInPlaceWith masks frame with given mask and returns frame
   368  // with masked payload and Mask header's field set.
   369  // Note that it applies xor cipher to f.Payload without copying, that is, it
   370  // modifies f.Payload inplace.
   371  func MaskFrameInPlaceWith(f Frame, m [4]byte) Frame {
   372  	f.Header.Masked = true
   373  	f.Header.Mask = m
   374  	Cipher(f.Payload, m, 0)
   375  	return f
   376  }
   377  
   378  // NewMask creates new random mask.
   379  func NewMask() (ret [4]byte) {
   380  	binary.BigEndian.PutUint32(ret[:], rand.Uint32())
   381  	return
   382  }
   383  
   384  // CompileFrame returns byte representation of given frame.
   385  // In terms of memory consumption it is useful to precompile static frames
   386  // which are often used.
   387  func CompileFrame(f Frame) (bts []byte, err error) {
   388  	buf := bytes.NewBuffer(make([]byte, 0, 16))
   389  	err = WriteFrame(buf, f)
   390  	bts = buf.Bytes()
   391  	return
   392  }
   393  
   394  // MustCompileFrame is like CompileFrame but panics if frame can not be
   395  // encoded.
   396  func MustCompileFrame(f Frame) []byte {
   397  	bts, err := CompileFrame(f)
   398  	if err != nil {
   399  		panic(err)
   400  	}
   401  	return bts
   402  }
   403  
   404  func makeCloseFrame(code StatusCode) Frame {
   405  	return NewCloseFrame(NewCloseFrameBody(code, ""))
   406  }
   407  
   408  var (
   409  	closeFrameNormalClosure           = makeCloseFrame(StatusNormalClosure)
   410  	closeFrameGoingAway               = makeCloseFrame(StatusGoingAway)
   411  	closeFrameProtocolError           = makeCloseFrame(StatusProtocolError)
   412  	closeFrameUnsupportedData         = makeCloseFrame(StatusUnsupportedData)
   413  	closeFrameNoMeaningYet            = makeCloseFrame(StatusNoMeaningYet)
   414  	closeFrameInvalidFramePayloadData = makeCloseFrame(StatusInvalidFramePayloadData)
   415  	closeFramePolicyViolation         = makeCloseFrame(StatusPolicyViolation)
   416  	closeFrameMessageTooBig           = makeCloseFrame(StatusMessageTooBig)
   417  	closeFrameMandatoryExt            = makeCloseFrame(StatusMandatoryExt)
   418  	closeFrameInternalServerError     = makeCloseFrame(StatusInternalServerError)
   419  	closeFrameTLSHandshake            = makeCloseFrame(StatusTLSHandshake)
   420  )