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 )