module WebSocket class Driver class Hybi class Parser def initialize(driver, require_masking) @driver = driver @require_masking = require_masking @reader = StreamReader.new @stage = 0 @frame = nil @message = nil end def parse(chunk) @reader.put(chunk) buffer = true while buffer case @stage when 0 then buffer = @reader.read(1) parse_opcode(buffer.getbyte(0)) if buffer when 1 then buffer = @reader.read(1) parse_length(buffer.getbyte(0)) if buffer when 2 then buffer = @reader.read(@frame.length_bytes) parse_extended_length(buffer) if buffer when 3 then buffer = @reader.read(4) if buffer @stage = 4 @frame.masking_key = buffer end when 4 then buffer = @reader.read(@frame.length) if buffer @stage = 0 emit_frame(buffer) end else buffer = nil end end end private def parse_opcode(octet) rsvs = [RSV1, RSV2, RSV3].map { |rsv| (octet & rsv) == rsv } @frame = Frame.new @frame.final = (octet & FIN) == FIN @frame.rsv1 = rsvs[0] @frame.rsv2 = rsvs[1] @frame.rsv3 = rsvs[2] @frame.opcode = (octet & OPCODE) @stage = 1 unless @driver.__send__(:valid_frame_rsv?, @frame.rsv1, @frame.rsv2, @frame.rsv3, @frame.opcode) return parser_error(:protocol_error, "One or more reserved bits are on: reserved1 = #{@frame.rsv1 ? 1 : 0}" + ", reserved2 = #{@frame.rsv2 ? 1 : 0 }" + ", reserved3 = #{@frame.rsv3 ? 1 : 0 }") end unless OPCODES.values.include?(@frame.opcode) return parser_error(:protocol_error, "Unrecognized frame opcode: #{@frame.opcode}") end unless MESSAGE_OPCODES.include?(@frame.opcode) or @frame.final return parser_error(:protocol_error, "Received fragmented control frame: opcode = #{@frame.opcode}") end if @message and OPENING_OPCODES.include?(@frame.opcode) return parser_error(:protocol_error, 'Received new data frame but previous continuous frame is unfinished') end end def parse_length(octet) @frame.masked = (octet & MASK) == MASK @frame.length = (octet & LENGTH) if @frame.length >= 0 and @frame.length <= 125 @stage = @frame.masked ? 3 : 4 return unless check_frame_length else @stage = 2 @frame.length_bytes = (@frame.length == 126) ? 2 : 8 end if @require_masking and not @frame.masked return parser_error(:unacceptable, 'Received unmasked frame but masking is required') end end def parse_extended_length(buffer) @frame.length = buffer.unpack(PACK_FORMATS[buffer.bytesize]).first @stage = @frame.masked ? 3 : 4 unless MESSAGE_OPCODES.include?(@frame.opcode) or @frame.length <= 125 return parser_error(:protocol_error, "Received control frame having too long payload: #{@frame.length}") end return unless check_frame_length end def check_frame_length length = @message ? @message.data.bytesize : 0 if length + @frame.length > MAX_MESSAGE_LENGTH parser_error(:too_large, 'WebSocket frame length too large') false else true end end def emit_frame(buffer) frame = @frame opcode = frame.opcode payload = frame.payload = Mask.mask(buffer, @frame.masking_key) bytesize = payload.bytesize bytes = payload.bytes.to_a @frame = nil case opcode when OPCODES[:continuation] then return parser_error(:protocol_error, 'Received unexpected continuation frame') unless @message @message << frame when OPCODES[:text], OPCODES[:binary] then @message = Message.new @message << frame when OPCODES[:close] then code = (bytesize >= 2) ? payload.unpack(PACK_FORMATS[2]).first : nil reason = (bytesize > 2) ? Driver.encode(bytes[2..-1] || [], UNICODE) : nil unless (bytesize == 0) or (code && code >= MIN_RESERVED_ERROR && code <= MAX_RESERVED_ERROR) or ERROR_CODES.include?(code) code = ERRORS[:protocol_error] end if bytesize > 125 or (bytesize > 2 and reason.nil?) code = ERRORS[:protocol_error] # TODO emit error on invalid code end @driver.__send__(:handle_close, code || DEFAULT_ERROR_CODE, reason || '') when OPCODES[:ping] then @driver.__send__(:handle_ping, payload) when OPCODES[:pong] then @driver.__send__(:handle_pong, payload) end emit_message if frame.final and MESSAGE_OPCODES.include?(opcode) end def emit_message @driver.__send__(:handle_message, @message.opcode, @message.rsv1, @message.rsv2, @message.rsv3, @message.data) @message = nil end def parser_error(type, message) @stage = -1 @driver.__send__(:handle_error, ERRORS[type], message) end end end end end