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 )