lib/http/2/framer.rb in http-2-0.7.0 vs lib/http/2/framer.rb in http-2-0.8.0

- old
+ new

@@ -1,7 +1,6 @@ module HTTP2 - # Performs encoding, decoding, and validation of binary HTTP/2 frames. # class Framer include Error @@ -28,47 +27,50 @@ ping: 0x6, goaway: 0x7, window_update: 0x8, continuation: 0x9, altsvc: 0xa, - } + }.freeze - FRAME_TYPES_WITH_PADDING = [ :data, :headers, :push_promise ] + FRAME_TYPES_WITH_PADDING = [:data, :headers, :push_promise].freeze # Per frame flags as defined by the spec FRAME_FLAGS = { data: { end_stream: 0, - padded: 3, compressed: 5 + padded: 3, + compressed: 5, }, headers: { - end_stream: 0, end_headers: 2, - padded: 3, priority: 5, + end_stream: 0, + end_headers: 2, + padded: 3, + priority: 5, }, priority: {}, rst_stream: {}, settings: { ack: 0 }, push_promise: { end_headers: 2, padded: 3, }, ping: { ack: 0 }, goaway: {}, - window_update:{}, + window_update: {}, continuation: { end_headers: 2 }, altsvc: {}, - } + }.each_value(&:freeze).freeze # Default settings as defined by the spec DEFINED_SETTINGS = { settings_header_table_size: 1, settings_enable_push: 2, settings_max_concurrent_streams: 3, settings_initial_window_size: 4, settings_max_frame_size: 5, settings_max_header_list_size: 6, - } + }.freeze # Default error types as defined by the spec DEFINED_ERRORS = { no_error: 0, protocol_error: 1, @@ -81,89 +83,87 @@ cancel: 8, compression_error: 9, connect_error: 10, enhance_your_calm: 11, inadequate_security: 12, - } + http_1_1_required: 13, + }.freeze RBIT = 0x7fffffff RBYTE = 0x0fffffff EBIT = 0x80000000 - UINT32 = "N".freeze - UINT16 = "n".freeze - UINT8 = "C".freeze + UINT32 = 'N'.freeze + UINT16 = 'n'.freeze + UINT8 = 'C'.freeze HEADERPACK = (UINT8 + UINT16 + UINT8 + UINT8 + UINT32).freeze FRAME_LENGTH_HISHIFT = 16 FRAME_LENGTH_LOMASK = 0xFFFF - BINARY = 'binary'.freeze - private_constant :RBIT, :RBYTE, :EBIT, :HEADERPACK, :UINT32, :UINT16, :UINT8, :BINARY + private_constant :RBIT, :RBYTE, :EBIT, :HEADERPACK, :UINT32, :UINT16, :UINT8 # Initializes new framer object. # def initialize @max_frame_size = DEFAULT_MAX_FRAME_SIZE end # Generates common 9-byte frame header. - # - http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-4.1 + # - http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-4.1 # # @param frame [Hash] # @return [String] - def commonHeader(frame) + def common_header(frame) header = [] - if !FRAME_TYPES[frame[:type]] - raise CompressionError.new("Invalid frame type (#{frame[:type]})") + unless FRAME_TYPES[frame[:type]] + fail CompressionError, "Invalid frame type (#{frame[:type]})" end if frame[:length] > @max_frame_size - raise CompressionError.new("Frame size is too large: #{frame[:length]}") + fail CompressionError, "Frame size is too large: #{frame[:length]}" end if frame[:length] < 0 - raise CompressionError.new("Frame size is invalid: #{frame[:length]}") + fail CompressionError, "Frame size is invalid: #{frame[:length]}" end if frame[:stream] > MAX_STREAM_ID - raise CompressionError.new("Stream ID (#{frame[:stream]}) is too large") + fail CompressionError, "Stream ID (#{frame[:stream]}) is too large" end if frame[:type] == :window_update && frame[:increment] > MAX_WINDOWINC - raise CompressionError.new("Window increment (#{frame[:increment]}) is too large") + fail CompressionError, "Window increment (#{frame[:increment]}) is too large" end header << (frame[:length] >> FRAME_LENGTH_HISHIFT) header << (frame[:length] & FRAME_LENGTH_LOMASK) header << FRAME_TYPES[frame[:type]] header << frame[:flags].reduce(0) do |acc, f| position = FRAME_FLAGS[frame[:type]][f] - if !position - raise CompressionError.new("Invalid frame flag (#{f}) for #{frame[:type]}") + unless position + fail CompressionError, "Invalid frame flag (#{f}) for #{frame[:type]}" end - acc |= (1 << position) - acc + acc | (1 << position) end header << frame[:stream] header.pack(HEADERPACK) # 8+16,8,8,32 end # Decodes common 9-byte header. # # @param buf [Buffer] - def readCommonHeader(buf) + def read_common_header(buf) frame = {} - len_hi, len_lo, type, flags, stream = buf.slice(0,9).unpack(HEADERPACK) + len_hi, len_lo, type, flags, stream = buf.slice(0, 9).unpack(HEADERPACK) frame[:length] = (len_hi << FRAME_LENGTH_HISHIFT) | len_lo - frame[:type], _ = FRAME_TYPES.select { |t,pos| type == pos }.first + frame[:type], _ = FRAME_TYPES.find { |_t, pos| type == pos } if frame[:type] - frame[:flags] = FRAME_FLAGS[frame[:type]].reduce([]) do |acc, (name, pos)| + frame[:flags] = FRAME_FLAGS[frame[:type]].each_with_object([]) do |(name, pos), acc| acc << name if (flags & (1 << pos)) > 0 - acc end end frame[:stream] = stream & RBIT frame @@ -175,115 +175,110 @@ # @param frame [Hash] def generate(frame) bytes = Buffer.new length = 0 - frame[:flags] ||= [] + frame[:flags] ||= [] frame[:stream] ||= 0 case frame[:type] when :data - bytes << frame[:payload] + bytes << frame[:payload] length += frame[:payload].bytesize when :headers if frame[:weight] || frame[:stream_dependency] || !frame[:exclusive].nil? unless frame[:weight] && frame[:stream_dependency] && !frame[:exclusive].nil? - raise CompressionError.new("Must specify all of priority parameters for #{frame[:type]}") + fail CompressionError, "Must specify all of priority parameters for #{frame[:type]}" end - frame[:flags] += [:priority] if !frame[:flags].include? :priority + frame[:flags] += [:priority] unless frame[:flags].include? :priority end if frame[:flags].include? :priority - bytes << [(frame[:exclusive] ? EBIT : 0) | - (frame[:stream_dependency] & RBIT)].pack(UINT32) + bytes << [(frame[:exclusive] ? EBIT : 0) | (frame[:stream_dependency] & RBIT)].pack(UINT32) bytes << [frame[:weight] - 1].pack(UINT8) length += 5 end - bytes << frame[:payload] + bytes << frame[:payload] length += frame[:payload].bytesize when :priority unless frame[:weight] && frame[:stream_dependency] && !frame[:exclusive].nil? - raise CompressionError.new("Must specify all of priority parameters for #{frame[:type]}") + fail CompressionError, "Must specify all of priority parameters for #{frame[:type]}" end - bytes << [(frame[:exclusive] ? EBIT : 0) | - (frame[:stream_dependency] & RBIT)].pack(UINT32) + bytes << [(frame[:exclusive] ? EBIT : 0) | (frame[:stream_dependency] & RBIT)].pack(UINT32) bytes << [frame[:weight] - 1].pack(UINT8) length += 5 when :rst_stream - bytes << pack_error(frame[:error]) + bytes << pack_error(frame[:error]) length += 4 when :settings if frame[:stream] != 0 - raise CompressionError.new("Invalid stream ID (#{frame[:stream]})") + fail CompressionError, "Invalid stream ID (#{frame[:stream]})" end - frame[:payload].each do |(k,v)| + frame[:payload].each do |(k, v)| if k.is_a? Integer - DEFINED_SETTINGS.has_value?(k) or next + DEFINED_SETTINGS.value?(k) || next else k = DEFINED_SETTINGS[k] - if k.nil? - raise CompressionError.new("Unknown settings ID for #{k}") - end + fail CompressionError, "Unknown settings ID for #{k}" if k.nil? end - bytes << [k].pack(UINT16) - bytes << [v].pack(UINT32) + bytes << [k].pack(UINT16) + bytes << [v].pack(UINT32) length += 6 end when :push_promise - bytes << [frame[:promise_stream] & RBIT].pack(UINT32) - bytes << frame[:payload] + bytes << [frame[:promise_stream] & RBIT].pack(UINT32) + bytes << frame[:payload] length += 4 + frame[:payload].bytesize when :ping if frame[:payload].bytesize != 8 - raise CompressionError.new("Invalid payload size \ - (#{frame[:payload].size} != 8 bytes)") + fail CompressionError, "Invalid payload size (#{frame[:payload].size} != 8 bytes)" end - bytes << frame[:payload] + bytes << frame[:payload] length += 8 when :goaway - bytes << [frame[:last_stream] & RBIT].pack(UINT32) - bytes << pack_error(frame[:error]) + bytes << [frame[:last_stream] & RBIT].pack(UINT32) + bytes << pack_error(frame[:error]) length += 8 if frame[:payload] - bytes << frame[:payload] + bytes << frame[:payload] length += frame[:payload].bytesize end when :window_update - bytes << [frame[:increment] & RBIT].pack(UINT32) + bytes << [frame[:increment] & RBIT].pack(UINT32) length += 4 when :continuation - bytes << frame[:payload] + bytes << frame[:payload] length += frame[:payload].bytesize when :altsvc bytes << [frame[:max_age], frame[:port]].pack(UINT32 + UINT16) length += 6 if frame[:proto] - frame[:proto].bytesize > 255 and raise CompressionError.new("Proto too long") - bytes << [frame[:proto].bytesize].pack(UINT8) << frame[:proto].force_encoding(BINARY) + fail CompressionError, 'Proto too long' if frame[:proto].bytesize > 255 + bytes << [frame[:proto].bytesize].pack(UINT8) << frame[:proto].force_encoding(Encoding::BINARY) length += 1 + frame[:proto].bytesize else bytes << [0].pack(UINT8) length += 1 end if frame[:host] - frame[:host].bytesize > 255 and raise CompressionError.new("Host too long") - bytes << [frame[:host].bytesize].pack(UINT8) << frame[:host].force_encoding(BINARY) + fail CompressionError, 'Host too long' if frame[:host].bytesize > 255 + bytes << [frame[:host].bytesize].pack(UINT8) << frame[:host].force_encoding(Encoding::BINARY) length += 1 + frame[:host].bytesize else bytes << [0].pack(UINT8) length += 1 end @@ -293,20 +288,20 @@ end end # Process padding. # frame[:padding] gives number of extra octets to be added. - # - http://tools.ietf.org/html/draft-ietf-httpbis-http2-12#section-6.1 + # - http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.1 if frame[:padding] unless FRAME_TYPES_WITH_PADDING.include?(frame[:type]) - raise CompressionError.new("Invalid padding flag for #{frame[:type]}") + fail CompressionError, "Invalid padding flag for #{frame[:type]}" end padlen = frame[:padding] if padlen <= 0 || padlen > 256 || padlen + length > @max_frame_size - raise CompressionError.new("Invalid padding #{padlen}") + fail CompressionError, "Invalid padding #{padlen}" end length += padlen bytes.prepend([padlen -= 1].pack(UINT8)) frame[:flags] << :padded @@ -316,39 +311,39 @@ # receiving. bytes << "\0" * padlen end frame[:length] = length - bytes.prepend(commonHeader(frame)) + bytes.prepend(common_header(frame)) end # Decodes complete HTTP/2 frame from provided buffer. If the buffer # does not contain enough data, no further work is performed. # # @param buf [Buffer] def parse(buf) return nil if buf.size < 9 - frame = readCommonHeader(buf) + frame = read_common_header(buf) return nil if buf.size < 9 + frame[:length] buf.read(9) payload = buf.read(frame[:length]) # Implementations MUST discard frames # that have unknown or unsupported types. - # - http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-5.5 + # - http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5.5 return nil if frame[:type].nil? # Process padding padlen = 0 if FRAME_TYPES_WITH_PADDING.include?(frame[:type]) padded = frame[:flags].include?(:padded) if padded padlen = payload.read(1).unpack(UINT8).first frame[:padding] = padlen + 1 - padlen > payload.bytesize and raise ProtocolError.new("padding too long") - padlen > 0 and payload.slice!(-padlen,padlen) + fail ProtocolError, 'padding too long' if padlen > payload.bytesize + payload.slice!(-padlen, padlen) if padlen > 0 frame[:length] -= frame[:padding] frame[:flags].delete(:padded) end end @@ -374,24 +369,24 @@ when :settings # NOTE: frame[:length] might not match the number of frame[:payload] # because unknown extensions are ignored. frame[:payload] = [] unless frame[:length] % 6 == 0 - raise ProtocolError.new("Invalid settings payload length") + fail ProtocolError, 'Invalid settings payload length' end if frame[:stream] != 0 - raise ProtocolError.new("Invalid stream ID (#{frame[:stream]})") + fail ProtocolError, "Invalid stream ID (#{frame[:stream]})" end (frame[:length] / 6).times do id = payload.read(2).unpack(UINT16).first val = payload.read_uint32 # Unsupported or unrecognized settings MUST be ignored. # Here we send it along. - name, _ = DEFINED_SETTINGS.select { |name, v| v == id }.first + name, _ = DEFINED_SETTINGS.find { |_name, v| v == id } frame[:payload] << [name, val] if name end when :push_promise frame[:promise_stream] = payload.read_uint32 & RBIT frame[:payload] = payload.read(frame[:length]) @@ -409,41 +404,37 @@ frame[:payload] = payload.read(frame[:length]) when :altsvc frame[:max_age], frame[:port] = payload.read(6).unpack(UINT32 + UINT16) len = payload.getbyte - len > 0 and frame[:proto] = payload.read(len) + frame[:proto] = payload.read(len) if len > 0 len = payload.getbyte - len > 0 and frame[:host] = payload.read(len) + frame[:host] = payload.read(len) if len > 0 - if payload.size > 0 - frame[:origin] = payload.read(payload.size) - end - else - # Unknown frame type is explicitly allowed + frame[:origin] = payload.read(payload.size) if payload.size > 0 + # else # Unknown frame type is explicitly allowed end frame end private def pack_error(e) - if !e.is_a? Integer + unless e.is_a? Integer if DEFINED_ERRORS[e].nil? - raise CompressionError.new("Unknown error ID for #{e}") + fail CompressionError, "Unknown error ID for #{e}" end e = DEFINED_ERRORS[e] end [e].pack(UINT32) end def unpack_error(e) - name, _ = DEFINED_ERRORS.select { |name, v| v == e }.first + name, _ = DEFINED_ERRORS.find { |_name, v| v == e } name || error end - end end