class Protocol::WebSocket::Frame
Constants
- OPCODE
Attributes
The generic frame header uses the following binary representation:
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-
-+--
——--
————--------------------------------
|F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | -
-+--
——--
————-+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - -------------------------------
| |Masking-key, if MASK set to 1 | -------------------------------
——————————-+ | Masking-key (continued) | Payload Data | +——————————– - - - - - - - - - - - - - - - + : Payload Data continued … : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued … | ---------------------------------------------------------------
Public Class Methods
@parameter length [Integer] The length of the payload, or nil if the header has not been read yet. @parameter mask [Boolean | String] An optional 4-byte string which is used to mask the payload.
# File lib/protocol/websocket/frame.rb, line 32 def initialize(finished = true, payload = nil, opcode: self.class::OPCODE, mask: false) if mask == true mask = SecureRandom.bytes(4) end @finished = finished @opcode = opcode @mask = mask @length = payload&.bytesize @payload = payload end
# File lib/protocol/websocket/frame.rb, line 134 def self.parse_header(buffer) byte = buffer.unpack("C").first finished = (byte & 0b1000_0000 != 0) # rsv = byte & 0b0111_0000 opcode = byte & 0b0000_1111 return finished, opcode end
# File lib/protocol/websocket/frame.rb, line 144 def self.read(finished, opcode, stream, maximum_frame_size) buffer = stream.read(1) or raise EOFError, "Could not read header!" byte = buffer.unpack("C").first mask = (byte & 0b1000_0000 != 0) length = byte & 0b0111_1111 if length == 126 buffer = stream.read(2) or raise EOFError, "Could not read length!" length = buffer.unpack('n').first elsif length == 127 buffer = stream.read(8) or raise EOFError, "Could not read length!" length = buffer.unpack('Q>').first end if length > maximum_frame_size raise ProtocolError, "Invalid payload length: #{@length} > #{maximum_frame_size}!" end if mask mask = stream.read(4) or raise EOFError, "Could not read mask!" end payload = stream.read(length) or raise EOFError, "Could not read payload!" if payload.bytesize != length raise EOFError, "Incorrect payload length: #{@length} != #{@payload.bytesize}!" end return self.new(finished, payload, opcode: opcode, mask: mask) end
Public Instance Methods
# File lib/protocol/websocket/frame.rb, line 44 def <=> other to_ary <=> other.to_ary end
# File lib/protocol/websocket/frame.rb, line 130 def apply(connection) connection.receive_frame(self) end
# File lib/protocol/websocket/frame.rb, line 64 def continued? @finished == false end
# File lib/protocol/websocket/frame.rb, line 52 def control? @opcode & 0x8 end
# File lib/protocol/websocket/frame.rb, line 56 def data? false end
# File lib/protocol/websocket/frame.rb, line 60 def finished? @finished == true end
# File lib/protocol/websocket/frame.rb, line 95 def pack(data) length = data.bytesize if length.bit_length > 63 raise ProtocolError, "Frame length #{@length} bigger than allowed maximum!" end if @mask @payload = String.new.b for i in 0...data.bytesize do @payload << (data.getbyte(i) ^ mask.getbyte(i % 4)) end @length = length else @payload = data @length = length end end
# File lib/protocol/websocket/frame.rb, line 48 def to_ary [@finished, @opcode, @mask, @length, @payload] end
# File lib/protocol/websocket/frame.rb, line 116 def unpack if @mask and !@payload.empty? data = String.new.b for i in 0...@payload.bytesize do data << (@payload.getbyte(i) ^ @mask.getbyte(i % 4)) end return data else return @payload end end
# File lib/protocol/websocket/frame.rb, line 176 def write(stream) buffer = String.new.b if @payload&.bytesize != @length raise ProtocolError, "Invalid payload length: #{@length} != #{@payload.bytesize} for #{self}!" end if @mask and @mask.bytesize != 4 raise ProtocolError, "Invalid mask length!" end if length <= 125 short_length = length elsif length.bit_length <= 16 short_length = 126 else short_length = 127 end buffer << [ (@finished ? 0b1000_0000 : 0) | @opcode, (@mask ? 0b1000_0000 : 0) | short_length, ].pack('CC') if short_length == 126 buffer << [@length].pack('n') elsif short_length == 127 buffer << [@length].pack('Q>') end buffer << @mask if @mask stream.write(buffer) stream.write(@payload) end