class WebSocket::Frame::Handler::Handler03

Constants

FRAME_TYPES

Hash of frame names and it's opcodes

FRAME_TYPES_INVERSE

Hash of frame opcodes and it's names

Public Class Methods

new(frame) click to toggle source
Calls superclass method WebSocket::Frame::Handler::Base::new
# File lib/websocket/frame/handler/handler03.rb, line 23
def initialize(frame)
  super
  @application_data_buffer = nil
end

Public Instance Methods

decode_frame() click to toggle source

@see WebSocket::Frame::Handler::Base#decode_frame

# File lib/websocket/frame/handler/handler03.rb, line 48
def decode_frame
  while @frame.data.size > 1
    valid_header, more, frame_type, mask, payload_length = decode_header
    return unless valid_header

    application_data = decode_payload(payload_length, mask)

    if more
      decode_continuation_frame(application_data, frame_type)
    elsif frame_type == :continuation
      return decode_finish_continuation_frame(application_data)
    else
      raise(WebSocket::Error::Frame::InvalidPayloadEncoding) if frame_type == :text && !application_data.valid_encoding?
      return @frame.class.new(version: @frame.version, type: frame_type, data: application_data, decoded: true)
    end
  end
  nil
end
encode_frame() click to toggle source

@see WebSocket::Frame::Handler::Base#encode_frame

# File lib/websocket/frame/handler/handler03.rb, line 34
def encode_frame
  frame = if @frame.outgoing_masking?
            masking_key = SecureRandom.random_bytes(4)
            tmp_data = Data.new(masking_key + @frame.data)
            tmp_data.set_mask
            masking_key + tmp_data.getbytes(4, tmp_data.size)
          else
            @frame.data
          end

  encode_header + frame
end
masking?() click to toggle source

Allow turning on or off masking

# File lib/websocket/frame/handler/handler03.rb, line 68
def masking?
  false
end
supported_frames() click to toggle source

@see WebSocket::Frame::Base#supported_frames

# File lib/websocket/frame/handler/handler03.rb, line 29
def supported_frames
  %i[text binary close ping pong]
end

Private Instance Methods

buffer_exists?(buffer_number) click to toggle source
# File lib/websocket/frame/handler/handler03.rb, line 138
def buffer_exists?(buffer_number)
  !@frame.data.getbyte(buffer_number - 1).nil?
end
decode_continuation_frame(application_data, frame_type) click to toggle source
# File lib/websocket/frame/handler/handler03.rb, line 205
def decode_continuation_frame(application_data, frame_type)
  @application_data_buffer ||= String.new('')
  @application_data_buffer << application_data
  @frame_type ||= frame_type
end
decode_finish_continuation_frame(application_data) click to toggle source
# File lib/websocket/frame/handler/handler03.rb, line 211
def decode_finish_continuation_frame(application_data)
  raise(WebSocket::Error::Frame::UnexpectedContinuationFrame) unless @frame_type
  @application_data_buffer << application_data
  # Test valid UTF-8 encoding
  raise(WebSocket::Error::Frame::InvalidPayloadEncoding) if @frame_type == :text && !@application_data_buffer.valid_encoding?
  message = @frame.class.new(version: @frame.version, type: @frame_type, data: @application_data_buffer, decoded: true)
  @application_data_buffer = nil
  @frame_type = nil
  message
end
decode_first_byte() click to toggle source
# File lib/websocket/frame/handler/handler03.rb, line 142
def decode_first_byte
  first_byte = @frame.data.getbyte(0)

  raise(WebSocket::Error::Frame::ReservedBitUsed) if first_byte & 0b01110000 != 0b00000000

  more = ((first_byte & 0b10000000) == 0b10000000) ^ fin
  frame_type = opcode_to_type first_byte & 0b00001111

  raise(WebSocket::Error::Frame::FragmentedControlFrame) if more && control_frame?(frame_type)
  raise(WebSocket::Error::Frame::DataFrameInsteadContinuation) if data_frame?(frame_type) && !@application_data_buffer.nil?

  [more, frame_type]
end
decode_header() click to toggle source
# File lib/websocket/frame/handler/handler03.rb, line 118
def decode_header
  more, frame_type = decode_first_byte
  header_length, payload_length, mask = decode_second_byte(frame_type)
  return unless header_length

  # Compute the expected frame length
  frame_length = header_length + payload_length
  frame_length += 4 if mask

  raise(WebSocket::Error::Frame::TooLong) if frame_length > WebSocket.max_frame_size

  # Check buffer size
  return unless buffer_exists?(frame_length) # Buffer incomplete

  # Remove frame header
  @frame.data.slice!(0...header_length)

  [true, more, frame_type, mask, payload_length]
end
decode_payload(payload_length, mask) click to toggle source
# File lib/websocket/frame/handler/handler03.rb, line 188
def decode_payload(payload_length, mask)
  pointer = 0

  # Read application data (unmasked if required)
  @frame.data.set_mask if mask
  pointer += 4 if mask
  payload = @frame.data.getbytes(pointer, payload_length)
  payload.force_encoding('UTF-8')
  pointer += payload_length
  @frame.data.unset_mask if mask

  # Throw away data up to pointer
  @frame.data.slice!(0...pointer)

  payload
end
decode_payload_length(length) click to toggle source
# File lib/websocket/frame/handler/handler03.rb, line 169
def decode_payload_length(length)
  case length
  when 127 # Length defined by 8 bytes
    # Check buffer size
    return unless buffer_exists?(10) # Buffer incomplete

    # Only using the last 4 bytes for now, till I work out how to
    # unpack 8 bytes. I'm sure 4GB frames will do for now :)
    [10, @frame.data.getbytes(6, 4).unpack('N').first]
  when 126 # Length defined by 2 bytes
    # Check buffer size
    return unless buffer_exists?(4) # Buffer incomplete

    [4, @frame.data.getbytes(2, 2).unpack('n').first]
  else
    [2, length]
  end
end
decode_second_byte(frame_type) click to toggle source
# File lib/websocket/frame/handler/handler03.rb, line 156
def decode_second_byte(frame_type)
  second_byte = @frame.data.getbyte(1)

  mask = @frame.incoming_masking? && (second_byte & 0b10000000) == 0b10000000
  length = second_byte & 0b01111111

  raise(WebSocket::Error::Frame::ControlFramePayloadTooLong) if length > 125 && control_frame?(frame_type)

  header_length, payload_length = decode_payload_length(length)

  [header_length, payload_length, mask]
end
encode_header() click to toggle source
# File lib/websocket/frame/handler/handler03.rb, line 95
def encode_header
  mask = @frame.outgoing_masking? ? 0b10000000 : 0b00000000

  output = String.new('')
  output << (type_to_opcode(@frame.type) | (fin ? 0b10000000 : 0b00000000)) # since more, rsv1-3 are 0 and 0x80 for Draft 4
  output << encode_payload_length(@frame.data.size, mask)
  output
end
encode_payload_length(length, mask) click to toggle source
# File lib/websocket/frame/handler/handler03.rb, line 104
def encode_payload_length(length, mask)
  output = String.new('')
  if length <= 125
    output << (length | mask) # since rsv4 is 0
  elsif length < 65_536 # write 2 byte length
    output << (126 | mask)
    output << [length].pack('n')
  else # write 8 byte length
    output << (127 | mask)
    output << [length >> 32, length & 0xFFFFFFFF].pack('NN')
  end
  output
end
fin() click to toggle source

This allows flipping the more bit to fin for draft 04

# File lib/websocket/frame/handler/handler03.rb, line 75
def fin
  false
end
opcode_to_type(opcode) click to toggle source

Convert frame opcode to type name @param [Integer] opcode Opcode @return [Symbol] Frame type name or nil @raise [WebSocket::Error] if frame type name is not known

# File lib/websocket/frame/handler/handler03.rb, line 91
def opcode_to_type(opcode)
  FRAME_TYPES_INVERSE[opcode] || raise(WebSocket::Error::Frame::UnknownOpcode)
end
type_to_opcode(frame_type) click to toggle source

Convert frame type name to opcode @param [Symbol] frame_type Frame type name @return [Integer] opcode or nil @raise [WebSocket::Error] if frame opcode is not known

# File lib/websocket/frame/handler/handler03.rb, line 83
def type_to_opcode(frame_type)
  FRAME_TYPES[frame_type] || raise(WebSocket::Error::Frame::UnknownFrameType)
end